淺拷貝:
1.什麼是淺拷貝? 淺拷貝會出現什麼問題?
所謂淺拷貝,指的是在對象複製時,只是對對象中的數據成員進行簡單的複製,默認拷貝構造函數執行的也是淺拷貝。簡單的說,淺拷貝就是值傳遞,將源空間裏面的內容複製到目標空間中。
存在缺陷:多個指針可能共用管理一塊內存空間,在釋放時,導致對一塊空間的多次釋放,造成內存泄露。
深拷貝:
2. 什麼是深拷貝?
在“深拷貝”的情況下,對於對象中動態成員,就不能僅僅簡單地賦值了,而應該重新動態分配空間。
深拷貝與淺拷貝:
3.淺拷貝與深拷貝的不同之處:
用深動形象的語言來說,如果把拷貝看作下面這幅圖,那麼,淺拷貝只能拷走美人魚的頭,水下部分它將無法操作,而深拷貝 不僅可以拷走頭,還可以操作水下隱含的部分。
好了,說到這裏,咱們可能對深淺拷貝或多或少的有了一些認識,那麼,下面,我們將實現一個String 類,來更加具體權威的解釋深淺構造函數。
4.完成String類-普通版(淺拷貝)
#include<iostream>
using namespace std;
class String
{
public :
String(const char*pStr="")//構造函數
{
if(NULL==pStr)
{
_pStr=new char[1];
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
}
String(const String& s)//拷貝構造函數
:_pStr(s._pStr)
{
//s2已經釋放,但s1不知道,會對空間再次釋放
}
String& operator=(const String& s)//賦值運算符重載
{
if(this!=&s)
{
_pStr=s._pStr;//內存泄露
}
return *this;
}
~String ()//析構函數
{
if(_pStr)
{
delete[] _pStr;
_pStr=NULL;
}
}
private:
char* _pStr;
};
void FunTest()
{
String s1("hello");
//String s2(s1);
String s3;
s3=s1;
}
int main ()
{
FunTest();
/*String s1("hello");
String s2(s1);
String s3(NULL);
String s4(s1);
s3=s4;
s3=s1;*/
return 0;
}
該例證明了淺拷貝會存在多個對象共用同一塊空間,在調用析構函數銷燬空間時,會出現對一塊空間多次釋放的情況,導致內存崩潰
5. 完成String類深拷貝—簡潔版
#include<iostream>
using namespace std;
class String
{
public :
//構造函數************************************************
String(const char*pStr="")
{
if(NULL==pStr)
{
_pStr=new char[1];
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
}
//拷貝構造函數********************************************
String(const String& s)
:_pStr(NULL)//選擇最佳
{
//_pStr=new char[1];//第二選擇
String strTemp(s._pStr );
swap(_pStr,strTemp._pStr);
}
//賦值 運算符重載******************************************
//方式一:
String& operator=(const String& s)
{
if(this!=&s)
{
String strTemp(s._pStr);
//String strTemp(s) ;
swap(_pStr,strTemp._pStr) ;
}
return *this;
}
//方式二:
//String& operator=(const String& s)
// {
// String strTemp(s) ;
// swap(_pStr,strTemp._pStr) ;
//return *this;
//}
//方式三:
//String& operator=( String s)
//{
// swap(_pStr,s._pStr );
// return *this;
//}
//三種賦值運算符重載解決方案, 第一種方式爲最優方案
//析構函數*****************************************
~String ()
{
if(_pStr)
{
delete[] _pStr;
_pStr=NULL;
}
}
private:
char* _pStr;
int _count;
};
void FunTest()
{
String s1("Hello");
String s2(s1);
s2=s1;
}
int main ()
{
FunTest();
/*String s1("hello");
String s2(s1);
String s3(NULL);
String s4(s1);
s3=s4;
s3=s1;*/
return 0;
}
6.引用計數:
A.什麼是引用計數?
在開闢空間時,爲了記錄該空間有多少對象在共用它,也就是說有多少指針指向它,採用再開闢一個空間的方式,記錄該空間被指向的次數,這種方式被稱爲引用計數。
B.用引用計數實現String時,引用計數可以普通的成員變量?爲什麼?
解析:引用計數不可以爲普通的成員變量,因爲一旦出了作用域,該空間被銷燬,達不到想要的效果
C.用引用計數實現String時,引用計數可以類的靜態成員變量嗎? 爲什麼?
解析:類的靜態成員變量,但在需要另外開闢空間時,採用這種方式就
#include<iostream>
using namespace std;
class String
{
public :
//構造函數
String(const char* pStr="")
{
if(NULL==pStr)
{
_pStr=new char[1];
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
_count=1;
}
String (const String& s)
:_pStr(s._pStr)
{
_count++;
}
~String ()
{
if(_pStr&&(0==--*_count))
{
delete[] _pStr;
_pStr=NULL;
}
}
private:
char* _pStr;
static int _count;
};
int String:: _count=0;
void FunTest()
{
String s1("hello");
String s2(s1);
String s3;
}
int main()
{
FunTest();
return 0;
}
爲了在釋放的時候,防止忘記釋放引用計數所開闢的空間,所以儘量採用new [ ]
的方式來開闢空間,與delete[ ]搭配使用。
7.完成引用計數版本的String類—該引用計數也屬於淺拷貝
//************引用計數*******************************
#include<iostream>
using namespace std;
class String
{
public :
//構造函數
String(const char* pStr="")
:_pCount(new int (1))
{
if(NULL==pStr)
{
_pStr=new char[1];
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1];
strcpy(_pStr,pStr);
}
}
String (const String& s)
:_pStr(s._pStr)
,_pCount(s._pCount)
{
++(*_pCount);
}
String& operator=(const String& s)
{
if(_pStr!=s._pStr)//被賦值的對象與當前對象不是同一塊空間
{
if(_pStr&&0==--*_pCount)
{
delete [] _pStr;
delete _pCount;
}
_pStr =s._pStr;
_pCount=s._pCount;
++_pCount;
}
return *this;
}
~String ()
{
if(_pStr&&(0==--*_pCount))
{
delete[] _pStr;
_pStr=NULL;
delete _pCount;
_pCount=NULL;
}
}
private:
char* _pStr;
int* _pCount;
};
void FunTest()
{
String s1("hello");
String s2(s1);
String s3;
}
int main()
{
FunTest();
return 0;
}
7. 完成COW(寫時拷貝版的String)
(COW不是奶牛)
//****寫時拷貝:如果要朝當前對象寫東西,最好使用這種方式*********
//單線程不會有問題
#include<iostream>
using namespace std;
class String
{
public :
//構造函數
String(const char* pStr="")
{
if(NULL==pStr)
{
_pStr=new char[1+4];
_pStr+=4;
*_pStr='\0';
}
else
{
_pStr=new char[strlen(pStr)+1+4];
_pStr+=4;
strcpy(_pStr,pStr);
}
GetRaf()=1;
}
String (const String& s)
:_pStr(s._pStr)
{
GetRaf()++;
}
String& operator=(const String& s)
{
if(_pStr!=s._pStr)//被賦值的對象與當前對象不是同一塊空間
{
Release();
_pStr =s._pStr;
++GetRaf();
}
return *this;
}
~String ()
{
Release();
}
char& operator [] (size_t index)
{
if(GetRaf()>1)//如果當前空間不止存放的一個對象
{
char* pTemp=new char [strlen(_pStr)+1+4];
*(int*)pTemp=1;
pTemp+=4;
strcpy(pTemp,_pStr);
--GetRaf();//一定是在改變指針指向之前
_pStr=pTemp;
}
return _pStr[index];
}
const char& operator [](size_t index)const
{
return _pStr[index];
}
private:
int& GetRaf()
{
return *((int*)_pStr-1);
}
void Release()
{
if(_pStr&&(0==--GetRaf()))
{
_pStr-=4;
delete[] _pStr;
_pStr=NULL;
}
}
private:
char* _pStr;
};
void FunTest()
{
String s1("hello");
String s2(s1);
s2[0]='w';
}
int main()
{
FunTest();
return 0;
}
下面,在這裏提出兩點關於string類來說非常重要點:
一.熟悉庫中string類的每個接口,查文檔,熟悉庫中的string類。
二.調研vs和linux系統下string類的結構,他們是否採用用深拷貝原理實現。