系统学习C++-第二十一讲-用哈希表封装 myunordered_map 和 myunordered_set

系统学习C++-第二十一讲-用哈希表封装 myunordered_map 和 myunordered_set

系统学习C++-第二十一讲-用哈希表封装 myunordered_map 和 myunordered_set

1. 源码及框架分析

SGI-STL30 版本源代码中没有 unordered_mapunordered_set ,SGI-STL30 版本是 C++11 之前的STL版本,

这两个容器是 C++11 之后才更新的。但是 SGI-STL30 实现了哈希表,只是容器的名字是 hash_maphash_set

他是作为非标准的容器出现的,非标准是指非 C++ 标准规定必须实现的,

源代码在hash_map/hash_set/stl_hash_map/stl_hash_set/stl_hashtable.h

hash_maphash_set 的实现结构框架核心部分截取出来如下:

// stl_hash_settemplate<classValue,classHashFcn= hash<Value>,classEqualKey= equal_to<Value>,classAlloc= alloc>classhash_set{private:typedef hashtable<Value, Value, HashFcn, identity<Value>, EqualKey, Alloc> ht; ht rep;public:typedeftypenameht::key_type key_type;typedeftypenameht::value_type value_type;typedeftypenameht::hasher hasher;typedeftypenameht::key_equal key_equal;typedeftypenameht::const_iterator iterator;typedeftypenameht::const_iterator const_iterator; hasher hash_funct()const{return rep.hash_funct();} key_equal key_eq()const{return rep.key_eq();}};// stl_hash_maptemplate<classKey,classT,classHashFcn= hash<Key>,classEqualKey= equal_to<Key>,classAlloc= alloc>classhash_map{private:typedef hashtable<pair<const Key, T>, Key, HashFcn, select1st<pair<const Key, T>>, EqualKey, Alloc> ht; ht rep;public:typedeftypenameht::key_type key_type;typedef T data_type;typedef T mapped_type;typedeftypenameht::value_type value_type;typedeftypenameht::hasher hasher;typedeftypenameht::key_equal key_equal;typedeftypenameht::iterator iterator;typedeftypenameht::const_iterator const_iterator;};// stl_hashtable.htemplate<classValue,classKey,classHashFcn,classExtractKey,classEqualKey,classAlloc>classhashtable{public:typedef Key key_type;typedef Value value_type;typedef HashFcn hasher;typedef EqualKey key_equal;private: hasher hash; key_equal equals; ExtractKey get_key;typedef __hashtable_node<Value> node; vector<node*,Alloc> buckets; size_type num_elements;public:typedef __hashtable_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> iterator; pair<iterator,bool>insert_unique(const value_type& obj); const_iterator find(const key_type& key)const;};template<classValue>struct__hashtable_node{ __hashtable_node* next; Value val;};
  • 这里我们就不再画图分析了,通过源码可以看到,结构上 hash_maphash_setmapset 的完全类似,复用同⼀个hashtable 实现 keykey/value 结构,hash_set 传给 hash_table 的是两个 keyhash_map 传给 hash_table 的是pair<const key, value>
  • 需要注意的是源码里面跟 map / set 源码类似,命名风格比较乱,这里比 mapset 还乱,hash_set 模板参数居然用的 Value 命名,hash_map 用的是 KeyT 命名,可见大佬有时写代码也不规范。下面我们模拟⼀份自己的出来,就按自己的风格走了。

2. 模拟实现 unordered_map 和 unordered_set

2.1 实现出复用哈希表的框架,并支持 insert

  • 参考源码框架,unordered_mapunordered_set 复用之前我们实现的哈希表。
  • 我们这里相比源码调整⼀下,key 参数就用 Kvalue 参数就用 V ,哈希表中的数据类型,我们使用 T
  • 其次跟 mapset 相比而言 unordered_mapunordered_set 的模拟实现类结构更复杂⼀点,但是大框架和思路是完全类似的。因为 HashTable 实现了泛型不知道 T 参数导致是 K ,还是 pair<K, V> ,那么 insert 内部进行插入时要用 K 对象转换成整形取模和 K 比较相等,因为 pairvalue 不参与计算取模,且默认支持的是 keyvalue ⼀起比较相等,我们需要时的任何时候只需要比较 K 对象,所以我们在 unordered_mapunordered_set 层分别实现⼀个 MapKeyOfTSetKeyOfT 的仿函数传给 HashTableKeyOfT ,然后 HashTable 中通过 KeyOfT 仿函数取出 T 类型对象中的 K 对象,再转换成整形取模和 K 比较相等,具体细节参考如下代码实现。
// MyUnorderedSet.hnamespace My_UnorderedSet {template<classK,classHash= HashFunc<K>>classunordered_set{structSetKeyOfT{const K&operator()(const K& key){return key;}};public:boolinsert(const K& key){return _ht.Insert(key);}private: hash_bucket::HashTable<K, K, SetKeyOfT, Hash> _ht;};}// MyUnorderedMap.hnamespace My_UnorderedMap {template<classK,classV,classHash= HashFunc<K>>classunordered_map{structMapKeyOfT{const K&operator()(const pair<K, V>& kv){return kv.first;}};public:boolinsert(const pair<K, V>& kv){return _ht.Insert(kv);}private: hash_bucket::HashTable<K, pair<K, V>, MapKeyOfT, Hash> _ht;};}// HashTable.htemplate<classK>structHashFunc{ size_t operator()(const K& key){return(size_t)key;}};namespace hash_bucket {template<classT>structHashNode{ T _data; HashNode<T>* _next;HashNode(const T& data):_data(data),_next(nullptr){}};// 实现步骤: // 1、实现哈希表 // 2、封装unordered_map和unordered_set的框架 解决KeyOfT // 3、iterator // 4、const_iterator // 5、key不⽀持修改的问题 // 6、operator[] template<classK,classT,classKeyOfT,classHash>classHashTable{typedef HashNode<T> Node;inlineunsignedlong__stl_next_prime(unsignedlong n){staticconstint __stl_num_primes =28;staticconstunsignedlong __stl_prime_list[__stl_num_primes]={53,97,193,389,769,1543,3079,6151,12289,24593,49157,98317,196613,393241,786433,1572869,3145739,6291469,12582917,25165843,50331653,100663319,201326611,402653189,805306457,1610612741,3221225473,4294967291};constunsignedlong* first = __stl_prime_list;constunsignedlong* last = __stl_prime_list + __stl_num_primes;constunsignedlong* pos =lower_bound(first, last, n);return pos == last ?*(last -1):*pos;}public:HashTable(){ _tables.resize(__stl_next_prime(_tables.size()),nullptr);}~HashTable(){// 依次把每个桶释放 for(size_t i =0; i < _tables.size(); i++){ Node* cur = _tables[i];while(cur){ Node* next = cur->_next;delete cur; cur = next;} _tables[i]=nullptr;}}boolInsert(const T& data){ KeyOfT kot;if(Find(kot(data)))returnfalse; Hash hs; size_t hashi =hs(kot(data))% _tables.size();// 负载因⼦==1扩容 if(_n == _tables.size()){ vector<Node*>newtables(__stl_next_prime(_tables.size()),nullptr);for(size_t i =0; i < _tables.size(); i++){ Node* cur = _tables[i];while(cur){ Node* next = cur->_next;// 旧表中结点,挪动新表重新映射的位置  size_t hashi =hs(kot(cur->_data))% newtables.size();// 头插到新表  cur->_next = newtables[hashi]; newtables[hashi]= cur; cur = next;} _tables[i]=nullptr;} _tables.swap(newtables);}// 头插  Node* newnode =newNode(data); newnode->_next = _tables[hashi]; _tables[hashi]= newnode;++_n;returntrue;}private: vector<Node*> _tables;// 指针数组  size_t _n =0;// 表中存储数据个数 };}

2.2 支持 iterator 的实现

iterator 核心源代码:

template<classValue,classKey,classHashFcn,classExtractKey,classEqualKey,classAlloc>struct__hashtable_iterator{typedef hashtable<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> hashtable;typedef __hashtable_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> iterator;typedef __hashtable_const_iterator<Value, Key, HashFcn, ExtractKey, EqualKey, Alloc> const_iterator;typedef __hashtable_node<Value> node;typedef forward_iterator_tag iterator_category;typedef Value value_type; node* cur; hashtable* ht;__hashtable_iterator(node* n, hashtable* tab):cur(n),ht(tab){}__hashtable_iterator(){} reference operator*()const{return cur->val;}#ifndef__SGI_STL_NO_ARROW_OPERATOR pointer operator->()const{return&(operator*());}#endif/* __SGI_STL_NO_ARROW_OPERATOR */ iterator&operator++(); iterator operator++(int);booloperator==(const iterator& it)const{return cur == it.cur;}booloperator!=(const iterator& it)const{return cur != it.cur;}};template<classV,classK,classHF,classExK,classEqK,classA> __hashtable_iterator<V, K, HF, ExK, EqK, A>& __hashtable_iterator<V, K, HF, ExK, EqK, A>::operator++(){const node* old = cur; cur = cur->next;if(!cur){ size_type bucket = ht->bkt_num(old->val);while(!cur &&++bucket < ht->buckets.size()) cur = ht->buckets[bucket];}return*this;}

iterator 实现思路分析:

  • iterator 实现的大框架跟 listiterator 思路是一致的,用一个类型封装结点的指针,再通过重载运算符实现,迭代器像指针一样访问的行为,要注意的是哈希表的迭代器是单向迭代器。
  • 这里的难点是 operator++ 的实现。iterator 中有一个指向结点的指针,如果当前桶下面还有结点,则结点的指针指向下一个结点即可。如果当前桶走完了,则需要想办法计算找到下一个桶。这里的难点是反而是结构设计的问题,参考上面的源码,我们可以看到iterator 中除了有结点的指针,还有哈希表对象的指针,这样当前桶走完了,要计算下⼀个桶就相对容易多了,用 key 值计算出当前桶位置,依次往后找下一个不为空的桶即可。
  • begin() 返回第一个桶中第一个节点指针构造的迭代器,这里 end() 返回迭代器可以用空表示。
  • unordered_setiterator 也不支持修改,我们把 unordered_set 的第⼆个模板参数改成 const K 即可,
    HashTable<K, const K, SetKeyOfT, Hash> _ht;
  • unordered_mapiterator 不支持修改 key 但是可以修改 value ,我们把 unordered_map 的第二个模板参数 pair 的第一个参数改成 const K 即可,HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;
  • 支持完整的迭代器还有很多细节需要修改,具体参考下面题的代码。
在这里插入图片描述

2.3 map 支持 []

  • unordered_map 要支持 [] 主要需要修改 insert 返回值支持,修改 HashTable 中的 insert 返回值为
    pair<Iterator, bool> Insert(const T& data)
  • 有了 insert 支持 [] 实现就很简单了,具体参考下面代码实现

2.4 unordered_map 和 unordered_set 代码实现

//HashTable.h#pragmaonce#include<iostream>#include<vector>usingnamespace std;inlineunsignedlong__stl_next_prime(unsignedlong n){// Note: assumes long is at least 32 bits.staticconstint __stl_num_primes =28;staticconstunsignedlong __stl_prime_list[__stl_num_primes]={53,97,193,389,769,1543,3079,6151,12289,24593,49157,98317,196613,393241,786433,1572869,3145739,6291469,12582917,25165843,50331653,100663319,201326611,402653189,805306457,1610612741,3221225473,4294967291};constunsignedlong* first = __stl_prime_list;constunsignedlong* last = __stl_prime_list + __stl_num_primes;// >=constunsignedlong* pos =lower_bound(first, last, n);return pos == last ?*(last -1):*pos;}template<classK>structHashFunc{ size_t operator()(const K& key)const{return(size_t)key;}};// 特化template<>structHashFunc<string>{ size_t operator()(const string& key)const{ size_t hash =0;for(auto ch : key){ hash += ch; hash *=131;}return hash;}};namespace open_adrress {enumState{ EXIST, EMPTY, DELETE };template<classK,classV>structHashData{ pair<K, V> _kv; State _state = EMPTY;};//struct StringHashFunc//{// size_t operator()(const string& key) const// {// size_t hash = 0;// for (auto ch : key)// {// hash += ch;// hash *= 131;// }//// return hash;// }//};template<classK,classV,classHash= HashFunc<K>>classHashTable{public:HashTable(size_t n =__stl_next_prime(0)):_tables(n),_n(0){}boolInsert(const pair<K, V>& kv){if(Find(kv.first))returnfalse;// 扩容,负载因子==0.7就扩容if((double)_n /(double)_tables.size()>=0.7){//vector<HashData<K, V>> newtables;//newtables.resize(_tables.size() * 2);//// 遍历旧表,将旧表的数据全部重新映射到新表//for (size_t i = 0; i < _tables.size(); i++)//{// if (_tables[i]._state == EXIST)// {// //...// }//}//_tables.swap(newtables);//HashTable<K, V> newht(_tables.size() * 2); HashTable<K, V, Hash>newht(__stl_next_prime(_tables.size()+1));// 遍历旧表,将旧表的数据全部重新映射到新表for(size_t i =0; i < _tables.size(); i++){if(_tables[i]._state == EXIST){ newht.Insert(_tables[i]._kv);}} _tables.swap(newht._tables);} Hash hs; size_t hash0 =hs(kv.first)% _tables.size(); size_t hashi = hash0; size_t i =1;// 线性探测while(_tables[hashi]._state == EXIST){ hashi += i * i; i++; hashi %= _tables.size();} _tables[hashi]._kv = kv; _tables[hashi]._state = EXIST;++_n;returntrue;} HashData<K, V>*Find(const K& key){ Hash hs; size_t hash0 =hs(key)% _tables.size(); size_t hashi = hash0; size_t i =1;while(_tables[hashi]._state != EMPTY){if(_tables[hashi]._state == EXIST && _tables[hashi]._kv.first == key){return&_tables[hashi];}// 线性探测 hashi += i; i++; hashi %= _tables.size();}returnnullptr;}boolErase(const K& key){ HashData<K, V>* ret =Find(key);if(ret){ ret->_state = DELETE;--_n;returntrue;}else{returnfalse;}}private: vector<HashData<K, V>> _tables; size_t _n;// 实际存储的数据个数};voidTestHashTable1(){int a[]={19,30,5,36,13,20,21,12}; HashTable<int,int> ht;for(auto e : a){ ht.Insert({ e, e });} cout << ht.Find(20)<< endl; cout << ht.Find(30)<< endl; ht.Erase(30); cout << ht.Find(20)<< endl; cout << ht.Find(30)<< endl; ht.Insert({-3,3}); ht.Insert({13,3}); ht.Insert({33,3}); ht.Insert({323,3}); ht.Insert({23,3});for(size_t i =0; i <100; i++){ ht.Insert({rand(),i });}}structpairHash{ size_t operator()(const pair<int,int>& kv)const{ size_t hash =0; hash += kv.first; hash *=131; hash += kv.second; hash *=131; cout << hash << endl;return hash;}};// 21:07voidTestHashTable2(){//HashTable<string, string, StringHashFunc> dict; HashTable<string, string> dict; dict.Insert({"sort","排序"}); dict.Insert({"left","左边"}); dict.Insert({"sort","xxx"}); unordered_map<pair<int,int>,int, pairHash> um; um.insert({{1,3},3}); um.insert({{3,1},3});/*StringHashFunc hf; cout << hf("abcd") <<endl; cout << hf("bcad") << endl; cout << hf("aadd") << endl;*/}}namespace hash_bucket {template<classT>structHashNode{ T _data; HashNode<T>* _next;HashNode(const T& data):_data(data),_next(nullptr){}};// 前置声明template<classK,classT,classKeyOfT,classHash>classHashTable;template<classK,classT,classRef,classPtr,classKeyOfT,classHash>structHTIterator{typedef HashNode<T> Node;typedef HashTable<K, T, KeyOfT, Hash> HT;typedef HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self; Node* _node;const HT* _pht;HTIterator(Node* node,const HT* pht):_node(node),_pht(pht){} Self&operator++(){if(_node->_next){// 当前桶还没走完 _node = _node->_next;}else// 21:05{// 当前桶走完了,需要找下一个不为空的桶里面的第一个节点 KeyOfT kot; Hash hs; size_t hashi =hs(kot(_node->_data))% _pht->_tables.size(); hashi++;while(hashi < _pht->_tables.size()){if(_pht->_tables[hashi]){ _node = _pht->_tables[hashi];break;}++hashi;}if(hashi == _pht->_tables.size()){// 所有桶都走完了,置为end() _node =nullptr;}}return*this;} Ref operator*(){return _node->_data;} Ptr operator->(){return&_node->_data;}booloperator!=(const Self& s)const{return _node != s._node;}booloperator==(const Self& s)const{return _node == s._node;}};template<classK,classT,classKeyOfT,classHash>classHashTable{// 友元声明template<classK,classT,classRef,classPtr,classKeyOfT,classHash>friendstructHTIterator;typedef HashNode<T> Node;public:typedef HTIterator<K, T, T&, T*, KeyOfT, Hash> Iterator;typedef HTIterator<K, T,const T&,const T*, KeyOfT, Hash> ConstIterator; Iterator Begin(){if(_n ==0)returnEnd();for(size_t i =0; i < _tables.size(); i++){if(_tables[i]){returnIterator(_tables[i],this);}}returnEnd();} Iterator End(){returnIterator(nullptr,this);} ConstIterator Begin()const{if(_n ==0)returnEnd();for(size_t i =0; i < _tables.size(); i++){if(_tables[i]){returnConstIterator(_tables[i],this);}}returnEnd();} ConstIterator End()const{returnConstIterator(nullptr,this);}HashTable(size_t n =__stl_next_prime(0)):_tables(n,nullptr),_n(0){}~HashTable(){for(size_t i =0; i < _tables.size(); i++){ Node* cur = _tables[i];while(cur){ Node* next = cur->_next;delete cur; cur = next;} _tables[i]=nullptr;}} pair<Iterator,bool>Insert(const T& data){ KeyOfT kot; Iterator it =Find(kot(data));if(it !=End())return{ it,false}; Hash hs;// 负载因子到1扩容if(_n == _tables.size()){//HashTable<K, V> newht(__stl_next_prime(_tables.size() + 1));//// 遍历旧表,将旧表的数据全部重新映射到新表//for (size_t i = 0; i < _tables.size(); i++)//{// Node* cur = _tables[i];// while (cur)// {// newht.Insert(cur->_kv);// cur = cur->_next;// }//}//_tables.swap(newht._tables);// 扩容 vector<Node*>newtables(__stl_next_prime(_tables.size()+1),nullptr);// 遍历旧表,将旧表的数据全部重新映射到新表for(size_t i =0; i < _tables.size(); i++){ Node* cur = _tables[i];while(cur){ Node* next = cur->_next;// cur头插到新表 size_t hashi =hs(kot(cur->_data))% newtables.size(); cur->_next = newtables[hashi]; newtables[hashi]= cur; cur = next;} _tables[i]=nullptr;} _tables.swap(newtables);} size_t hashi =hs(kot(data))% _tables.size();// 头插 Node* newnode =newNode(data); newnode->_next = _tables[hashi]; _tables[hashi]= newnode;++_n;return{Iterator(newnode,this),true};} Iterator Find(const K& key){ Hash hs; KeyOfT kot; size_t hashi =hs(key)% _tables.size(); Node* cur = _tables[hashi];while(cur){if(kot(cur->_data)== key)returnIterator(cur,this); cur = cur->_next;}returnEnd();}boolErase(const K& key){ Hash hs; size_t hashi =hs(key)% _tables.size(); KeyOfT kot; Node* prev =nullptr; Node* cur = _tables[hashi];while(cur){if(kot(cur->_data)== key){if(prev ==nullptr){ _tables[hashi]= cur->_next;}else{ prev->_next = cur->_next;}--_n;delete cur;returntrue;} prev = cur; cur = cur->_next;}returnfalse;}private: vector<Node*> _tables; size_t _n;// 实际存储有效数据个数};//unordered_set.h#include"HashTable.h"namespace My_UnorderedSet {template<classK,classHash= HashFunc<K>>classunordered_set{structSetKeyOfT{const K&operator()(const K& key){return key;}};public:typedeftypenamehash_bucket::HashTable<K,const K, SetKeyOfT, Hash>::Iterator iterator;typedeftypenamehash_bucket::HashTable<K,const K, SetKeyOfT, Hash>::ConstIterator const_iterator; iterator begin(){return _ht.Begin();} iterator end(){return _ht.End();} const_iterator begin()const{return _ht.Begin();} const_iterator end()const{return _ht.End();} pair<iterator,bool>insert(const K& key){return _ht.Insert(key);} iterator find(const K& key){return _ht.Find(key);}boolerase(const K& key){return _ht.Erase(key);}private: hash_bucket::HashTable<K,const K, SetKeyOfT, Hash> _ht;};}//unordered_map.h#include"HashTable.h"namespace My_UnorderedMap {template<classK,classV,classHash= HashFunc<K>>classunordered_map{structMapKeyOfT{const K&operator()(const pair<K, V>& kv){return kv.first;}};public:typedeftypenamehash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::Iterator iterator;typedeftypenamehash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::ConstIterator const_iterator; iterator begin(){return _ht.Begin();} iterator end(){return _ht.End();} const_iterator begin()const{return _ht.Begin();} const_iterator end()const{return _ht.End();} pair<iterator,bool>insert(const pair<K, V>& kv){return _ht.Insert(kv);} iterator find(const K& key){return _ht.Find(key);}boolerase(const K& key){return _ht.Erase(key);} V&operator[](const K& key){ pair<iterator,bool> ret =insert({ key,V()});return ret.first->second;}private: hash_bucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _ht;};}

Read more

【C++】智能指针

【C++】智能指针

前言         上文我们学到了C++11的异常,了解到了C++与C语言处理错误的区别,异常的特点在于抛出与接收。【C++11】异常-ZEEKLOG博客         本文我们来学习C++中的下一个功能:智能指针 1.智能指针的使用场景         在上文我们知道了抛异常的知识,抛异常的“抛”这个动作一般来说是当程序出现了错误,抛出错误信息为了让我们解决。这个原本是解决错误的动作,在某些时候却称为了“铸就”错误的是罪魁祸首。         比如:我们知道执行throw,这意味着在这个局部域中throw后面的语句将不再执行,跳过一段又一段程序直到找到匹配的catch时,才会从catch这个语句进行向下执行。那么一个局部域中如果在抛出异常时申请了空间,明明可以正常销毁的,但是却因为抛异常跳过了销毁空间的语句。这就导致一个及其严重的事故:内存泄漏!         在此之前,为了防止出现内存泄漏。我们通常是将抛出的异常再次捕获,执行销毁语句后,将异常重新抛出。但是这种方法并不太好用,所以为了更好的解决这个问题:智能指针诞生了。 2.RAII和智能指针的设计

By Ne0inhk
【C++】红黑树详解(2w字详解)

【C++】红黑树详解(2w字详解)

手搓AVL树 * 手搓红黑树 * github地址 * 0. 前言 * 1. 什么是红黑树 * 概念与定义 * 红黑树示例 * 2. 红黑树的性质 * 红黑树的性质解读 * 树的路径再认识 * 3. 红黑树如何确保最长路径不超过最短路径的2倍? * 4. 红黑树的实现 * 整体架构设计 * 结点颜色的枚举类 * 红黑树的结点定义 * 红黑树设计 * 红黑树的插入实现 * 1. 空树的插入 * 2. 新插入节点的父亲为黑色 * 新结点的颜色 * 3. 新插入节点的父亲为红色 * (1)叔叔存在且为红色:变色 + 继续向上处理 * (2)叔叔不存在或叔叔为黑色:旋转 + 变色 * ①LL型:右单旋 + 变色 * ②RR型:左单旋 + 变色 * ③LR型:左右双旋 + 变色 * ①RL型:右左双旋 + 变色 * 4.

By Ne0inhk
【C++】带你手搓二叉搜索树(2w字详解)

【C++】带你手搓二叉搜索树(2w字详解)

二叉搜索树 * 二叉搜索树 * github地址 * 0. 前言 * 1. 二叉搜索树的定义 * 2. 整体架构设计 * 结点设计 * 树结构设计 * 3. 相关操作实现 * 1. 构造与析构 * 构造 * 析构 * 2. 拷贝构造与赋值 * 拷贝构造 * operator=赋值 * 3. 插入 * 迭代版插入 * 递归插入 * 4. 查找 * 迭代查找: * 递归查找: * 5. 中序遍历 * 6. 删除 * 迭代删除 * 情况一 * 情况二 * 情况三 * 完整代码与逐步说明 * 递归删除 * 4. 性能分析 * 5. 完整实现代码 * 6. 结语 二叉搜索树 github地址 有梦想的电信狗 0.

By Ne0inhk
C++ 拷贝构造函数与赋值运算符:深拷贝与浅拷贝的核心辨析

C++ 拷贝构造函数与赋值运算符:深拷贝与浅拷贝的核心辨析

C++ 拷贝构造函数与赋值运算符:深拷贝与浅拷贝的核心辨析 💡 学习目标:掌握拷贝构造函数与赋值运算符的定义及调用场景,理解深拷贝与浅拷贝的本质区别,能够在实际开发中避免内存泄漏与野指针问题。 💡 学习重点:拷贝构造函数的触发条件、浅拷贝的缺陷、深拷贝的实现方法、赋值运算符的重载原则。 一、拷贝构造函数的概念与触发场景 ✅ 结论:拷贝构造函数是一种特殊的构造函数,用于通过一个已存在的对象创建一个新对象,其参数必须是本类对象的常量引用(const 类名&)。 1.1 拷贝构造函数的语法格式 class 类名 {public:// 普通构造函数 类名(参数列表);// 拷贝构造函数 类名(const 类名& other);}; ⚠️ 注意事项: 1. 拷贝构造函数的参数必须是常量引用,使用 const 防止实参被修改,使用引用避免无限递归调用拷贝构造函数。 2. 如果没有手动定义拷贝构造函数,编译器会自动生成一个默认拷贝构造函数,实现简单的成员变量值拷贝。 1.2 拷贝构造函数的触发条件

By Ne0inhk