1. 類的默認成員函數
如果一個類什么成員都沒有,簡稱空類。但實際上,任何類在不寫成員時,編譯器會自動生成6個默認成員函數(用戶未顯式實現,編譯器生成的成員函數)
這6個成員函數可分為三類:
1. 初始化和清理:構造函數完成初始化工作;析構函數完成清理工作
2. 拷貝復制:拷貝構造是用同類對象初始化創建對象;賦值重載是把一個對象賦值給另一個對象
3. 取地址重載:普通對象和const對象取地址,這兩個很少自己實現
2. 構造函數
2.1 引入
class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};
對于上面的Date類,如果每次創建對象后要設置信息都要調用 Init 的公有方法,會有些麻煩。那能不能在創建對象時就能設置信息呢?構造函數便出現了
構造函數是一個特殊的成員函數,創建類類型對象時由編譯器自動調用,在對象整個生命周期內只調用一次
2.2 簡介
構造函數雖然叫構造,但它的作用不是開空間創建對象,而是初始化對象
它具有以下特性:
1. 函數名和類名相同
2. 無返回值
3. 構造函數可以重載
4. 對象實例化時編譯器自動調用
5. 如果類沒有顯式定義構造函數,那編譯器會自動生成一個無參的默認構造函數;如果類顯式定義了,那編譯器不再生成
6. 構造函數對于類中內置類型成員不初始化,對于自定義類型成員調用它的默認成員函數(c++11中對于這一點打了補丁:內置類型成員在類中聲明時可以給默認值)
7. 無參的構造函數和全缺省的構造函數都稱為默認構造函數,并且默認構造函數只有一個(即:無參構造函數、全缺省構造函數、編譯器自己默認生成的構造函數都是默認構造函數,但為了避免二義性,通常只定義一種)
?
class Time
{
public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;}private:int _hour;int _minute;int _second;
};class Date
{
public:/*//一旦顯式定義任何構造函數,編譯器將不再生成Date(int year, int month, int day){_year = year;_month = month;_day = day;}*///無參構造函數//如果使用無參構造函數創建對象,對象后不加括號,否則就變成函數聲明Date(){}//帶參構造函數Date(int year, int month, int day){_year = year;_month = month;_day = day;}//全缺省構造函數//全缺省和無參可以看為函數重載,但在調用時會出現二義性Date(int year = 2025, int month = 1, int day = 1){_year = year;_month = month;_day = day;}private://內置類型,c++11內置成員變量可以在聲明時給默認值int _year;int _month;int _day;//自定義類型Time t;
};int main()
{Date d1;//調用無參構造函數Date d2(2025, 1, 1);//帶參構造函數return 0;
}?
3. 析構函數
3.1 引入
構造函數是完成初始化對象的工作的,和構造函數相反,析構函數就是完成對象中資源的清理工作(析構函數不完成對對象本身銷毀),對象銷毀時編譯器自動調用
3.2 簡介
析構函數具有以下特征:
1. 析構函數名是類名前加字符 ~
2. 無參數無返回類型
3. 一個類只能有一個析構函數,不能重載。如果沒顯式定義,系統自動生成默認析構函數
4. 對象生命周期結束,編譯器自動調用析構函數
5. 對于對象中內置類型,編譯器不需要資源清理,最后系統直接將其內存回收即可;對于自定義類型,會調用它的析構函數
6. 如果類沒有申請資源,析構函數可以不寫,使用生成的默認析構函數即可;但有資源申請時,一定要寫,否則會資源泄露
class Time
{
public:~Time(){cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};class Date
{
private://內置類型int _year = 2025;int _month = 1;int _day = 1;//自定義類型Time t;//創建Date對象,銷毀時要將Time類對象t銷毀,編譯器會給Date類生成一個默認析構函數//目的是調用Time類析構函數,確保Date對象銷毀時內部每個自定義對象都可以銷毀
};
4. 拷貝構造函數
4.1 引入
如果我們要創建一個和已經存在對象一樣的新對象,那拷貝構造函數便發揮作用
拷貝構造函數目的是用已經存在的類類型對象創建新對象,編譯器自動調用。所以它只有一個形參(對該類類型對象的引用,一般用const修飾)
4.2 簡介
它的特征如下:
1. 拷貝構造函數是構造函數的一個重載形式
2. 參數只有一個且必須是該類類型對象的引用(用傳值方式直接報錯 --- 會引發無窮遞歸)
3. 如果沒有顯式定義,編譯器自動生成默認拷貝構造函數。(默認拷貝構造函數按內存存儲字節序完成拷貝,這種拷貝叫淺拷貝或值拷貝)
4. 對于默認拷貝構造函數,內置類型是淺拷貝,而自定義類型是調用其拷貝構造函數完成拷貝
(調用拷貝構造要傳參,傳參就要拷貝構造,拷貝構造就要傳參,傳參就要........,這也是為什么如果參數不是傳引用,就會引發無窮遞歸)
5. 如果類中沒涉及資源申請,拷貝構造可寫可不寫;一旦涉及資源申請,拷貝構造一定要寫,否則是淺拷貝。
(比如:我們創建了一個棧對象s1,在它的構造函數中申請10個元素的空間;然后對象s2使用s1拷貝構造,而類中沒有顯式定義拷貝構造函數,那編譯器自動生成默認拷貝構造函數,而這個拷貝構造是值拷貝,就會導致s1和s2指向同一塊空間。當銷毀s1和s2時,這個空間會釋放多次,導致程序崩潰)
6. 拷貝構造的調用場景:
?使用已存在對象創建新對象
?函數參數類型為類類型對象
?函數返回類型為類類型對象
一般對象傳參使用引用類型;返回時,如果場景允許引用(如非局部類對象.....)盡量使用引用
?
#include<iostream>
using namespace std;
class Date
{
public:Date(int year, int month, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;}
private:int _year;int _month;int _day;
};Date Test(Date d)
{//調用拷貝構造函數創建tempDate temp(d);//以值方式返回,調用拷貝構造構造臨時變量return temp;
}int main()
{//構造函數創建d1Date d1(2025, 1, 1);//傳值方式傳遞,調用拷貝構造函數Test(d1);return 0;
}?
5. 運算符重載
5.1 運算符重載
運算符重載是具有特殊函數名的函數,有函數名字、參數列表和返回值類型,返回值類型和參數列表和普通函數相似
函數名字:operator+要重載運算符符合(比如:operator< )
函數原型:返回值類型 operator操作符(參數列表)
注意事項:
1. 不能連接其他符號創建新操作符:operator@(這是錯誤的)
2. 重載操作符必須有一個類類型參數(即必須有一個自定義類型),操作符有幾個操作數,重載函數就有幾個參數
3. 用于內置類型的運算符,含義不能變
4. 作為類成員函數重載,形參看起來比操作數少1(成員函數第一個參數為隱藏的this)
5. 以下5個運算符不能重載:.*? ? ::? ? sizeof? ? ?? :? ? ?.
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}//左操作數為this指針
bool operator==(const Date& d2)
{return _year == d2._year&& _month == d2._month&& _day == d2._day;
}private:int _year;int _month;int _day;
};//全局operator==
//如果成員變量是私有的,那無法訪問;重載成成員函數即可
/*bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}*/void Test()
{Date d1(2025, 1, 1);Date d2(2025, 2, 2);
}
5.2 賦值運算符重載
1. 格式:
參數類型:const T&,傳引用提高效率
返回值類型:T& ,返回引用提高效率,便于支持連續賦值
檢測是否給自己賦值
返回*this:符合連續賦值含義
2.特征:
?1. 賦值運算符只能重載成類的成員函數,不能重載成全局函數
(如果賦值運算符不顯示實現,編譯器默認生成。那再在類外自己實現一個全局賦值運算符重載,會導致沖突)
?2. 用戶沒顯式實現,編譯器自動生成一個默認賦值運算符重載(值拷貝的方式)
(內置類型成員變量直接復制,自定義類型要調用對應類的復制運算符重載)
3. 如果類為涉及資源管理,賦值運算符可寫可不寫;但涉及資源管理必須自己實現
(比如:我們創建了一個棧對象s1,在它的構造函數中申請10個元素的空間;然后對象s2的構造函數也申請10個元素的空間,s2=s1,編譯器會將s1的內容原封不動拷貝給s2,這導致s2的原空間丟失,內存泄漏;s1和s2共享一塊空間,后續會導致一份空間釋放兩次)
class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time& operator=(const Time& t){if (this != &t){_hour = t._hour;_minute = t._minute;_second = t._second;}return *this;}
private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year = 2025, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}//賦值重載Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private://內置類型int _year;int _month;int _day;//自定義類型Time t;
};
5.3 前置++和后置++重載
class Date
{
public:Date(int year = 2025, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//前置++//this指向對象在函數結束后不銷毀,用傳引用Date& operator++(){_day += 1;return *this;}//后置++//為了讓前置++和后置++區別,c++規定:后置++重載時多加一個int類型參數//但調用函數時這個參數不用傳遞,編譯器自動傳遞//tmp為臨時對象,只能傳值返回Date operator++(int){Date tmp(*this);_day += 1;return tmp;}private:int _year;int _month;int _day;
};
6. const成員
const修飾的“成員函數”稱為const成員函數,實際修飾該成員函數隱藏this指針,表示該成員函數中不能對類的任何成員進行修飾
class Date{public:Date(int year = 2025, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//實際看為void print(const Date*this)void print()const{cout << "Print()const" << endl;cout << _year << endl;cout << _month << endl;cout << _day << endl;}private:int _year;int _month;int _day;};
注意事項:
1. 成員函數后面加const后,普通和const對象都可以調用
2. 不能所有成員函數都加const,比如要修改的對象成員變量的函數不能加
3. 只有成員函數內部不修改成員變量,都應該加const,這樣const對象和普通對象都可以調用
7. 取地址和const取地址操作符重載
這兩個默認成員函數一般不重新定義,編譯器會自己生成
class Date{public:Date* operator&(){return this;}const Date* operator&()const{return this;}private:int _year;int _month;int _day;};
使用編譯器默認取地址重載即可,特殊情況如:想讓別人獲取到指定內容,才重載