拷貝構造
創建對象時使用同類對象來進行初始化,這時所用的構造函數稱爲拷貝構造函數(Copy Constructor),拷貝構造函數是特殊的構造函數
就是使用一個已經存在的對象去創建一個新的對象。新的對象是舊的對象的一份拷貝。
- 拷貝構造函數其實是一個構造函數的重載。
- 拷貝構造函數的參數必須使用引用傳參,使用傳值方式會引發無窮遞歸調用。
- 若未顯示定義,系統會默認缺省的拷貝構造函數。默認生成(淺拷貝:一個字節一個字節拷貝)依次拷貝類成員進行初始化。
//使用默認的拷貝構造(淺拷貝)
#include<iostream>
using namespace std;
class Data
{
public:
//帶參的缺省的構造函數
Data(int year=1900,int month=1,int day=1)
{
_year=year;
_month=month;
_day=day;
}
void Display()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
~Data()//析構函數
{
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1(2018,1,1);//調用構造函數
Data d2(d1);//調用拷貝構造函數
//Data d2=d1 和上面是等價的
d1.Display();
d2.Display();
return 0;
}
[zyc@localhost lession_class]$ ./a.out
2018-1-1
2018-1-1
結果分析:
利用d1.我們拷貝構造了一個對象d2。
默認拷貝構造的缺陷
來看下面例子
#include<iostream>
#include <stdlib.h>
using namespace std;
class Array
{
public:
//構造函數
Array()
{
cout<<"Array()"<<endl;
_ptr=(int *)malloc(2*sizeof(int));
}
//這裏的析構函數需要完成清理工作(釋放空間)
~Array()
{
cout<<"~Array()"<<endl;
free(_ptr);
_ptr=NULL;
}
private:
int *_ptr;
};
int main()
{
Array ptr;
return 0;
}
編譯通過,調用一次構造函數,一次析構函數
[zyc@localhost lession_class]$ ./a.out
Array()
~Array()
類在我們沒有定義拷貝構造函數的時候,會默認定義默認拷貝構造函數,也就是說可以直接用同類型的類間可以相互賦值、初始化:
int main()
{
Array p1;
Array p2=p1;
return 0;
}
這個時候,編譯器機會報錯,原因就是因爲同一塊空間被釋放了兩次
類的默認拷貝構造函數只會用被拷貝類的成員的 值 爲拷貝類簡單初始化,也就是說二者的p指針指向的內存空間是一致的。以前面Array可以知道,編譯器爲我們默認定義的拷貝構造函數爲:
Array(const Array& Array)
{
p1 = Array.p1;
pp2 = Array.p1; //兩個類的p指針指向的地址一致。
}
main函數將要退出時,拷貝類p2的析構函數先得到執行,它把自身p指向的堆空間釋放了;接下來,p1的析構函數得到調用,被拷貝類p1的析構函數得到調用,它同樣要去析構自身的p指向指向的堆空間,但是該空間和p2類中p指向的空間一樣,造成重複釋放,程序運行崩潰。
爲了解決這種問題,我們可以使用自定義拷貝構造函數,裏面用深度拷貝的方式爲拷貝類初始化
深拷貝原理
關於深拷貝後面再介紹。
自定義拷貝構造函數,也可以定義成簡單的值拷貝,即淺拷貝。
class Data
{
public:
//帶參的缺省的構造函數
Data(int year=1900,int month=1,int day=1)
{
_year=year;
_month=month;
_day=day;
}
Data(const Data& d)//拷貝構造函數,這裏必須使用傳引用
{
this->_year=d._year;
this->_month=d._month;
this->_day=d._day;
}
void Display()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
~Data()//析構函數
{
}
private:
int _year;
int _month;
int _day;
};
拷貝構造函數 需要注意的問題
拷貝構造函數的參數必須使用引用傳參,使用傳值方式會引發無窮遞歸調用。
傳引用,否則無限遞歸
舉例:假設是傳參時使用傳值方式。
class Data
{
public:
//構造函數
Data()
{
cout<<"Data()"<<endl;
}
//拷貝構造函數,以傳值方式
Data(const Data d )
{
cout<<"Data(const Data &d)"<<endl;
}
~Data()
{
cout<<"~Data()"<<endl;
}
private:
int a;
};
int main()
{
Data d1;
Data d2=d1;
return 0;
}
運行結果:會提示報錯信息。編不過
[zyc@localhost lession_class]$ g++ tmp.cpp -g
tmp.cpp:21: error: invalid constructor; you probably meant ‘Data (const Data&)’
原因:
將傳值應用更改成傳引用。
Data(const Data &d )
運行結果
[zyc@localhost lession_class]$ ./a.out
Data()
Data(const Data &d)
~Data()
~Data()