六個默認成員函數(shù)
如果一個類中什么成員都沒有,簡稱為空類。空類中真的什么都沒有嗎? 并不是
任何類在什么都不寫時,編譯器會自動生成以下6個默認成員函數(shù)。
默認成員函數(shù):用戶沒有顯式實現(xiàn),編譯器會生成的成員函數(shù)稱為默認成員函數(shù) 我們實現(xiàn)了,編譯器就不會生成了
1 構(gòu)造函數(shù)
構(gòu)造函數(shù)是一個特殊的成員函數(shù),名字與類名相同,創(chuàng)建類類型對象時由編譯器自動調(diào)用,以保證
每個數(shù)據(jù)成員都有 一個合適的初始值,并且在對象整個生命周期內(nèi)只調(diào)用一次
語法
- 函數(shù)名與類名相同。
- 無返回值
- 對象實例化時編譯器自動調(diào)用對應(yīng)的構(gòu)造函數(shù)
- 構(gòu)造函數(shù)可以重載(最好實現(xiàn)一個全缺省的構(gòu)造函數(shù))
class Date {
public:
Date(int year = 2024, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
void show() {
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date today(2024, 2, 18);//默認構(gòu)造
Date yesterday(2024, 2, 17);//默認構(gòu)造
today.show();
yesterday.show();
return 0;
}
來看效果
特性
-
如果類中沒有顯示定義構(gòu)造函數(shù),C++編譯器會自動生成一個無參的默認構(gòu)造函數(shù),一旦用戶顯示定義,編譯器將不在生成。
-
關(guān)于編譯器生成的默認成員函數(shù),有個疑惑:不實現(xiàn)構(gòu)造函數(shù)的情況下,編譯器會生成 默認的構(gòu)造函數(shù)。但是看起來默認構(gòu)造函數(shù)又沒什么用?Date對象調(diào)用了編譯器生成的默認構(gòu)造函數(shù),但是Date對象_year / _month / _day,依舊是隨機值。也就說在這里編譯器生成的默認構(gòu)造函數(shù)并沒有什么用??
解答:C++把類型分成內(nèi)置類型(基本類型)和自定義類型。內(nèi)置類型就是語言提供的數(shù)據(jù)類型,如:int/char…,自定義類型就是我們使用class/struct/union等自己定義的類型,看看下面的程序,就會發(fā)現(xiàn)編譯器生成默認的構(gòu)造函數(shù)會對自定類型成員 _t 調(diào)用的它的默認成員函數(shù)。
class Time
{
public:
Time(){
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本類型(內(nèi)置類型)
int _year;
int _month;
int _day;
// 自定義類型
Time _t;
};
int main()
{
Date d;
return 0;
}
上面程序會對 _t 進行默認構(gòu)造,由于我們編寫了構(gòu)造函數(shù),_t 中 的內(nèi)容成功初始化,但是_year / _month / _day缺依然是隨機值。
- 無參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)都稱為默認構(gòu)造函數(shù),并且默認構(gòu)造函數(shù)只能有一個。注意:無參構(gòu)造函數(shù)、全缺省構(gòu)造函數(shù)、我們沒寫編譯器默認生成的構(gòu)造函數(shù),都可以認為是默認構(gòu)造函數(shù)
2 析構(gòu)函數(shù)
析構(gòu)函數(shù)與構(gòu)造函數(shù)功能相反,析構(gòu)函數(shù)不是完成對對象本身的銷毀,局部對象銷毀工作是由編譯器完成的。而對象在銷毀時會自動調(diào)用析構(gòu)函數(shù),完成對象中資源的清理工作。
語法
- 析構(gòu)函數(shù)名是在類名前加上字符
~
。 - 無參數(shù)無返回值類型。
- 一個類只能有一個析構(gòu)函數(shù)。若未顯式定義,系統(tǒng)會自動生成默認的析構(gòu)函數(shù)。注意:析構(gòu)函數(shù)不能重載
- 對象生命周期結(jié)束時,C++編譯系統(tǒng)系統(tǒng)自動調(diào)用析構(gòu)函數(shù)。
下面的棧可以幫助我們理解析構(gòu)函數(shù)的作用
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申請空間失敗!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 析構(gòu)函數(shù)
~Stack()
{ //如果 不為空
if (_array)
{ // 釋放指針
free(_array);
//初始化
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
void TestStack()
{
Stack s;
s.Push(1);
s.Push(2);
//生命周期結(jié)束自動析構(gòu)清理
}
特性
關(guān)于編譯器自動生成的析構(gòu)函數(shù),是否會完成一些事情呢?下面的程序我們會看到,編譯器生成的默認析構(gòu)函數(shù),對自定類型成員調(diào)用它的析構(gòu)函數(shù)。
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本類型(內(nèi)置類型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定義類型
Time _t;
};
int main()
{
Date d;
return 0;
}
調(diào)用自定義類型 time 的析構(gòu)函數(shù)
注意 :
析構(gòu)函數(shù)的調(diào)用順序,先創(chuàng)建先銷毀,先銷毀局部變量,在銷毀全局變量。
如果類中沒有申請資源時,析構(gòu)函數(shù)可以不寫,直接使用編譯器生成的默認析構(gòu)函數(shù),比如Date類;有資源申請時,一定要寫,否則會造成資源泄漏,比如Stack類
3 拷貝構(gòu)造函數(shù)
拷貝構(gòu)造函數(shù):只有單個形參,該形參是對本類類型對象的引用(一般常用const修飾),在用已存在的類類型對象創(chuàng)建新對象時由編譯器自動調(diào)用
特性
- 拷貝構(gòu)造是構(gòu)造函數(shù)的一個重載形式。
- 拷貝構(gòu)造函數(shù)的參數(shù)只有一個且必須是類類型對象的引用,使用直接的傳值編譯器會直接報錯,因為會引發(fā)無窮遞歸調(diào)用。
原因:傳值拷貝時
第一步:開辟一個臨時空間;
第二步:由于臨時空間是需要構(gòu)造的,重新調(diào)用拷貝構(gòu)造函數(shù)(無窮遞歸形成…)
- 若未顯式定義,編譯器會生成默認的拷貝構(gòu)造函數(shù)。 默認的拷貝構(gòu)造函數(shù)對象按內(nèi)存存儲按字節(jié)序完成拷貝,這種拷貝叫做淺拷貝,或者值拷貝。
- 淺拷貝不能實現(xiàn)復雜的類拷貝,涉及指針等內(nèi)容會拷貝失敗。
- 拷貝構(gòu)造函數(shù)典型調(diào)用場景:
使用已存在對象創(chuàng)建新對象
函數(shù)參數(shù)類型為類類型對象
函數(shù)返回值類型為類類型對象
4 賦值運算符重載
運算符重載
C++為了增強代碼的可讀性引入了運算符重載,運算符重載是具有特殊函數(shù)名的函數(shù),也具有其返回值類型,函數(shù)名字以及參數(shù)列表,其返回值類型與參數(shù)列表與普通的函數(shù)類似。函數(shù)名字為:關(guān)鍵字operator后面接需要重載的運算符符號。
函數(shù)原型:返回值類型 operator"操作符" (參數(shù)列表)
注意:
- 不能通過連接其他符號來創(chuàng)建新的操作符:比如operator@
- 重載操作符必須有一個類類型參數(shù)
- 用于內(nèi)置類型的運算符,其含義不能改變,例如:內(nèi)置的整型+,不 能改變其含義作為類成員函數(shù)重載時,其形參看起來比操作數(shù)數(shù)目少1,因為成員函數(shù)的第一個參數(shù)為隱藏的this
-
.* :: sizeof ?: .
注意以上5個運算符不能重載。這個經(jīng)常在筆試選擇題中出現(xiàn)。
來看樣例:
class Date {
public:
Date(int year = 2024, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
bool operator==( const Date& d2) {
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
void show() {
cout << _year << "-" << this->_month << "-" << this->_day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date today(2024, 2, 18);
Date yesterday(2024, 2, 17);
today.show();
yesterday.show();
cout << "operator==()\n" << (today == yesterday) << endl;
return 0;
}
不相等返回假。
賦值運算符重載
- 賦值運算符重載格式
參數(shù)類型:const T&,傳遞引用可以提高傳參效率
返回值類型:T&,返回引用可以提高返回的效率,有返回值目的是為了支持連續(xù)賦值
檢測是否自己給自己賦值
返回*this :要復合連續(xù)賦值的含義
class Date {
public:
Date(int year = 2024, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
//運算符重載
bool operator==( const Date& d2) {
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
//賦值運算符重載
Date& operator=(const Date& d)
{
if(this != &d){
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void show() {
cout << _year << "-" << this->_month << "-" << this->_day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date today(2024, 2, 18);
Date day = today;
today.show();
day.show();
return 0;
}
可以成功賦值。
- 賦值運算符只能重載成類的成員函數(shù)不能重載成全局函數(shù)
原因:賦值運算符如果不顯式實現(xiàn),編譯器會生成一個默認的。此時用戶再在類外自己實現(xiàn)一個全局的賦值運算符重載,就和編譯器在類中生成的默認賦值運算符重載沖突了,故賦值運算符重載只能是類的成員函數(shù) - 用戶沒有顯式實現(xiàn)時,編譯器會生成一個默認賦值運算符重載,以值的方式逐字節(jié)拷貝。注意:內(nèi)置類型成員變量是直接賦值的,而自定義類型成員變量需要調(diào)用對應(yīng)類的賦值運算符重載完成賦值。
既然編譯器生成的默認賦值運算符重載函數(shù)已經(jīng)可以完成字節(jié)序的值拷貝了
還需要自己實現(xiàn)嗎?當然像日期類這樣的類是沒必要的。那么下面的類呢?驗證一下試試?
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申請空間失敗");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType *_array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2;
s2 = s1;
return 0;
}
這里會發(fā)現(xiàn)下面的程序會崩潰掉?這里就需要我們以后講的深拷貝去解決。
注意:如果類中未涉及到資源管理,賦值運算符是否實現(xiàn)都可以;一旦涉及到資源管理則必須要實現(xiàn)文章來源:http://www.zghlxwxcb.cn/news/detail-833562.html
特例:前置++ 與 后置++
前置++:返回+1之后的結(jié)果
// 注意:this指向的對象函數(shù)結(jié)束后不會銷毀,故以引用方式返回提高效率
Date& operator++()
{
_day += 1;
return *this;
}
后置++:
前置++和后置++都是一元運算符,為了讓前置++與后置++形成能正確重載
C++規(guī)定:后置++重載時多增加一個int類型的參數(shù),但調(diào)用函數(shù)時該參數(shù)不用傳遞,編譯器自動傳遞
注意:
后置++是 先使用后+1,因此需要返回+1之前的舊值,故需在實現(xiàn)時需要先將this保存
一份,然后給this+1,文章來源地址http://www.zghlxwxcb.cn/news/detail-833562.html
Date operator++(int)
{
Date temp(*this);
_day += 1;
// temp是臨時對象,因此只能以值的方式返回,不能返回引用
return temp;
}
Thanks?(?ω?)?謝謝閱讀!??!
下一篇文章見?。?!
到了這里,關(guān)于【C++】C++入門—初識構(gòu)造函數(shù) , 析構(gòu)函數(shù),拷貝構(gòu)造函數(shù),賦值運算符重載的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!