目錄
1. 簡單認識C++ 類的默認函數
1.1 默認構造函數
1.2 析構函數
1.3 拷貝構造函數
2. 拷貝構造函數的深入理解
拷貝構造的特點:?
實際運用
3. 賦值運算符重載的深入理解
3.1.運算符重載
3.2樣例
1.比較運算符重載
2.算術運算符重載
3.自增和自減運算符重載
4.輸入輸出運算符重載
?3.3.賦值運算符重載
總結
在 C++ 編程中,類的默認函數拷貝構造函數以及賦值運算符重載是非常重要的概念。它們不僅可以讓我們更方便地操作對象,還能增強代碼的可讀性和可維護性。本文將結合提供的日期類(Date
)代碼,深入剖析這些概念。
1. 簡單認識C++ 類的默認函數
在 C++ 中,當我們定義一個類時,編譯器會自動為我們生成一些默認的成員函數,包括默認構造函數、析構函數、拷貝構造函數和賦值運算符重載函數。不過,當我們手動定義了其中某些函數時,編譯器就不會再生成對應的默認函數了。
1.1 默認構造函數
默認構造函數是在創建對象時沒有提供任何參數時調用的函數。在Date
類中,我們手動定義了一個帶參數的構造函數:
Date(int year = 1, int month = 1, int day = 1)
{_year = year;_month = month;_day = day;
}
這個構造函數有默認參數,因此它既可以作為帶參數的構造函數使用,也可以作為默認構造函數使用。當我們創建對象時不提供參數,就會使用默認值year = 1, month = 1, day = 1
。
1.2 析構函數
析構函數在對象生命周期結束時自動調用,用于釋放對象占用的資源。在Date
類中,析構函數如下:
~Date()//可不寫
{_year = 0;_month = 0;_day = 0;
}
1.3 拷貝構造函數
拷貝構造函數用于創建一個新對象,該對象是另一個已存在對象的副本。在Date
類中,注釋掉的拷貝構造函數如下:
// Date(const Date& d1)//可不寫
// {
// _year = d1._year;
// _month = d1._month;
// _day = d1._day;
// }
2. 拷貝構造函數的深入理解
如果一個構造函數的第一個參數是自身類類型的引用,且任何額外的參數都有默認值,則此構造函數也叫做拷貝構造函數,也就是說拷貝構造是一個特殊的構造函數。
拷貝構造的特點:?
1.拷貝構造函數是構造函數的一個重載。
2.拷貝構造函數的參數只有一個且必須是類類型對象的引用,使用傳值方式編譯器直接報錯,因為語法邏輯上會引發無窮遞歸調用。
3.C++規定自定義類型對象進行拷貝行為必須調用拷貝構造,所以這里自定義類型傳值傳參和傳值返回都會調用拷貝構造完成。
4.若未顯式定義拷貝構造,編譯器會生成自動生成拷貝構造函數。自動生成的拷貝構造對內置類型成員變量會完成值拷貝/淺拷貝(一個字節一個字節的拷貝),對自定義類型成員變量會調用他的拷貝構造。
?5.像Date這樣的類成員變量全是內置類型且沒有指向什么資源,編譯器自動生成的拷貝構造就可以完成需要的拷貝,所以不需要我們顯示實現拷貝構造。像Stack這樣的類,雖然也都是內置類型,但是_a指向了資源,編譯器自動生成的拷貝構造完成的值拷貝/淺拷貝不符合我們的需求,所以需要我們自己實現深拷貝(對指向的資源也進行拷貝)。像MyQueue這樣的類型內部主要是自定義類型Stack成員,編譯器自動生成的拷貝構造會調用Stack的拷貝構造,也不需要我們顯示實現MyQueue的拷貝構造。這里還有一個小技巧,如果一個類顯示實現了析構并釋放資源,那么他就需要顯示寫拷貝構造,否則就不需要。
實際運用
拷貝構造函數的原型通常為ClassName(const ClassName& other)
。當我們使用以下方式創建對象時,會調用拷貝構造函數:
Date d1(2025, 6, 12);
Date d2(d1); // 調用拷貝構造函數
在Date
類中,由于成員變量都是基本數據類型,淺拷貝就足夠了。但如果類中包含動態分配的資源(如指針),淺拷貝可能會導致多個對象指向同一塊內存,從而在析構時引發重復釋放的問題,這時就需要手動定義拷貝構造函數進行深拷貝。
3. 賦值運算符重載的深入理解
3.1.運算符重載
1.當運算符被用于類類型的對象時,C++語言允許我們通過運算符重載的形式指定新的含義。C++規定類類型對象使用運算符時,必須轉換成調用對應運算符重載,若沒有對應的運算符重載,則會編譯報錯。
2.運算符重載是具有特殊名字的函數,他的名字是由operator和后面要定義的運算符共同構成。和其他函數一樣,它也具有其返回類型和參數列表以及函數體。
3.重載運算符函數的參數個數和該運算符作用的運算對象數量一樣多。一元運算符有一個參數,二元運算符有兩個參數,二元運算符的左側運算對象傳給第一個參數,右側運算對象傳給第二個參數。
4.如果一個重載運算符函數是成員函數,則它的第一個運算對象默認傳給隱式的this指針,因此運算符重載作為成員函數時,參數比運算對象少一個。
5.運算符重載以后,其優先級和結合性與對應的內置類型運算符保持一致。不能通過連接語法中沒有的符號來創建新的操作符:比如operator@。
6.注意以下5個運算符不能重載
7.重載操作符至少有一個類類型參數,不能通過運算符重載改變內置類型對象的含義,如:int
operator+(int x,int y)
8.一個類需要重載哪些運算符,是看哪些運算符重載后有意義,比如Date類重載operator-就有意義,但是重載operator+就沒有意義。
3.2樣例
1.比較運算符重載
比較運算符(==
,?!=
,?>
,?>=
,?<
,?<=
)用于比較兩個對象的大小關系。以==
運算符重載為例:
bool Date::operator==(const Date& d) const
{return _year == d._year&& _month == d._month&& _day == d._day;
}
?這個函數比較兩個Date
對象的年、月、日是否相等,如果相等則返回true
,否則返回false
。其他比較運算符的重載也類似,通過比較成員變量的值來確定對象的大小關系。
2.算術運算符重載
算術運算符(+
,?-
,?+=
,?-=
)用于對日期進行加減操作。以+=
運算符重載為例
Date& Date::operator+=(int day)
{if(day < 0){return *this -= (-day);}_day += day;while(_day > GetMonthDay(_year, _month))//獲取該月天數的函數{_day -= GetMonthDay(_year, _month);++_month;if(_month == 13){_year++;_month = 1;}}return *this;
}
這個函數將日期加上指定的天數,并處理了跨月和跨年的情況。+
運算符重載則是通過調用+=
運算符重載來實現的:
Date Date::operator+(int day) const
{Date tmp = *this;tmp += day;return tmp;
}
3.自增和自減運算符重載
自增和自減運算符(++
,?--
)分為前置和后置兩種形式。前置運算符返回引用,后置運算符返回臨時對象。以前置++
運算符重載為例:
Date& Date::operator++()
{*this += 1;return *this;
}
后置++
運算符重載通過一個int
參數來區分前置和后置:
Date Date::operator++(int)
{Date tmp = *this;*this += 1;return tmp;
}
4.輸入輸出運算符重載
輸入輸出運算符(<<
,?>>
)用于將對象輸出到流中或從流中讀取對象。
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}
?這個函數將Date
對象的年、月、日輸出到輸出流中。>>
運算符重載則用于從輸入流中讀取年、月、日,并檢查輸入的日期是否合法:
istream& operator>>(istream& in, Date& d)
{while(1){cout << "請輸入年月日:>";in >> d._year >> d._month >> d._day;if(!d.CheckDate()){cout << "輸入的日期非法!";d.Print();cout << "請重新輸入年月日!!" << endl;}else{break;}}return in;
}
?3.3.賦值運算符重載
賦值運算符重載是一個默認成員函數,用于完成兩個已經存在的對象直接的拷貝賦值,這里要注意跟拷貝構造區分,拷貝構造用于一個對象拷貝初始化給另一個要創建的對象。
賦值運算符重載的特點:
1.賦值運算符重載是一個運算符重載,規定必須重載為成員函數。賦值運算重載的參數建議寫成const 當前類類型引用,否則會傳值傳參會有拷貝
2.有返回值,且建議寫成當前類類型引用,引用返回可以提高效率,有返回值目的是為了支持連續值場景。
3.沒有顯式實現時,編譯器會自動生成一個默認賦值運算符重載,默認賦值運算符重載行為跟默認構造函數類似,對內置類型成員變量會完成值拷貝/淺拷貝(一個字節一個字節的拷貝),對自定義類型成員變量會調用他的拷貝構造。
4.像Date這樣的類成員變量全是內置類型且沒有指向什么資源,編譯器自動生成的賦值運算符重載可以完成需要的拷貝,所以不需要我們顯示實現賦值運算符重載。像Stack這樣的類,雖然也都是內置類型,但是_a指向了資源,編譯器自動生成的賦值運算符重載完成的值拷貝/淺拷貝不符合我們的需求,所以需要我們自己實現深拷貝(對指向的資源也進行拷貝)。像MyQueue這樣的類型內部主要是自定義類型Stack成員,編譯器自動生成的賦值運算符重載會調用Stack的賦值運算符重載,也不需要我們顯示實現MyQueue的賦值運算符重載。這里還有一個小技巧,如果一個類顯示實現了析構并釋放資源,那么他就需要顯示寫賦值運算符重載,否則就不需要。
總結
通過對Date
類的分析,我們深入了解了 C++ 類的默認函數、拷貝構造函數和賦值運算符重載的概念和用法。默認函數為我們提供了基本的對象創建和銷毀機制,拷貝構造函數用于創建對象的副本,運算符重載則讓我們可以像使用內置類型一樣使用自定義類型進行運算。在實際編程中,我們需要根據類的具體情況來決定是否需要手動定義這些函數,以確保代碼的正確性和性能。