前提:如果一個類是空類,C++中空類中真的什么都沒有嗎,不是的,編譯器會自動生成6個默認成員函數。默認成員函數:用戶沒有顯式實現,編譯器會生成的成員函數稱為默認成員函數。
默認成員函數:構造函數,析構函數,拷貝構造函數,賦值運算符重載,取地址重載,const取地址重載。
構造函數(完成初始化):
我們在設計類的時候,在C語言中必須有一個初始化函數才好定義變量。在C++中類如果我們沒有寫這個初始化函數,那么編譯器就會默認生成一個默認構造函數。這個默認構造函數對內置類型不處理,自定義類型調用它的默認構造函數。
構造函數的調用:
class Data
{
public:Data(int year,int month,int day){_year = year;_month = month;_day = day;}
private://內置類型int _year = 2025;int _month = 4;int _day = 28;//自定義類型//Time _t;
};int main()
{Data day(2025,4,28);//調用帶參的構造函數return 0;
}
如果是無參的構造函數:Data day;? 即可,不要括號。?
構造函數的特性:
1.函數名與類名相同
2.無返回值
3.對象(也可以稱為自定義類型變量)實例化時編譯器自動調用對應的構造函數。
4.構造函數也可以重載。
5.如果類中沒有顯式定義構造函數,編譯器才會生成,一旦顯式定義就不會生成
6.編譯器生成的默認構造函數對內置類型不處理,自定義類型調用它的默認構造函數。
7.無參的構造函數,全缺省的構造函數,編譯器自動生成的構造函數都是默認構造函數。
注意提醒:編譯器生成的默認構造函數對于自定義類型會自動調用它的默認構造函數,而如果我們自己寫了一個無參的構造函數就按照自己寫的內容來,不會自動調用。
還有一個容易混淆的點:編譯器自動生成的默認構造函數會自動調用自定義類型的默認構造函數,如果這個自定義類型的構造函數如果不是默認構造函數就會報錯。
//會報錯的代碼:
class Time
{
public://不是默認構造函數Time(int hour,int minute,int second){cout << "Time" << endl;_hour = hour;_minute = minute;_second = second;}
private:int _hour;int _minute;int _second;
};class Data
{
private://內置類型int _year;int _month;int _day;//自定義類型Time _t;
};int main()
{Data day;return 0;
}
上述代碼 Data 類中就有 Time 自定義類型,但是構造函數不是默認構造函數所以報錯了。
//正確的代碼:
class Time
{
public://全缺省函數Time(int hour=0,int minute=0,int second=0){cout << "Time" << endl;_hour = hour;_minute = minute;_second = second;}
private:int _hour;int _minute;int _second;
};class Data
{
private://內置類型int _year;int _month;int _day;//自定義類型Time _t;
};int main()
{Data day;return 0;
}
所以我們盡量使用全缺省構造函數。
還有對于編譯器自動生成的默認構造函數,針對內置類型不做處理,C++11有了內置類型成員在類中聲明時可以給默認值 就是在聲明時給缺省值:
析構函數(完成清理工作):
析構函數是在對象銷毀時完成對象中資源的清理工作,對象在銷毀時會自動調用析構函數。
析構函數不是銷毀對象本身,對象本身是由編譯器銷毀的。
析構函數的特性:
1.析構函數名是類名前面加字符~。
2.無參數也無返回值。
3.一個類只有一個析構函數,如果沒有顯式定義,編譯器會自動生成默認的析構函數,注意:析構函數不能重載。
4.對象生命周期結束時,系統會自動調用析構函數。
5.編譯器生成的默認析構函數,對內置類型不做處理,對自定義類型調用它的析構函數。
6.如果類中有申請資源,例如:動態申請空間之類的需要手動銷毀,必須要寫析構函數手動銷毀,否則會造成資源泄漏。如果沒有資源申請則可以默認析構函數。
#include<iostream>
using namespace std;class Time
{
public:Time(int hour=0,int minute=0,int second=0){cout << "Time" << endl;_hour = hour;_minute = minute;_second = second;}~Time(){cout << "Time析構函數" << endl;}
private:int _hour;int _minute;int _second;
};class Data
{
public:Data(int year,int month,int day){cout << "Data" << endl;_year = year;_month = month;_day = day;}~Data(){cout << "Data析構函數" << endl;}
private:int _year = 2025;int _month = 4;int _day = 28;
};int main()
{Data day(2025,4,28);Time TY;return 0;
}
?上述代碼讓我們明白系統調用析構函數的順序:局部對象(后定義先析構)-> 局部靜態對象 ->
全局對象(后定義先析構):
拷貝構造函數(同類對象初始化創建對象):
拷貝構造函數:只有單個形參,形參是本類類型對象的引用(一般用const修飾),用已存在的自定義類型對象創建新對象時由編譯器自動調用。
拷貝構造的特性:
1.拷貝構造函數是構造函數的一個重載形式。
2.拷貝構造函數的參數只有一個且必須是類類型對象的引用,使用傳值方式編譯器會報錯,會引發無盡遞歸調用。
3.如果類中沒有顯式定義拷貝構造,編譯器會生成默認拷貝構造函數,默認拷貝構造對內置類型按內存存儲按字節序完成拷貝,是值拷貝,或者叫淺拷貝。自定義類型調用它的拷貝構造。
4.編譯器的默認拷貝構造,只會完成淺拷貝,一旦類中有資源申請,拷貝構造函數必須要寫,例如:有動態內存管理,那么指向動態內存的指針,淺拷貝只會拷貝地址,不會生成相同的動態內存,會導致很多問題。
5.拷貝構造典型調用場景:使用已存在對象創建新對象 / 函數參數類型為類類型對象 / 函數返回值類型為類類型對象。
class Data
{
public:Data(int year = 2025,int month = 4,int day = 29){cout << "Data" << endl;_year = year;_month = month;_day = day;}Data(const Data& d){cout << "Data拷貝構造" << endl;_year = d._year;_month = d._month;_day = d._day;}~Data(){cout << "Data析構函數" << endl;}
private://內置類型int _year;int _month;int _day;
};int main()
{Data day1;Data day2(day1);return 0;
}
?
當函數傳值傳參時或者返回值時調用拷貝構造:
class Data
{
public:Data(int year = 2025,int month = 4,int day = 29){cout << "Data" << endl;_year = year;_month = month;_day = day;}Data(const Data& d){cout << "Data拷貝構造" << endl;_year = d._year;_month = d._month;_day = d._day;}~Data(){cout << "Data析構函數" << endl;}
private://內置類型int _year;int _month;int _day;
};Data Test(Data d)
{Data tmp(d);return tmp;
}int main()
{Data day1;Test(day1);return 0;
}
?
實際過程中編譯器為了效率會減少一些拷貝構造優化拷貝構造。為了提高效率,一般對象傳參盡量使用引用類型,返回值看情況。
賦值運算符重載(把一個對象賦值給另一個對象):
運算符重載:
C++引入了運算符重載,運算符重載是具有特殊函數名的函數。
注意:1.不能通過連接其他符號來創建新操作符:operator@
2.重載操作符必須有一個類類型參數,不能去重載運算符改變內置類型的行為。
3.? .*? ?::? ?sizeof? ?:? .? ?以上五個運算符不能被重載。
class Data
{
public:Data(int year = 2025,int month = 4,int day = 29){cout << "Data" << endl;_year = year;_month = month;_day = day;}Data(const Data& d){cout << "Data拷貝構造" << endl;_year = d._year;_month = d._month;_day = d._day;}//這里左操作數是this指針,指向調用函數的對象bool operator==(const Data& d2){return _year == d2._year && _month == d2._month && _day == d2._day;}~Data(){cout << "Data析構函數" << endl;}
private://內置類型int _year;int _month;int _day;
};int main()
{Data day1;Data day2(day1);cout << (day1 == day2) << endl;return 0;
}
?賦值運算符重載:
1.賦值運算符重載格式:1參數類型 const T& ,傳遞引用提高效率。2返回值類型 T& 支持連續賦值且提高效率。3檢查是否給自己賦值。4返回 *this,要符合連續賦值的含義。
2.賦值運算符只能重載成類的成員函數不能重載成全局函數。因為類中沒有顯式實現,編譯器會生成默認賦值運算符重載,如果在全局中也有一個,就沖突了。
3.編譯器生成的默認運算符重載,對內置類型淺拷貝,對自定義類型調用它的賦值運算符重載。
還是需要注意類中是否有資源申請,有的話必須要實現。
class Data
{
public:Data(int year = 2025,int month = 4,int day = 29){cout << "Data" << endl;_year = year;_month = month;_day = day;}Data(const Data& d){cout << "Data拷貝構造" << endl;_year = d._year;_month = d._month;_day = d._day;}Data& operator= (const Data& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}bool operator== (const Data& d2){return _year == d2._year && _month == d2._month && _day == d2._day;}~Data(){cout << "Data析構函數" << endl;}
private://內置類型int _year;int _month;int _day;
};int main()
{Data day1(1,2,3);Data day2;day2 = day1;return 0;
}
和拷貝構造的區別:拷貝構造是一個存在的對象創建一個新對象并且賦值,賦值運算符重載是兩個存在的對象,一個給另一個賦值。