C++類與對象深入之運算符重載與const及初始化列表詳解
一:運算符重載
C++為了增強代碼的可讀性引入了運算符的重載,運算符重載是具有特殊函數(shù)名的函數(shù),也具有其返回值類型以及參數(shù)列表,其返回值類型與參數(shù)列表與普通函數(shù)類似。
函數(shù)名字為:關(guān)鍵字operator后面接需要重載的運算符符號
函數(shù)原型:返回值類型 operator操作符(參數(shù)列表)
相等運算符重載
對內(nèi)置類型我們想要判斷兩個變量是否相等我們可以直接使用相等運算符,但是如果是一個自定義類型呢?那么這時候就需要重載運算符了。
下面重載一個全局的 operator ==:
class Date { public: // 默認生成的析構(gòu)函數(shù),內(nèi)置類型成員不做處理,自定義類型成員會去調(diào)用它的析構(gòu)函數(shù) Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print(){ cout << _year << "-" << _month << "-" << _day << endl; } int GetYear(){ return _year; } int GetMonth(){ return _month; } int GetDay(){ return _day; } private: int _year; int _month; int _day; }; bool operator==( Date& d1, Date& d2) { return d1.GetYear() == d2.GetYear() && d1.GetMonth() == d2.GetMonth() && d1.GetDay() == d2.GetDay(); } int main(){ Date d1(2022, 5, 16); Date d2(2022, 5, 16); if (operator==(d1, d2)){ cout << "==" << endl; } if (d1 == d2){ // 編譯器會處理成對應(yīng)重載運算符調(diào)用 if (operator==(d1, d2)){ cout << "==" << endl; } system("pause"); return 0; }
我們把運算符重載成全局的時候,面對私有成員不可類外訪問,我們提供三個函數(shù)接口,當(dāng)然還有別的處理方式。我們可以上述“運算符重載可以判斷兩個自定義日期類是否相等
我們看到主函數(shù)中兩處調(diào)用重載運算符,兩種寫法都可以,第二種方法更簡單,編譯器會自動處理成第一種方式。
我們還可以重載成類的成員函數(shù),作為類成員重載函數(shù)時,其形參看起來比操作數(shù)目少1個
class Date { public: // 默認生成的析構(gòu)函數(shù),內(nèi)置類型成員不做處理,自定義類型成員會去調(diào)用它的析構(gòu)函數(shù) Date(int year = 1, int month = 1, int day = 1){ _year = year; _month = month; _day = day; } void Print(){ cout << _year << "-" << _month << "-" << _day << endl; } bool operator==(const Date& d) { return _year == d._year && _month == d._month && _day == d._day; } private: int _year; int _month; int _day; }; int main() { Date d1(2022, 5, 16); Date d2(2022, 5, 16); if (d1.operator==(d2)){ cout << "==" << endl; } if (d1 == d2){ // 編譯器會處理成對應(yīng)重載運算符調(diào)用 if (d1.operator==(d2)) cout << "==" << endl; } system("pause"); return 0; }
”“解釋:重載成成員函數(shù)的時候,成員函數(shù)里隱藏了this指針,形參列表其實是(Date * this, const Date & d2),函數(shù)調(diào)用時左操作數(shù)是this指針指向的對象!
同樣地,我們看到主函數(shù)中兩處調(diào)用重載運算符,兩種寫法都可以,第二種方法更簡單,編譯器會自動處理成第一種方式。
賦值運算符重載
C++編譯器至少給一個類添加4個函數(shù):
- 默認構(gòu)造函數(shù)(無參,函數(shù)體為空)
- 默認析構(gòu)函數(shù)(無參,函數(shù)體為空)
- 默認拷貝構(gòu)造函數(shù),對屬性進行值拷貝
- 賦值運算符 operator=, 對屬性進行值拷貝
賦值運算符主要有4點:
- 參數(shù)類型
- 返回值
- 檢查是否自己給自己賦值
- 返回 *this
代碼示例:
class Date { public: // 默認生成的析構(gòu)函數(shù),內(nèi)置類型成員不做處理,自定義類型成員會去調(diào)用它的析構(gòu)函數(shù) Date(int year = 1, int month = 1, int day = 1){ _year = year; _month = month; _day = day; } void Print(){ cout << _year << "-" << _month << "-" << _day << endl; } // d2 = d1; -> d2.operator=(&d2, d1) // d1 = d1 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; }; int main() { Date d1(2022, 5, 16); Date d2; Date d3(d1); // 拷貝構(gòu)造 -- 一個存在的對象去初始化另一個要創(chuàng)建的對象 d2 = d1; // 賦值重載/復(fù)制拷貝 -- 兩個已經(jīng)存在對象之間賦值 d1 = d1; system("pause"); return 0; }
”“解釋:類對象d1給d2賦值,特別注意賦值重載函數(shù)的返回值,和檢查是否自己給自己賦值!
我們要區(qū)分拷貝構(gòu)造和賦值重載:拷貝構(gòu)造是一個存在的對象去初始化另一個要創(chuàng)建的對象,而賦值重載是兩個已經(jīng)存在對象之間賦值。
如下列監(jiān)視列表我們可以看出,結(jié)果d1.d2,d3都是一樣的。
正如一開始所說的,如果一個類中沒有顯示定義賦值運算符重載,編譯器也會生成一個,完成對象的淺拷貝。既然是淺拷貝就有局限,如果類中有屬性指向堆區(qū),做賦值操作時也會出現(xiàn)深淺拷貝問題。
小于運算符重載
下面我們比較日期類的大?。?/p>
代碼示例:
class Date { public: // 默認生成的析構(gòu)函數(shù),內(nèi)置類型成員不做處理,自定義類型成員會去調(diào)用它的析構(gòu)函數(shù) Date(int year = 1, int month = 1, int day = 1){ _year = year; _month = month; _day = day; } void Print(){ cout << _year << "-" << _month << "-" << _day << endl; } bool operator<(const Date& d){ if ((_year < d._year) || (_year == d._year && _month < d._month) || (_year == d._year && _month == d._month && d._day < d._day)) { return true; } else{ return false; } } private: int _year; int _month; int _day; }; int main() { Date d1(2022, 4, 16); Date d2(2022, 5, 16); if (d1 < d2){ // 編譯器會處理成對應(yīng)重載運算符調(diào)用 if (d1.operator<(d2)) cout << "<" << endl; } system("pause"); return 0; } < 請按任意鍵繼續(xù). . .
二:const成員
const修飾類的成員函數(shù)
將const修飾的類成員函數(shù)稱之為const成員函數(shù),const修飾類成員函數(shù),實際修飾該成員函數(shù)隱含的this指針,表明在該成員函數(shù)中不能對類的任何成員進行修改。
//this指針的本質(zhì)是一個指針常量,指針的指向不可修改
//如果想讓指針指向的值也不可以修改,需要聲明常函數(shù)
我們見下面一段代碼:
void Func(const Date& d) { d.Print(); // d.Print(&d); -> const Date* //傳的是一個指針指向的內(nèi)容不可以改變的指針, //而單純的this指針是指向不可以改變,所以權(quán)限放大了 必須在Print函數(shù)后加const,把this指針權(quán)限進一步放小 cout << &d << endl; } void TestDate3() { Date d1(2022, 5, 18); d1.Print(); // d1.Print(&d1); -> Date* Func(d1); cout << &d1 << endl; }
代碼解釋:如果Print不是常函數(shù),那么在TestDate3()函數(shù)中調(diào)用Print函數(shù)不會報錯,但是如果在Func函數(shù)中調(diào)用就會報錯,這是因為在TestDate3()函數(shù)中調(diào)用Print函數(shù)傳過去d1的地址,用this指針接收,權(quán)限縮小。而在Func函數(shù)中,d指針指向的內(nèi)容不可以改變,而在形參this指向的內(nèi)容可以改變,所以權(quán)限放大。所以必須給Print函數(shù)加上const,以表示常函數(shù)。
????????????建議:
建議成員函數(shù)中不修改成員變量的成員函數(shù),都可以加上const, 普通對象和const對象都可以調(diào)用。
三:cin、cout重載
我們都知道了cin、cout對于內(nèi)置類型可以自動識別其類型進行輸入輸出,這是因為在庫函數(shù)中提供了對應(yīng)的重載!
下面我們直接看代碼:
class Date { public: // 友元函數(shù) friend std::ostream& operator<<(std::ostream& out, const Date& d); friend std::istream& operator>>(std::istream& out, Date& d); //........ }
首先我們定義成全局函數(shù),必須在對應(yīng)的類中聲明友元,這樣全局函數(shù)才可以訪問類中成員!
std::ostream& operator<<(std::ostream& out, const Date& d) { out << d._year << "-" << d._month << "-" << d._day << endl; return out; } std::istream& operator>>(std::istream& in, Date& d) { in >> d._year >> d._month >> d._day; return in; }
四:初始化列表
C++提供了初始化列表語法,用來初始化屬性
構(gòu)造函數(shù)賦初值
在創(chuàng)建對象的時候,編譯器通過調(diào)用構(gòu)造函數(shù),給對象中各個成員變量一個合適的初值。
class Person { public: Person(int a, int b, int c) { m_A = a; m_B = b; m_C = c; } private: int m_A; int m_B; int m_C; }; int main() { Person p(1, 2, 3); system("pause"); return 0; }
上述代碼我們通過構(gòu)造函數(shù)賦值的方法來“初始化”。
注意:上述代碼中調(diào)用構(gòu)造函數(shù)后,對象中已經(jīng)有了一個初始值,但是我們不能將之稱為類對象成員的初始化,只能稱為賦值,因為初始化只可以初始化一次,而構(gòu)造函數(shù)體內(nèi)可以賦值多次
初始化列表
class Person { public: //初始化列表方式初始化 Person(int a, int b, int c) : m_A(a) , m_B(b) , m_C(c) {} private: int m_A; int m_B; int m_C; }; int main() { Person p(1, 2, 3); system("pause"); return 0; }
初始化列表:以一個冒號開始,以逗號分隔,每部分由成員變量后面跟上一個放在括號里的初始值或者表達式。
注意:
每個成員變量在初始化列表中只能出現(xiàn)一次(即初始化只一次)。
類中包含以下成員,就必須在初始化列表位置進行初始化。
- 引用成員變量
- const成員變量
- 自定義類型成員(該類沒有對應(yīng)的默認構(gòu)造函數(shù))
我們這里先這么理解一下:初始化列表可以認為就是對象的成員變量定義的地方,對于上面的三種成員只能在定義初始化,而其他的內(nèi)置類型變量,/可以在定義時初始化,也可以定義時不初始化,后面再賦值修改。
下面我們直接上代碼:
int value = 10; class A { public: A(int x) :_x(x) {}① /*A(int x = 0) :_x(x) {}*/② private: int _x; }; class Date { public: Date(int year, int n, int a) :_n(n) , _ref(value) ,_aa(a)//①:當(dāng)自定義類型沒提供默認構(gòu)造時,就在想用我們提供的值去初始化的時候,就在這里顯式的去調(diào)用它的構(gòu)造函數(shù) //如果有對應(yīng)的默認構(gòu)造,就不用寫這行。不在初始化列表初始化,自動調(diào)用默認構(gòu)造函數(shù)初始化 { _year = year; //如果不在初始化列表初始化 ,但是我們還想去改變里面變量的值,只能這么玩 //A aa(a); 調(diào)用默認構(gòu)造 ② //_aa = aa;//賦值 這里使用了默認提供的賦值運算符重載? 這種方式麻煩 就用下面這種好 } private: int _year; // 聲明 const int _n; int& _ref; A _aa; }; int main() { Date d1(2022, 5, 20); // 對象定義 system("pause"); return 0; }
代碼解釋:上述代碼中,_n、_ref、_aa都是要在初始化列表初始化的變量,見代碼注釋
??????:上面在初始化的時候都比較麻煩,因此我們建議盡量在初始化列表初始化,如下代碼:
int value = 10; class A { public: A(int x) :_x(x) {} private: int _x; }; class Date { public: Date(int year, int n, int a) :_n(n) , _ref(value) , _year(year) , _aa(a)//當(dāng)自定義類型沒提供默認構(gòu)造時,就在這里顯式的去調(diào)用它的構(gòu)造函數(shù) {} //總結(jié):建議盡量在初始化列表初始化 private: int _year; // 聲明 const int _n; int& _ref; A _aa; }; int main() { Date d1(2022, 5, 20); // 對象定義 system("pause"); return 0; }
正如我們代碼注釋的地方所說,當(dāng)自定義類型沒提供默認構(gòu)造時,我們又想用我們提供的值去初始化的時候,就需要我們?nèi)ナ謩拥恼{(diào)用它的構(gòu)造函數(shù),,,,,,
初始化結(jié)果如下:
如果有對應(yīng)的默認構(gòu)造函數(shù),我們也可以不用寫這行。這時候就不在初始化列表初始化,編譯器自動調(diào)用默認構(gòu)造函數(shù)初始化,初始化為隨機值還是確定值要看是什么類型的默認構(gòu)造函數(shù)。
int value = 10; class A { public: A(int x = 10)//全缺省默認構(gòu)造 :_x(x) {} private: int _x; }; class Date { public: Date(int year, int n, int a) :_n(n) , _ref(value) , _year(year) //, _aa(a) 這時候我們在A類中提供了全缺省的默認構(gòu)造函數(shù),就可以不寫這行代碼 {} private: int _year; // 聲明 const int _n; int& _ref; A _aa; }; int main() { Date d1(2022, 5, 20); // 對象定義 system("pause"); return 0; }
如上述代碼,我們沒寫, _aa(a)這一行,但我們提供了默認構(gòu)造函數(shù),結(jié)果表明依然可以初始化。
??????????????????????????????????????????????????????????????????:
下面我們再看一個例子:
class Stack { public: Stack(int capacity = 0) { _a = (int*)malloc(sizeof(int)*capacity); _top = 0; _capacity = capacity; } private: int* _a; int _top; int _capacity; }; class MyQueue { public: MyQueue(int size = 100) :_size(size)//如果這里什么都沒寫,那么默認初始化列表就表示_st1就調(diào)用_st1的默認構(gòu)造,_st2同理, //_size如果給了缺省值,就用缺省值初始化,沒給就是隨機值, //如果顯式(int size = 1)、_size(size)寫了 就用顯式的這個值初始化 {} private: Stack _st1; Stack _st2; size_t _size = 1000; // 缺省值 如果上面哪個地方給了缺省參數(shù),這里的這個缺省值也沒用了 }; int main() { MyQueue mq; return 0; }
代碼解釋:在MyQueue類中聲明_size的時候給了一個缺省值,然后在默認構(gòu)造函數(shù)的地方也給了缺省形參,還在初始化列表中對_size進行初始化。如下結(jié)果:
class Stack { public: Stack(int capacity = 0) { _a = (int*)malloc(sizeof(int)*capacity); _top = 0; _capacity = capacity; } private: int* _a; int _top; int _capacity; }; class MyQueue { public: MyQueue(int size = 100) //:_size(size)//如果這里什么都沒寫,那么默認初始化列表就表示_st1就調(diào)用_st1的默認構(gòu)造,_st2同理, //_size如果給了缺省值,就用缺省值初始化,沒給就是隨機值, //如果顯式(int size = 1)、_size(size)寫了 就用顯式的這個值初始化 {} private: Stack _st1; Stack _st2; size_t _size = 1000; // 缺省值 如果上面哪個地方給了缺省參數(shù),這里的這個缺省值也沒用了 }; int main() { MyQueue mq; return 0; }
如果上述_size沒有在初始化列表初始化,那么_size就被聲明時候給的缺省值初始化。如下結(jié)果:
我們再看三段代碼,看看他們的不同之處:
class Stack { public: Stack(int capacity = 0) { _a = (int*)malloc(sizeof(int)*capacity); _top = 0; _capacity = capacity; } private: int* _a; int _top; int _capacity; }; class MyQueue { public: MyQueue(int size = 100) :_size(size) {} private: Stack _st1; Stack _st2; size_t _size = 1000; }; int main() { MyQueue mq(10); return 0; }
class Stack { public: Stack(int capacity = 0) { _a = (int*)malloc(sizeof(int)*capacity); _top = 0; _capacity = capacity; } private: int* _a; int _top; int _capacity; }; class MyQueue { public: MyQueue(int size) :_size(size) {} private: Stack _st1; Stack _st2; size_t _size = 1000; }; int main() { MyQueue mq(10); return 0; }
class Stack { public: Stack(int capacity = 0) { _a = (int*)malloc(sizeof(int)*capacity); _top = 0; _capacity = capacity; } private: int* _a; int _top; int _capacity; }; class MyQueue { public: MyQueue(int size = 100) {} private: Stack _st1; Stack _st2; size_t _size = 1000; }; int main() { MyQueue mq(10); return 0; }
explicit關(guān)鍵字
構(gòu)造函數(shù)不僅可以構(gòu)造和初始化對象,對于單個參數(shù)的構(gòu)造函數(shù),還具有類型轉(zhuǎn)換的作用。
下面我們還是看一段代碼:
class Date { public: /*explicit Date(int year) :_year(year) { cout << "Date(int year)" << endl; }*/ Date(int year) :_year(year) { cout << "Date(int year)" << endl; } private: int _year; }; int main() { Date d2 = 2022; // 構(gòu)造 + 拷貝構(gòu)造 -》 優(yōu)化 合二為一 system("pause"); return 0; }
代碼解釋:當(dāng)沒有在有參構(gòu)造前面加explicit的時候,程序不會報錯,以Date d2 = 2022這種方式來調(diào)用構(gòu)造函數(shù),其實是先調(diào)用有參構(gòu)造,在調(diào)用拷貝構(gòu)造函數(shù)完成!
??如果我們加上explicit關(guān)鍵字:
class Date { public: explicit Date(int year) :_year(year) { cout << "Date(int year)" << endl; } private: int _year; }; int main() { //Date d1(2022); // 構(gòu)造 Date d2 = 2022; // 構(gòu)造 + 拷貝構(gòu)造 -》 優(yōu)化 合二為一 //Date& d6 = 2022;//一開始就說了對常數(shù)取別名要加const //const Date& d6 = 2022;//整型2022被不同類型區(qū)別名時,前面就說了,臨時變量具有常性,此時是引用的2022的臨時變量的別名 system("pause"); return 0; }
程序報錯:
這是因為這其中發(fā)生了隱式類型轉(zhuǎn)換,當(dāng)加上explicit時,就阻止了這個轉(zhuǎn)換!
??再比如說:
class Date { public: explicit Date(int year) :_year(year) { cout << "Date(int year)" << endl; } private: int _year; }; int main() { const Date& d6 = 2022;//整型2022被不同類型區(qū)別名時,前面就說了,臨時變量具有常性,此時是引用的2022的臨時變量的別名 system("pause"); return 0; }
我們引用類型和引用實體不是同一個類型的時候,我們需要加上const,這是在前文就說過(前文查看),這其中發(fā)生隱式類型轉(zhuǎn)換的時候產(chǎn)生了臨時變量,需要加上const,那么這個時候加上explicit就阻止了這個轉(zhuǎn)換,所以報錯!
??總結(jié):反正隱式轉(zhuǎn)換法中會有類型的轉(zhuǎn)換,explicit可以阻止這種轉(zhuǎn)換!
到此這篇關(guān)于C++類與對象深入之運算符重載與const及初始化列表詳解的文章就介紹到這了,更多相關(guān)C++類與對象內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言獲取Shell返回結(jié)果的實現(xiàn)方法
下面小編就為大家?guī)硪黄狢語言獲取Shell返回結(jié)果的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-07-07