12.1動態內存和類
12.1.1複習實例和靜態類成員
12.1.2特殊成員函數
首先看這樣一個例子
#include <iostream>
#include <cstring>
using namespace std;
class string1
{
private:
char *str;
int len;
public:
string1(const char*);
string1();
~string1();
void display()
{
cout<<str<<endl;
}
};
string1::string1()
{
len=0;
str=new char [1];//這裏是爲了,和析構函數裏的 delete [] 匹配
str[0]='\0';
}
string1::string1(const char*s)
{
len=strlen(s);
str = new char[len+1];
strcpy(str,s);
}
string1::~string1()
{
delete [] str;
cout<<"析構函數被調用"<<endl;
}
string1 fun()
{
string1 a("111");
return a;
}
int main()
{
string1 a=fun();
a.display();
return 0;
}
這個例子是錯誤的,對於類中不帶指針類型成員變量的類來說可以使用這種函數返回賦值的方法,但是上面這個例子就不行了,因爲在fun函數裏,定義了一個局部對象,在離開函數時候,這個局部對象會被析構,所以函數將創建一個臨時無名對象,這個臨時無名對象會調用一個叫複製構造函數的構造函數,逐個賦值局部對象的非靜態成員,它複製的是成員的值。
如果沒有定義複製構造函數,程序會調用自己生成的默認複製構造函數
12.1.3使用程序提供的默認複製構造函數存在的問題
存在什麼問題呢
這個臨時無名對象裏的東西跟局部對象一模一樣,就連指針也一樣,它們指向了同一部分內存,但是函數結束後,析構函數會釋放這部分內存,這時候在主函數裏訪問這部分內存就會出現問題。
如何解決這個問題
很明顯我們可以把局部對象裏指針指向的那部分內存,賦給另一部分內存,並讓臨時無名對象的指針指向它,這樣函數結束程序調用析構函數時,臨時無名對象不會被修改
如何實現這個操作呢
複製構造函數,顧名思義,複製時候使用的構造函數,他只有一個參數,即對象的引用。
string a("123");
string b;
b=a;//將調用複製構造函數
如果沒有定義複製構造函數,系統將自動生成一個默認的複製構造函數,它的作用是逐個複製非靜態成員,複製的是成員的值,這種拷貝稱爲淺層拷貝,在本例中它長這個樣子:
string1::string1(string1 &p)
{
str=p.str;
}
可以看出它只是將指針指向的地址給複製了,所以就出現了上面這個問題。
要解決上面這個問題,我們需要手動定義一個複製構造函數
string1::string1(const string1 &p)
{
len=p.len;
str=new char[len+1];
strcpy(str,p.str);
cout<<"複製構造函數被調用"<<endl;
}
輸出結果是
這種拷貝稱爲深層拷貝
何時調用複製構造函數
1.新建一個對象並將其初始化爲同類現有對象時。
如果是賦值則不會調用
2.函數按值傳遞和返回對象時
3.作爲當對象作爲函數參數時
12.1.4賦值運算符
上述調用複製構造函數的時候不包括非初始化賦值的時候,也就是說當我們使用下述代碼時,還是會發生淺層拷貝。
string1 a("123");
string1 b;
b = a;
可以通過重載=來實現深層拷貝
string1& string1::operator=(const string1 &p)
{
if(this==&p)如果=兩邊對象地址相同,說明程序正進行自己給自己賦值這種操作,直接返回對象本身即可
{
return *this;
}
delete[] str;//要釋放對象內指針本來指向的內存
len=p.len;
str = new char[len+1];
strcpy(str,p.str);
cout<<"=重載成功"<<endl;
return *this;
}
返回一個對象的引用的作用是,幫助我們實現a=b=c這種操作。
12.2實現string類的其他功能
這裏直接給出代碼
#include <iostream>
#include <cstring>
using namespace std;
class string1
{
private:
char *str;
int len;
static const int MAX=100;//最大100個字符
public:
//構造函數
string1();//默認構造函數
string1(const char*);//構造函數
string1(const string1 &p);//複製構造函數
//重載運算符
string1& operator =(const string1 &p);//重載=
string1& operator =(const char *p);
char& operator[](int i);
//重載比較運算符
friend bool operator <(const string1 &a,const string1 &b);
friend bool operator >(const string1 &a,const string1 &b);
friend bool operator ==(const string1 &a,const string1 &b);
friend ostream& operator <<(ostream &os,const string1 &a);
friend istream& operator >>(istream &is,string1 &a);
//析構函數
~string1();//析構函數
//其他功能函數
int length();
void display();
};
//構造函數
string1::string1()
{
len=0;
str=new char[1];
str='\0';
}
string1::string1(const char*s)
{
len=strlen(s);
str = new char[len+1];
strcpy(str,s);
}
string1::string1(const string1 &p)
{
len=p.len;
str=new char[len+1];
strcpy(str,p.str);
cout<<"複製構造函數被調用"<<endl;
}
string1& string1::operator=(const string1 &p)
{
if(this==&p)
{
return *this;
}
delete[] str;
len=p.len;
str = new char[len+1];
strcpy(str,p.str);
cout<<"=重載成功"<<endl;
return *this;
}
string1& string1::operator=(const char *p)
{
delete []str;
len=strlen(p);
str=new char[len+1];
strcpy(str,p);
return *this;
}
char & string1::operator[](int i)
{
return str[i];
}
bool operator<(const string1 &a,const string1 &b)
{
return strcmp(a.str,b.str);
}
bool operator>(const string1 &a,const string1 &b)
{
return b<a;//直接使用重載後的小於號
}
bool operator==(const string1 &a,const string1 &b)
{
if(strcmp(a.str,b.str)==0)
return true;
return false;
}
ostream& operator <<(ostream &os,const string1 &a)
{
os<<a.str<<endl;
return os;
}
istream& operator >>(istream &is,string1 &a)
{
char temp[string1::MAX];
is.get(temp,string1::MAX);
if(is)
{
a=temp;
}
while(is&&is.get()!='\n')
{
continue;
}
return is;
}
int string1::length()
{
return len;
}
void string1::display()
{
cout<<str<<endl;
}
//析構函數
string1::~string1()
{
delete [] str;
cout<<"析構函數被調用"<<endl;
}
int main()
{
string1 b;
cin>>b;
cout<<b;
return 0;
}
12.3在構造函數中使用new時應注意的事項
1.如果在構造函數中使用new來初始化指針成員,則應在析構函數中使用delete
2.new 和delete必須相互兼容,new對應於delete,而new[]對應於delete[]
3.如果有多個構造函數,則應該以相同的方式使用new,要麼都帶括號,要麼都不帶,因爲只有一個析構函數與它兼容
4.應定義一個複製構造函數,通過深度複製將一個對象初始化爲另一個對象
5.應定義一個賦值運算符,通過深度複製將一個對象複製給另一個對象
12.4有關返回對象的說明
使用const引用是爲了提高效率,但對於何時採用還有一些限制。如果函數返回傳遞給它的對象,可以通過返回引用來提高效率
,因爲返回對象會調用複製構造函數,而返回引用不會。
不能返回一個局部對象的引用,因爲函數結束後,局部對象的內存會被釋放,所以引用將引用一個不確定的內存。
12.5定位new運算符
用將定位new運算符來創建新的類對象後,當該對象消亡是時,程序並不會自動地調用其析構函數,所以必須顯示地調用析構函數。這個少數的需要顯示調用析構函數的情況之一。
需要注意的是,對於使用定位new運算符創建的對象,應以與創建順序相反的順序進行刪除。原因在於,晚創建的對象可能依賴於早創建的對象。另外,僅當所有對象都被銷燬後,才能釋放用於儲存這些對象的緩衝區。