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