?目錄
1、類的六個(gè)默認(rèn)成員函數(shù)
2、構(gòu)造函數(shù)
2.1 構(gòu)造函數(shù)的概念
2.2 特性
2.2.1 構(gòu)造函數(shù)的重載:
2.2.2 全缺省的構(gòu)造函數(shù):
3、析構(gòu)函數(shù)
3.1 析構(gòu)函數(shù)的概念
3.2 特性
4、拷貝構(gòu)造函數(shù)
4.1 拷貝構(gòu)造函數(shù)的概念
4.2 特征
1、類的六個(gè)默認(rèn)成員函數(shù)
如果一個(gè)類中什么成員都沒(méi)有,簡(jiǎn)稱為空類??疹愔姓娴氖裁炊紱](méi)有嗎?并不是,任何類在什么都不寫時(shí),編譯器會(huì)自動(dòng)生成以下6個(gè)默認(rèn)成員函數(shù)。
2、構(gòu)造函數(shù)
2.1 構(gòu)造函數(shù)的概念
我們這里來(lái)看看日期類的初始化:
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2022, 7, 5);
d1.Print();
return 0;
}
運(yùn)行結(jié)果:
我們剛接觸C++,一定會(huì)這樣初始化。
如果我們實(shí)例化的對(duì)象太多了,忘記初始化對(duì)象了,程序運(yùn)行出來(lái)的結(jié)果可能就是隨機(jī)值了,也可能出問(wèn)題。
這里C++祖師爺想到了,為我們?cè)O(shè)計(jì)了構(gòu)造函數(shù)。
我們先來(lái)看一下忘記初始化直接打印的結(jié)果:
這里是隨機(jī)值,那這是為什么呢?我們接著往下看。
構(gòu)造函數(shù)是一個(gè)特殊的成員函數(shù),名字與類名相同,創(chuàng)建類類型對(duì)象時(shí)由編譯器自動(dòng)調(diào)用,以保證每個(gè)數(shù)據(jù)成員都有 一個(gè)合適的初始值,并且在對(duì)象整個(gè)生命周期內(nèi)只調(diào)用一次。
2.2 特性
構(gòu)造函數(shù)是特殊的成員函數(shù),需要注意的是,構(gòu)造函數(shù)雖然名稱叫構(gòu)造,但是構(gòu)造函數(shù)的主要任務(wù)并不是開(kāi)空間創(chuàng)建對(duì)象,而是初始化對(duì)象。
其特征如下:
1. 函數(shù)名與類名相同。
2. 無(wú)返回值(不是void,是不用寫)。
3. 對(duì)象實(shí)例化時(shí)編譯器自動(dòng)調(diào)用對(duì)應(yīng)的構(gòu)造函數(shù)。
4. 構(gòu)造函數(shù)可以重載。
我們先寫一個(gè)日期類的構(gòu)造函數(shù)來(lái)看看:
class Date
{
public:
Date()//構(gòu)造函數(shù),無(wú)參構(gòu)造
{
cout << "Date()" << endl;
_year = 1;
_month = 1;
_day = 1;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day;
}
private:
int _year;
int _month;
int _day;
};
我們測(cè)試看一下:
我們main函數(shù)里沒(méi)有調(diào)用構(gòu)造函數(shù),但是這里打印了我們做的標(biāo)記,這里我們實(shí)驗(yàn)出來(lái)了實(shí)例化對(duì)象時(shí)構(gòu)造函數(shù)是自動(dòng)調(diào)用的。
我們?cè)賮?lái)看將我們寫的構(gòu)造函數(shù)注釋掉會(huì)發(fā)生什么:
我們能看到,注釋掉后,仍然能打印出來(lái),只不過(guò)是隨機(jī)值。因?yàn)?strong>當(dāng)我們不寫,編譯器會(huì)自動(dòng)生成默認(rèn)的構(gòu)造函數(shù),并自動(dòng)調(diào)用。
C++將類型分為內(nèi)置類型(基本類型):如int,char,double,int*……(自定義類型*也是);
自定義類型:如class,struct,union……。
并且這里我們能看出來(lái),對(duì)于內(nèi)置類型的成員不會(huì)處理,在C++11,支持成員變量給缺省值,算是補(bǔ)漏洞了。
2.2.1 構(gòu)造函數(shù)的重載:
class Date
{
public:
Date()
{
cout << "Date()" << endl;
_year = 1;
_month = 1;
_day = 1;
}
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
Date d2(2023, 8, 1);//這里初始化必須是這樣寫,這是語(yǔ)法
d2.Print();
return 0;
}
運(yùn)行結(jié)果:
注意:我們?cè)趯?shí)例化對(duì)象的時(shí)候,當(dāng)調(diào)用的構(gòu)造函數(shù)沒(méi)有參數(shù),不能在對(duì)象后加括號(hào),語(yǔ)法規(guī)定。
如果這樣寫,編譯器分不清這到底是函數(shù)聲明還是在調(diào)用。d2不會(huì)混淆是因?yàn)橛袀髦?,函?shù)聲明不會(huì)出現(xiàn)那樣的寫法。
2.2.2 全缺省的構(gòu)造函數(shù):
我們其實(shí)可以將上面的兩個(gè)構(gòu)造函數(shù)合并為一個(gè)
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
Date d2(2023, 8, 1);
d2.Print();
Date d3(2023, 9);
d3.Print();
return 0;
}
運(yùn)行結(jié)果:
全缺省構(gòu)造函數(shù)才是最適用的。無(wú)參構(gòu)造與全缺省可以同時(shí)存在,但是不建議這樣寫,雖然不報(bào)錯(cuò),但是在調(diào)用全缺省時(shí)我們不想傳參,編譯器不知道我們到底想調(diào)用哪個(gè)構(gòu)造,會(huì)產(chǎn)生二義性。
我們?cè)倏从脙蓚€(gè)棧實(shí)現(xiàn)隊(duì)列的問(wèn)題:
class Stack
{
public:
Stack(int n = 4)
{
if (n == 0)
{
_a = nullptr;
_size = -1;
_capacity = 0;
}
else
{
int* tmp = (int*)realloc(_a, sizeof(int) * n);
if (tmp == nullptr)
{
perror("realloc fail");
exit(-1);
}
_a = tmp;
_size = -1;
_capacity = n;
}
}
void Push(int n)
{
if (_size + 1 == _capacity)
{
int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
if (nullptr == tmp)
{
perror("realloc fail:");
exit(-1);
}
_a = tmp;
_capacity = newcapacity;
}
_a[_size++] = n;
}
int Top()
{
return _a[_size];
}
void Pop()
{
assert(_size > -1);
_size--;
}
void Destort()
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
bool Empty()
{
return _size == -1;
}
private:
int* _a;
int _size;
int _capacity;
};
class MyQueue
{
private:
Stack _pushst;
Stack _popst;
};
一般情況下都需要我們自己寫構(gòu)造函數(shù),決定初始化方式,成員變量全是自定義類型,可以考慮不寫構(gòu)造函數(shù)。會(huì)調(diào)用自定義類型的默認(rèn)構(gòu)造函數(shù)。
總結(jié):無(wú)參構(gòu)造函數(shù)、全缺省構(gòu)造函數(shù)、我們不寫編譯器默認(rèn)生成的構(gòu)造函數(shù),都可以認(rèn)為是默認(rèn)構(gòu)造函數(shù),并且默認(rèn)構(gòu)造函數(shù)只能存在一個(gè)(多個(gè)并存會(huì)產(chǎn)生二義性)。
3、析構(gòu)函數(shù)
3.1 析構(gòu)函數(shù)的概念
析構(gòu)函數(shù):與構(gòu)造函數(shù)功能相反,析構(gòu)函數(shù)不是完成對(duì)對(duì)象本身的銷毀,局部對(duì)象銷毀工作是由編譯器完成的。而對(duì)象在銷毀時(shí)會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù),完成對(duì)象中資源的清理工作。
3.2 特性
析構(gòu)函數(shù)是特殊的成員函數(shù),其特征如下:
1. 析構(gòu)函數(shù)名是在類名前加上字符 ~。
2. 無(wú)參數(shù)無(wú)返回值類型。
3. 一個(gè)類只能有一個(gè)析構(gòu)函數(shù)。若未顯式定義,系統(tǒng)會(huì)自動(dòng)生成默認(rèn)的析構(gòu)函數(shù)(對(duì)內(nèi)置類型不做處理,自定義類型會(huì)調(diào)用它自己的析構(gòu)函數(shù))。注意:析構(gòu)函數(shù)不能重載。
4. 對(duì)象生命周期結(jié)束時(shí),C++編譯系統(tǒng)會(huì)自動(dòng)調(diào)用析構(gòu)函數(shù)。
我們先來(lái)看日期類的析構(gòu):
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
cout << "Date(int year = 1, int month = 1, int day = 1)" << endl;
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
~Date()
{
cout << "~Date()" << endl;
_year = 0;
_month = 0;
_day = 0;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2;
return 0;
}
運(yùn)行結(jié)果:
我們這里可以看出析構(gòu)函數(shù)也是自動(dòng)調(diào)用的。
我們不寫,編譯器自動(dòng)生成默認(rèn)的析構(gòu)函數(shù)。
析構(gòu)函數(shù)的調(diào)用順序跟棧類似,后實(shí)例化的先析構(gòu)。
如果類中沒(méi)有申請(qǐng)資源時(shí),析構(gòu)函數(shù)可以不寫,直接使用編譯器生成的默認(rèn)析構(gòu)函數(shù),比如Date類;有資源申請(qǐng)時(shí),一定要寫,否則會(huì)造成資源泄漏,比如Stack類。
我們畫圖來(lái)看一下:
棧中的析構(gòu)函數(shù)就代替了棧的銷毀:
class Stack
{
public:
Stack(int n = 4)
{
if (n == 0)
{
_a = nullptr;
_top = -1;
_capacity = 0;
}
else
{
int* tmp = (int*)realloc(_a, sizeof(int) * n);
if (tmp == nullptr)
{
perror("realloc fail");
exit(-1);
}
_a = tmp;
_top = -1;
_capacity = n;
}
}
~Stack()
{
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
//void Destort()
//{
// free(_a);
// _a = nullptr;
// _top = _capacity = 0;
//}
void Push(int n)
{
if (_top + 1 == _capacity)
{
int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
if (nullptr == tmp)
{
perror("realloc fail:");
exit(-1);
}
_a = tmp;
_capacity = newcapacity;
}
_a[_top++] = n;
}
int Top()
{
return _a[_top];
}
void Pop()
{
assert(_top > -1);
_top--;
}
bool Empty()
{
return _top == -1;
}
private:
int* _a;
int _top;
int _capacity;
};
class MyQueue
{
private:
Stack _pushst;
Stack _popst;
};
對(duì)于棧這樣的,我們析構(gòu)函數(shù)代替了銷毀函數(shù),析構(gòu)函數(shù)會(huì)自動(dòng)調(diào)用,以前我們需要手動(dòng)調(diào)用銷毀函數(shù)的接口,現(xiàn)在不用調(diào)用了。
因此,構(gòu)造函數(shù)和析構(gòu)函數(shù)最大的優(yōu)勢(shì)是自動(dòng)調(diào)用。
4、拷貝構(gòu)造函數(shù)
4.1 拷貝構(gòu)造函數(shù)的概念
拷貝構(gòu)造函數(shù):只有單個(gè)形參,該形參是對(duì)本類類型對(duì)象的引用(一般常用const修飾),在用已存在的類類型對(duì)象創(chuàng)建新對(duì)象時(shí)由編譯器自動(dòng)調(diào)用。
4.2 特征
拷貝構(gòu)造函數(shù)也是特殊的成員函數(shù),其特征如下:
1. 拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載形式。
2. 拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須是同類型對(duì)象的引用,使用傳值方式編譯器會(huì)引發(fā)無(wú)窮遞歸調(diào)用。
3. 若未顯式定義,編譯器會(huì)生成默認(rèn)的拷貝構(gòu)造函數(shù)。 默認(rèn)的拷貝構(gòu)造函數(shù)對(duì)象按內(nèi)存存儲(chǔ)按字節(jié)序完成拷貝,這種拷貝叫做淺拷貝,或者值拷貝。
拷貝構(gòu)造就像是復(fù)制粘貼一樣。
拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須是類類型對(duì)象的引用,使用傳值方式編譯器會(huì)引發(fā)無(wú)窮遞歸調(diào)用。
傳值拷貝會(huì)發(fā)生無(wú)窮遞歸,我們來(lái)寫一個(gè)拷貝構(gòu)造函數(shù)。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//拷貝構(gòu)造
Date(Date d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
private:
int _year;
int _month;
int _day;
};
void func(Date d)
{
d.Print();
}
int main()
{
Date d1(2023, 8, 2);
func(d1);
return 0;
}
內(nèi)置類型的拷貝是直接拷貝,自定義類型的拷貝要調(diào)用拷貝構(gòu)造完成。
在vs2019中,傳值傳參編譯器會(huì)報(bào)錯(cuò):
因此,我們要是寫拷貝構(gòu)造函數(shù),形參必須是同類型的引用:
引用是給變量起別名,析構(gòu)自動(dòng)調(diào)用的順序是后定義先析構(gòu),拷貝的時(shí)候d1還沒(méi)有析構(gòu),因此是可以使用引用的,這樣就不會(huì)導(dǎo)致遞歸拷貝了。
我們將寫的拷貝構(gòu)造函數(shù)屏蔽掉,看看會(huì)如何:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//拷貝構(gòu)造
//Date(Date& d)
//{
// _year = d._year;
// _month = d._month;
// _day = d._day;
//}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
~Date()
{
_year = 0;
_month = 0;
_day = 0;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 8, 2);
Date d2(d1);
d2.Print();
return 0;
}
運(yùn)行結(jié)果:
?我們發(fā)現(xiàn),如果我們不寫,還是能實(shí)現(xiàn)拷貝,這是因?yàn)槲覀儾粚?,編譯器默認(rèn)生成一個(gè)拷貝構(gòu)造函數(shù),對(duì)于日期類這樣的淺拷貝,默認(rèn)生成的構(gòu)造函數(shù)是可以實(shí)現(xiàn)拷貝的。
我們?cè)賮?lái)看棧的拷貝構(gòu)造:
typedef int DataType;
class Stack
{
public:
Stack(int n = 4)
{
if (n == 0)
{
_a = nullptr;
_size = -1;
_capacity = 0;
}
else
{
int* tmp = (int*)realloc(_a, sizeof(int) * n);
if (tmp == nullptr)
{
perror("realloc fail");
exit(-1);
}
_a = tmp;
_size = -1;
_capacity = n;
}
}
//拷貝構(gòu)造
Stack(Stack& s)
{
_a = s._a;
_size = s._size;
_capacity = s._capacity;
}
~Stack()
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
void Push(int n)
{
if (_size + 1 == _capacity)
{
int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
if (nullptr == tmp)
{
perror("realloc fail:");
exit(-1);
}
_a = tmp;
_capacity = newcapacity;
}
_a[_size++] = n;
}
int Top()
{
return _a[_size];
}
void Pop()
{
assert(_size > -1);
_size--;
}
bool Empty()
{
return _size == -1;
}
private:
int* _a;
int _size;
int _capacity;
};
int main()
{
Stack s1;
Stack s2(s1);
return 0;
}
我們這里為棧寫的拷貝構(gòu)造,我們來(lái)試一下拷貝構(gòu)造:
這里為什么引發(fā)了異常呢?
我們調(diào)試看看:
這里我們可以看到,s1的_a與s2的_a地址是一樣的,當(dāng)s2拷貝完后就會(huì)析構(gòu),s2的_a被釋放掉后,s1還會(huì)再調(diào)用一次析構(gòu)函數(shù),這時(shí)再去釋放_(tái)a,_a的空間已經(jīng)被釋放過(guò)了,就會(huì)引發(fā)空指針異常的問(wèn)題。
因此,對(duì)于有空間申請(qǐng)的對(duì)象,在寫拷貝構(gòu)造的時(shí)候必須要深拷貝。
我們來(lái)改正代碼:
typedef int DataType;
class Stack
{
public:
Stack(int n = 4)
{
if (n == 0)
{
_a = nullptr;
_size = -1;
_capacity = 0;
}
else
{
int* tmp = (int*)realloc(_a, sizeof(int) * n);
if (tmp == nullptr)
{
perror("realloc fail");
exit(-1);
}
_a = tmp;
_size = -1;
_capacity = n;
}
}
//拷貝構(gòu)造
Stack(Stack& s)
{
cout << "Stack(Stack& s)" << endl;
//深拷貝
_a = (DataType*)malloc(sizeof(DataType) * s._capacity);
if (nullptr == _a)
{
perror("malloc fail:");
exit(-1);
}
memcpy(_a, s._a, sizeof(DataType) * (s._size+1));
_size = s._size;
_capacity = s._capacity;
}
~Stack()
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
}
void Push(int n)
{
if (_size + 1 == _capacity)
{
int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
int* tmp = (int*)realloc(_a, sizeof(int) * newcapacity);
if (nullptr == tmp)
{
perror("realloc fail:");
exit(-1);
}
_a = tmp;
_capacity = newcapacity;
}
_a[_size++] = n;
}
int Top()
{
return _a[_size];
}
void Pop()
{
assert(_size > -1);
_size--;
}
bool Empty()
{
return _size == -1;
}
private:
int* _a;
int _size;
int _capacity;
};
運(yùn)行結(jié)果:
總結(jié):像Date這樣不需要我們實(shí)現(xiàn)拷貝構(gòu)造,默認(rèn)生成的就可以用;Stack需要我們自己實(shí)現(xiàn)深拷貝的拷貝構(gòu)造,默認(rèn)生成的會(huì)出問(wèn)題;對(duì)于成員全是自定義類型的也不需要寫拷貝構(gòu)造,會(huì)調(diào)用自定義類型的拷貝構(gòu)造函數(shù)。
引申:
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-654153.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-654153.html
到了這里,關(guān)于[C++] 類與對(duì)象(中)類中六個(gè)默認(rèn)成員函數(shù)(1)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!