浅谈深浅拷贝问题(这里只针对拷贝构造函数和赋值运算符重载)和简易srting类模拟实现
淺談深淺拷貝問題(這里只針對(duì)拷貝構(gòu)造函數(shù)和賦值運(yùn)算符重載)和簡(jiǎn)易srting類模擬實(shí)現(xiàn)
文章目錄
- 淺談深淺拷貝問題(這里只針對(duì)拷貝構(gòu)造函數(shù)和賦值運(yùn)算符重載)和簡(jiǎn)易srting類模擬實(shí)現(xiàn)
- 一、什么是淺拷貝
- 二、再談淺拷貝
- 三、淺拷貝的解決方法(只針對(duì)拷貝構(gòu)造函數(shù)和賦值運(yùn)算符重載)
- 1.**深拷貝**:
- 2.**`淺拷貝+引用計(jì)數(shù)`**:
- 四、簡(jiǎn)易模擬實(shí)現(xiàn)string類
一、什么是淺拷貝
淺拷貝也叫值拷貝、位拷貝,是編譯器將被拷貝的對(duì)象原封不動(dòng)的拷貝過來(注意是原封不動(dòng)的拷貝),也就是編譯器只把被拷貝的對(duì)象的值拷貝過來而已; 注意編譯器提供默認(rèn)淺拷貝的拷貝構(gòu)造函數(shù)不一定會(huì)造成操作違規(guī),但如果對(duì)象中涉及資源管理問題,就會(huì)導(dǎo)致其中一個(gè)對(duì)象使用完這份資源后,把這份資源給釋放了,然而其他共享這些資源的對(duì)象以為該資源還存在,當(dāng)對(duì)該資源進(jìn)行訪問操作時(shí),就會(huì)導(dǎo)致訪問操作違規(guī)。
二、再談淺拷貝
假如模擬實(shí)現(xiàn)string類時(shí),我們沒有顯式提供拷貝構(gòu)造函數(shù)和賦值運(yùn)算符重載,編譯器就會(huì)默認(rèn)生成一個(gè)拷貝構(gòu)造函數(shù)和一個(gè)賦值運(yùn)算符重載,但編譯器默認(rèn)生成的都是淺拷貝函數(shù);如:
在上圖中:
三、淺拷貝的解決方法(只針對(duì)拷貝構(gòu)造函數(shù)和賦值運(yùn)算符重載)
1.深拷貝:
從上面可以知道產(chǎn)生淺拷貝的原因主要有一份資源被多次釋放和想銷毀一個(gè)野指針指向的空間,那么深拷貝就從根源上解決問題:
此時(shí)對(duì)于拷貝構(gòu)造函數(shù):
對(duì)于賦值運(yùn)算重載:
string& operator=(const string& s) {if(this!=&s){char*_pstr=new char[strlen(s._str)+1];//防止第二個(gè)字符串太長(zhǎng),所以另外申請(qǐng)空間strcpy(_pstr,s._str);delete[] _str;_str=_pstr;}return *this; }通過上面的代碼,就解決了引起淺拷貝的兩個(gè)問題,主要解決思想是另外申請(qǐng)一個(gè)空間,把內(nèi)容拷貝過去,這樣就不會(huì)存在資源共享問題,也就不存在淺拷貝,但上面代碼利用率低,復(fù)用性不高。
當(dāng)然深拷貝也可以這樣解決,代碼如下:
對(duì)于拷貝構(gòu)造函數(shù):
string(const string& s) :_str(nullptr) {string strTemp(s._str);swap(_str,strTemp._str); }對(duì)于賦值運(yùn)算符重載:
2.淺拷貝+引用計(jì)數(shù):
(1).通過這種方法解決主要思想是:
a.怎么解決多次釋放一份資源問題:此時(shí)是和教室里最后一個(gè)人走關(guān)燈一樣,如果我們知道當(dāng)前共有多少個(gè)對(duì)象共享一份資源,當(dāng)一個(gè)對(duì)象準(zhǔn)備釋放時(shí),如果發(fā)現(xiàn)還有對(duì)象使用這份資源,就不釋放,此時(shí)就解決一份資源多次釋放問題
b.怎么知道當(dāng)前共有多少個(gè)對(duì)象共享這份資源:引用計(jì)數(shù)
(2)拷貝構(gòu)造函數(shù)的解決方法:
(注意之所以把錯(cuò)解寫出來,是因?yàn)楹竺婷恳徊蕉际窃阱e(cuò)解的基礎(chǔ)上進(jìn)行改正的,方便理解)
錯(cuò)解1:在成員中定義一個(gè)int型變量,用來記錄共享這份資源的對(duì)象的個(gè)數(shù)
#if 0 namespace wolf {class string{public:string(const char* str = "")//構(gòu)造函數(shù):_str(new char[strlen(str) + 1]){if (str == nullptr){str = "";}_count = 1;//因?yàn)橐坏┩ㄟ^構(gòu)造函數(shù)來構(gòu)造一個(gè)對(duì)象,說明這個(gè)對(duì)象的//資源只有它自己用,還沒來得及共享,就把_count置為1strcpy(_str, str);}string(string& s)//拷貝構(gòu)造函數(shù):_str(s._str)//在拷貝構(gòu)造函數(shù)中采用淺拷貝的方式,_count(++s._count)//string s1("wolf");string s2(s1);此時(shí)對(duì)//象s2通過拷貝構(gòu)造函數(shù)共享s1這個(gè)對(duì)象的資源時(shí),//就把s1._count加1,表明現(xiàn)在除了s1自己外還有別人共享這份資源,然后把//s1._count賦值給需要共享的對(duì)象s2,//此時(shí)s2._count也記錄了當(dāng)前共享一份資源的對(duì)象的個(gè)數(shù);{}string& operator=(const string& s)//賦值運(yùn)算符重載{if (this != &s)//防止自己給自己賦值{//.....}return *this;}~string()//析構(gòu)函數(shù){if (_str&&(--_count==0))//_count-1==0的意思是除去自己這個(gè)對(duì)象使//用這份資源外,如果為0,表明除了自己//沒有其他對(duì)象共享這份資源,此時(shí)釋放就不會(huì)引起一份資源被多次釋放的//問題,反之_count-1!=0就表示//還有對(duì)象使用這份資源,此時(shí)就不能釋放。{delete[] _str;_str = nullptr;}}private:char* _str;int _count;}; } #endifstring s1(“wolf”);string s2(s1);通過調(diào)試發(fā)現(xiàn)這種放法是錯(cuò)誤的,因?yàn)楫?dāng)s2釋放后,s2._count確實(shí)減少了,但是當(dāng)我們?nèi)メ尫舠1時(shí),s1的s2._count仍為2,這樣s2就沒有釋放,最終導(dǎo)致資源泄露
錯(cuò)解2:因?yàn)榈谝环N方法放法主要是_count不同步造成的,所以采用static修飾_count的方法
#if 0 namespace wolf {class string{public:string(const char* str = "")//構(gòu)造函數(shù):_str(new char[strlen(str) + 1]){if (str == nullptr){str = "";}_count = 1;//因?yàn)橐坏┩ㄟ^構(gòu)造函數(shù)來構(gòu)造一個(gè)對(duì)象,說明這個(gè)對(duì)象的資源只//有它自己用,還沒來得及共享,所以就把_count置為1strcpy(_str, str);}string(string& s)//拷貝構(gòu)造函數(shù):_str(s._str)//在拷貝構(gòu)造函數(shù)中采用淺拷貝的方式{_count++;//}string& operator=(const string& s)//賦值運(yùn)算符重載{if (this != &s)//防止自己給自己賦值{//.......}return *this;}~string()//析構(gòu)函數(shù){if (_str && (--_count == 0))//_count-1==0的意思是除去自己這個(gè)對(duì)//象使用這份資源外,如果為0,表明除了自己//沒有其他對(duì)象共享這份資源,此時(shí)釋放就不會(huì)引起一份資源被多次釋放的//問題,反之_count-1!=0就表示//還有對(duì)象使用這份資源,此時(shí)就不能釋放。{delete[] _str;_str = nullptr;}}private:char* _str;static int _count;};int string::_count = 0; } #endif正解:如果每一份資源都有自己的一個(gè)計(jì)數(shù),這樣計(jì)數(shù)就和資源個(gè)數(shù)保持一致,所以采用共享計(jì)數(shù)的方法
namespace wolf {class string{public:string(const char* str = "")//構(gòu)造函數(shù):_str(new char[strlen(str) + 1]),_pcount(new int (1))//在堆上申請(qǐng)一個(gè)空間并初始化為1;{if (str == nullptr){str = "";}strcpy(_str, str);}string(string& s)//拷貝構(gòu)造函數(shù):_str(s._str)//在拷貝構(gòu)造函數(shù)中采用淺拷貝的方式,_pcount(s._pcount)//共享同一份資源的計(jì)數(shù){++(*_pcount);}string& operator=(const string& s)//賦值運(yùn)算符重載{if (this != &s)//防止自己給自己賦值{//.........}return *this;}~string()//析構(gòu)函數(shù){if (_str && (--(*_pcount) == 0))//_pcount-1==0的意思是除去自己//這個(gè)對(duì)象使用這份資源外,如果為0,表明除了自己//沒有其他對(duì)象共享這份資源,此時(shí)釋放就不會(huì)引起一份資源被多次釋放的//問題,反之_pcount-1!=0就表示//還有對(duì)象使用這份資源,此時(shí)就不能釋放。{delete[] _str;_str = nullptr;delete _pcount;_pcount = nullptr;}}private:char* _str;int* _pcount;}; } #endif(3).賦值運(yùn)算符重載的解決方法:
經(jīng)過上面的鋪墊就很容易的到賦值運(yùn)算符重載的解決方法:
以上解決淺拷貝的方法也有其問題:如果修改對(duì)象s3中的資源時(shí)s3[0] = ‘W’;,就發(fā)現(xiàn)其他和其共享資源的對(duì)象s1,s2也發(fā)生了改變,相當(dāng)于一改全改接下來就是想怎么解決這一問題。
(4).解決淺拷貝+引用計(jì)數(shù)的缺陷:寫時(shí)拷貝copy on write(COW)(這里只針對(duì)部分)
main函數(shù):
void TestMystring() {wolf::string s1("wolf guidao");wolf::string s2(s1);wolf::string s3;s3 = s2;s3[0] = 'W'; }int main() {TestMystring();system("pause");return 0; }以上代碼中每一步都有詳細(xì)解釋,如果還不明白,下面鏈接中有介紹淺拷貝+引用計(jì)數(shù)的使用方法和這種方法的缺點(diǎn);
淺拷貝+引用計(jì)數(shù)解決淺拷貝
淺拷貝+引用計(jì)數(shù)解決淺拷貝問題的缺陷
四、簡(jiǎn)易模擬實(shí)現(xiàn)string類
#define _CRT_SECURE_NO_WARNINGS #include<iostream> #include<Windows.h> #include<stdlib.h> #include<string.h> using namespace std;namespace wolf {class string{public:typedef char* iterator;public:string(const char* str = "")//構(gòu)造函數(shù){if (str == nullptr)//如果傳進(jìn)來為空指針,就讓他改為"",這樣就可以放止程序崩潰{str = "";}_size = strlen(str);_capacity = _size;_str = new char[_capacity+1];//申請(qǐng)空間strcpy(_str, str);//把str中的內(nèi)容拷貝過去}string(const string& s)//拷貝構(gòu)造函數(shù);采用深拷貝:_size(s._size),_capacity(s._size+1){_str = new char[_capacity];strcpy(_str, s._str);}string& operator=(string& s)//賦值運(yùn)算符重載{if (this != &s){string temp(s._str);//調(diào)用構(gòu)造函數(shù)把s的字符串拷貝一份,防止淺拷貝問題swap(_str, temp._str);}return *this;}~string()//析構(gòu)函數(shù){if (_str)//不為空才進(jìn)行釋放,否則會(huì)多次釋放{_size = 0;_capacity = 0;delete[] _str;_str = nullptr;}}size_t Size()const//返回字符串個(gè)數(shù){return _size;}size_t Capacity()const//返回空間容量daxiao{return _capacity;}bool Empty()const//判空{return _size == 0;}iterator begin()//迭代器(注意迭代器的范圍都是[begin,end)){return _str;//返回字符串首地址}iterator end(){return _str + _size;//返回字符串最后一個(gè)位置,即\0出}void Reserve(size_t newcapacity)//調(diào)整空間大小{if (newcapacity > _capacity)//如果新空間大小大于舊空間大小才進(jìn)行{char* strtemp = new char[newcapacity + 1];//多一個(gè)是為了放'\0'strcpy(strtemp, _str);delete[]_str;_str = strtemp;_capacity = newcapacity;//新空間大小不是newcapacity+1是為了和類庫中string保持一致}}void Resize(size_t newsize,char ch)//把空間中有效字符個(gè)數(shù)調(diào)整為newsize的,多余的空間用ch填充{size_t oldsize = Size();if (newsize < oldsize)//如果調(diào)整后有效字符個(gè)數(shù)小于原有字符個(gè)數(shù){_size = newsize;_str[_size] = '\0';}else{if (newsize > _capacity)//如果調(diào)整后字符個(gè)數(shù)大于空間容量,就進(jìn)行擴(kuò)容{Reserve(newsize);}memset(_str+_size, ch, newsize - oldsize);//從原有字符后面進(jìn)行填充ch}_size = newsize;_str[_size] = '\0';//記得加上\0}void Push_back(char ch)//在原來字符串末尾追加一個(gè)字符ch{if (_size == _capacity)//判滿{Reserve(_capacity * 2);}_str[_size++] = ch;_str[_size] = '\0';}string& operator+=(char ch)//運(yùn)算符重載{if (_size == _capacity){Reserve(_capacity * 2);}Push_back(ch);_size = _size + 1;return *this;}void Append(const char* str);//追加字符串string& operator+=(const char* str);//運(yùn)算符重載void Clear()//把空間中有效字符個(gè)數(shù)清空{_size = 0;_str[_size] = '\0';}void Swap(string& s)//把兩個(gè)string對(duì)象中的內(nèi)容交換,注意要全部交換{swap(_str, s._str);swap(_size, s._size);swap(_capacity, s._capacity);}const char* C_str()const//返回當(dāng)前對(duì)象的C格式字符串{return _str;}char& operator[](size_t index)//運(yùn)算符重載{if (index < _size){return _str[index];}}bool operator>(const string& s)const;bool operator>=(const string& s)const;bool operator<(const string& s)const;bool operator<=(const string& s)const;bool operator==(const string& s)const;bool operator!=(const string& s)const;int Find(char ch, size_t pos = 0)const//從pos開始,從前往后找字符,返回ch的下標(biāo){for (size_t i = pos;i < _size;i++){if (_str[i] == ch){return i;}}return -1;}int rFind(char ch, size_t pos = -1)const//從pos開始,從后往前找字符,返回ch的下標(biāo){if (pos == npos){pos = _size - 1;}for (size_t i = pos;i >= 0;i--){if (_str[i] == ch){return i;}}return npos;}int Find(char* str, size_t pos = 0)const//從pos開始,從前往后找字符串,返回str的下標(biāo){}int rFind(char* str, size_t pos = 0)const//從pos開始,從后往前找字符串,返回str的下標(biāo){}string& Erase(size_t pos,size_t num)//從pos開始刪除num個(gè)字符{}string& Insert(size_t pos,char ch)//在pos處插字符ch{}string& Insert(size_t pos, char* str)//在pos處插字符串str{}friend ostream& operator<<(ostream& _cout, const string& s){_cout << s.C_str();return _cout;}friend istream& operator>>(istream& _cin, const string& s){_cin >> s._str;return _cin;}private:char* _str;//存放字符串size_t _size;//字符串個(gè)數(shù)size_t _capacity;//字符串容量static size_t npos;}; }size_t wolf::string::npos = -1;void Teststeing() {wolf::string a("wolf");//cout << a.Size() << endl;//cout << a.Capacity() << endl;wolf::string b(a);//cout << b.Size() << endl;//cout << a.Capacity() << endl;b.Resize(10, '!');//cout << b << endl;//cout << b.Size() << endl;//cout << b.Capacity() << endl;b.Reserve(30);//cout << b.Capacity() << endl;b.Push_back(' ');b.Push_back('i');//cout << b << endl;//b.Clear();//cout << b << endl;//cout << b.Find('o') << endl;//cout << b.rFind('o') << endl;//cout << b.C_str() << endl;wolf::string::iterator it = b.begin();/*while (it < b.end()){cout << *it;it++;}*///cout << endl;/*for (auto e: b){cout << e;}*///cout << endl;wolf::string c;cin >> c;cout << c; }int main() {Teststeing();system("pause");return 0; }總結(jié)
以上是生活随笔為你收集整理的浅谈深浅拷贝问题(这里只针对拷贝构造函数和赋值运算符重载)和简易srting类模拟实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 顺序表、链表、双向循环链表
- 下一篇: Linux中基础指令