C++中類的默認成員函數(shù)詳解
C++中,對于任意一個類,都會為我們提供4個默認的成員函數(shù)(如果我們不顯示的去聲明)——構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)、賦值函數(shù)。這些函數(shù)在特定的情況下會被自動調(diào)用,但自動調(diào)用并不意味著它們能像用戶所期望的那樣能實現(xiàn)特定的功能或者完成特定的任務,更多的時候需要我們自己實現(xiàn)這些函數(shù)的功能
A(); //默認的構(gòu)造函數(shù) ~A(); //析構(gòu)函數(shù) A(const A&); //默認的拷貝函數(shù) A& operator = (const A& a); //默認賦值函數(shù)
一、構(gòu)造函數(shù)
構(gòu)造函數(shù)是一個特殊的成員函數(shù),名字與類名相同,通過類創(chuàng)建對象時由編譯器自動調(diào)用,保證每個數(shù)據(jù)成員都有 一個合適的初始值,并且在對象的生命周期內(nèi)只調(diào)用一次。構(gòu)造函數(shù)的功能是由類的實現(xiàn)者實現(xiàn),根據(jù)實際情況設計函數(shù)體和函數(shù)參數(shù),構(gòu)造函數(shù)必須有一個,或者可以有多個。
class Person { public: //無參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)都稱為默認構(gòu)造函數(shù),并且默認構(gòu)造函數(shù)只能有一個。 //注意:無參構(gòu)造函數(shù)、全缺省構(gòu)造函數(shù)、我們沒寫編譯器默認生成的構(gòu)造函數(shù),都可以認為是默認成員函數(shù)。 Person(string name = "馮同學", int age = 18) { _name = name; _age = age; } //打印信息 void Print() const { cout << "姓名:" << _name << "——年齡:" << _age << endl; } private: string _name; int _age; }; int main() { Person A;//調(diào)用全缺省的構(gòu)造函數(shù) Person B("風同學");//調(diào)用半缺省的構(gòu)造函數(shù) Person C("瘋同學",20); A.Print(); B.Print(); C.Print(); }
關(guān)于編譯器生成的默認成員函數(shù),很多人會有疑惑:在我們不實現(xiàn)構(gòu)造函數(shù)的情況下,編譯器會生成默認的構(gòu)造函數(shù)。但是看起來默認構(gòu)造函數(shù)又沒什么用?A對象調(diào)用了編譯器生成的默認構(gòu)造函數(shù),但是A對象_name是空字符串,_age依舊是隨機值。也就說在這里編譯器生成的默認構(gòu)造函數(shù)并沒有什么用??
class Person { public: void Print() const { cout << "姓名:" << _name << "——年齡:" << _age << endl; } private: string _name; int _age; }; int main() { Person A; A.Print(); }
解答:C++把類型分成內(nèi)置類型(基本類型)和自定義類型。內(nèi)置類型就是語法已經(jīng)定義好的類型:如
int/char…,自定義類型就是我們使用class/struct/union自己定義的類型,看看下面的程序,就會發(fā)現(xiàn)
編譯器生成默認的構(gòu)造函數(shù)會對自定類型成員B調(diào)用的它的默認成員函數(shù)
class B { public: B(int b) { _b = b; } private: int _b = 0; }; class A { private: int _a; B bb; }; int main() { A aa; return 0; }
在A類中,用B類創(chuàng)建了一個bb對象,bb對象就會調(diào)用構(gòu)造函數(shù),因為是自定義的構(gòu)造函數(shù),編譯器就不會給出默認的構(gòu)造函數(shù),所以就會報錯。如果將B的構(gòu)造函數(shù)改為無參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù),那么程序就對了,這樣也就證明了自定類型成員會調(diào)用的它的默認成員函數(shù)
構(gòu)造函數(shù)的特點
函數(shù)名與類名相同。無返回值。對象實例化時編譯器自動調(diào)用對應的構(gòu)造函數(shù)。構(gòu)造函數(shù)可以重載。在定義類時,如果沒有定義構(gòu)造函數(shù),則C++編譯器會自動提供一個默認構(gòu)造函數(shù)(沒有參數(shù)),一旦我們定義構(gòu)造函數(shù),C++編譯器就不會提供默認構(gòu)造函數(shù)無參構(gòu)造函數(shù)、全缺省構(gòu)造函數(shù)、我們沒寫編譯器默認生成的構(gòu)造函數(shù),都可以認為是默認成員函數(shù)。并且默認構(gòu)造函數(shù)只能有一個(如果默認構(gòu)造函數(shù)出現(xiàn)多個,在創(chuàng)建對象調(diào)用構(gòu)造函數(shù)時,可能會出現(xiàn)二義性)
二、析構(gòu)函數(shù)
與構(gòu)造函數(shù)相反的是析構(gòu)函數(shù),析構(gòu)函數(shù)不是完成對象的銷毀,局部對象銷毀工作是由編譯器完成的。而對象在銷毀時會自動調(diào)用析構(gòu)函數(shù),完成類的一些資源清理工作,例如在構(gòu)造函數(shù)中,我們?yōu)槌蓡T變量申請了內(nèi)存,我們就可以在析構(gòu)函數(shù)中將申請的內(nèi)存釋放
class Person { public: Person(string name = "馮同學", int age = 18) { _name = name; _age = age; } void Print() const { cout << "姓名:" << _name << "——年齡:" << _age << endl; } ~Person() { cout << _name << "正在調(diào)用~Person()" << endl; } private: string _name; int _age; }; int main() { Person A; Person B("風同學"); Person C("瘋同學", 20); A.Print(); B.Print(); C.Print(); }
關(guān)于編譯器自動生成的析構(gòu)函數(shù),是否會完成一些事情呢?下面的程序我們會看到,編譯器生成的默認析構(gòu)函數(shù),對會自定類型成員調(diào)用它的析構(gòu)函數(shù)。
class B { public: B(int b = 0) { _b = b; } ~B() { cout << "正在調(diào)用~B()" << endl; } private: int _b = 0; }; class A { private: int _a; B bb; }; int main() { A aa; return 0; }
析構(gòu)函數(shù)的特點
析構(gòu)函數(shù)名是在類名前加上字符 ~。無參數(shù)無返回值。一個類有且只有一個析構(gòu)函數(shù)。若未顯式定義,系統(tǒng)會自動生成默認的析構(gòu)函數(shù)。對象生命周期結(jié)束時,C++編譯系統(tǒng)系統(tǒng)自動調(diào)用析構(gòu)函數(shù)。
三、拷貝構(gòu)造函數(shù)
拷貝構(gòu)造函數(shù)是一個特殊的構(gòu)造函數(shù)(構(gòu)造函數(shù)的重載形式)。用基于同一類的已經(jīng)存在的一個對象拷貝初始化另一個馬上創(chuàng)建的對象。
class Person { public: Person(string name = "馮同學", int age = 18) { _name = name; _age = age; } void Print() const { cout << "姓名:" << _name << "——年齡:" << _age << endl; } Person(const Person& p) { _name = p._name; _age = p._age; } private: string _name; int _age; }; int main() { Person f("風同學", 20); Person l(f); f.Print(); l.Print(); }
如果沒有定義拷貝構(gòu)造函數(shù),C++編譯器也會提供一個默認的拷貝構(gòu)造函數(shù),不過該函數(shù)實現(xiàn)的是一個淺拷貝功能(將拷貝源按字節(jié)序賦值給拷貝對象)。淺拷貝對內(nèi)置類型基本存在什么影響,但對于在堆上開辟的對象會存在安全隱患,來看看一下程序
class Person { public: Person(int age = 18) { _name = new string("馮同學"); _age = age; } ~Person() { cout << "正在調(diào)用~Person()——" << _name << endl; delete _name; _name = nullptr; } private: string* _name; int _age; }; int main() { Person f(20); Person l(f); }
通過打印的結(jié)果我們可以發(fā)現(xiàn)用對象 f 去拷貝構(gòu)造對象 l 時,f 中的_name和 l 中的_name指向同一塊內(nèi)存空間(010C5440),并且在調(diào)用析構(gòu)函數(shù)時,對同一塊內(nèi)存空間進行了兩次釋放,最終導致了程序崩潰,這就是淺拷貝帶來的程序安全隱患。
不過我們可以將淺拷貝轉(zhuǎn)換為深拷貝從而解決問題
//自己實現(xiàn)拷貝構(gòu)造函數(shù) Person(const Person& p) { _name = new string; *_name = *p._name; _age = p._age; }
先申請內(nèi)存,在進行賦值就很好解決了淺拷貝問題
拷貝構(gòu)造函數(shù)的特點
拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個重載形式。拷貝構(gòu)造函數(shù)的參數(shù)只有一個且必須使用引用傳參,使用傳值方式會引發(fā)無窮遞歸調(diào)用。若未顯示定義,系統(tǒng)生成默認的拷貝構(gòu)造函數(shù)。 默認的拷貝構(gòu)造函數(shù)對象按內(nèi)存存儲按字節(jié)序完成拷貝,這種拷貝我們叫做淺拷貝,或者值拷貝。
四、賦值函數(shù)(賦值運算符重載)
賦值函數(shù)和拷貝構(gòu)造函數(shù)有點類似,不過賦值函數(shù)只是把一個已存在的對象賦值給另一個已存在的對象,使得那個已存在的對象具有和原對象相同的狀態(tài)。
class Person { public: Person(string name = "馮同學", int age = 18) { _name = name; _age = age; } Person& operator=(const Person& p) { _name = p._name; _age = p._age; } void Print() const { cout << "姓名:" << _name << "——年齡:" << _age << endl; } private: string _name; int _age; }; int main() { Person f("風同學", 21); Person l("鳳同學", 20); f = l; f.Print(); l.Print(); }
賦值函數(shù)的特點
使用關(guān)鍵字operator(所有的運算符重載都會使用這個關(guān)鍵字)返回值為類的引用(返回*this)不能改變運算符的優(yōu)先級/結(jié)合性/操作數(shù)個數(shù)一個類如果沒有顯式定義賦值運算符重載,編譯器也會生成一個,完成對象按字節(jié)序的值拷貝。
總結(jié)
到此這篇關(guān)于C++中類的默認成員函數(shù)詳解的文章就介紹到這了,更多相關(guān)C++成員函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
pybind11: C++ 工程提供 Python 接口的實例代碼
這篇文章主要介紹了pybind11: C++ 工程如何提供 Python 接口,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-09-09FFmpeg實現(xiàn)將編碼后數(shù)據(jù)保存成mp4
這篇文章主要為大家詳細介紹了FFmpeg如何實現(xiàn)將編碼后數(shù)據(jù)保存成mp4,即從內(nèi)存塊中獲取原始數(shù)據(jù),然后依次進行解碼、編碼、最后保存成mp4視頻文件,感興趣的可以了解一下2023-08-08