C++類與對象深入之構(gòu)造函數(shù)與析構(gòu)函數(shù)詳解
對象的初始化和清理
生活中我們買的電子產(chǎn)品都基本會有出廠設(shè)置,在某一天我們不用時候也會刪除一些自己信息數(shù)據(jù)保證安全。C++中的面向?qū)ο髞碓从谏睿總€對象也都會有初始設(shè)置以及對象銷毀前的清理數(shù)據(jù)的設(shè)置。
一:構(gòu)造函數(shù)
對象的初始化和清理也是兩個非常重要的安全問題,一個對象或者變量沒有初始狀態(tài),對其使用后果是未知。c++利用了構(gòu)造函數(shù)解決上述問題,這兩個函數(shù)將會被編譯器自動調(diào)用,完成對象初始化和清理工作。對象的初始化和清理工作是編譯器強制要我們做的事情,因此如果我們不提供構(gòu)造和析構(gòu),編譯器也會提供,編譯器提供的構(gòu)造函數(shù)和析構(gòu)函數(shù)是空實現(xiàn)。
構(gòu)造函數(shù)是一個特殊的成員函數(shù),名字與類名相同,實例化類對象時由編譯器自動調(diào)用,保證每個數(shù)據(jù)成員都有一個合適的初始值,并且在對象的生命周期內(nèi)只調(diào)用一次
構(gòu)造函數(shù)語法:類名(){}
1.1:構(gòu)造函數(shù)的特性
構(gòu)造函數(shù)是特殊的成員函數(shù),需要注意的是,構(gòu)造函數(shù)的名字雖然叫構(gòu)造,但是構(gòu)造函數(shù)的主要任務(wù)并不是開空間創(chuàng)建對象,而是初始化對象。
構(gòu)造函數(shù)特征:
1. 構(gòu)造函數(shù),沒有返回值也不寫void
2. 函數(shù)名稱與類名相同
3. 構(gòu)造函數(shù)可以有參數(shù),因此可以發(fā)生重載
4. 程序在調(diào)用對象時候會自動調(diào)用構(gòu)造,無須手動調(diào)用,而且只會調(diào)用一次
class Date { public: Date() { _year = 1; _month = 1; _day = 1; } Date(int year, int month, int day) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { Date d1; //調(diào)用無參構(gòu)造 d1.Print(); Date d2(2022, 5, 15); //調(diào)用帶參的構(gòu)造 d2.Print(); system("pause"); return 0; }
5. 如果類中沒有顯式定義的構(gòu)造函數(shù),則C++編譯器會自動生成一個無參的默認構(gòu)造函數(shù),一旦用戶顯式定義編譯器將不再生成。
6. 無參的構(gòu)造函數(shù)和全缺省的構(gòu)造函數(shù)都被稱為默認構(gòu)造函數(shù),并且默認構(gòu)造函數(shù)只有一個。注意:無參構(gòu)造函數(shù)、全缺省構(gòu)造函數(shù)、以及我們沒顯式寫由編譯器默認生成的構(gòu)造函數(shù),都可以認為是默認構(gòu)造函數(shù)。即不用傳參就可以調(diào)用的函數(shù)
class Date { public: Date(int year = 1, int month = 1, int day = 1)//默認全缺省構(gòu)造函數(shù) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; // 年 int _month; // 月 int _day; // 日 }; int main() { Date d1; d1.Print(); Date d2(2022, 5, 15); d2.Print(); Date d3(2022); d3.Print(); Date d4(2022, 10); d4.Print(); system("pause"); return 0; }
1-1-1
2022-5-15
2022-1-1
2022-10-1
請按任意鍵繼續(xù). . .
7. 默認生成構(gòu)造函數(shù)對于內(nèi)置類型成員變量不做處理,因為編譯器默認生成的構(gòu)造函數(shù)都是空實現(xiàn),對于自定義類型成員變量做出處理,相當于實例化對象自動調(diào)用該類的默認構(gòu)造函數(shù)!如下述代碼中Date date和A _aa有什么區(qū)別呢?不都是實例化對象自動調(diào)用默認構(gòu)造函數(shù)嗎?。?!
代碼示例:
class A { public: A(){ cout << " A()" << endl; _a = 0; } private: int _a; }; class Date { public: void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; // 年 int _month; // 月 int _day; // 日 A _aa; }; int main(){ Date date; date.Print(); system("pause"); return 0; } A()
-858993460--858993460--858993460
請按任意鍵繼續(xù). . .
默認構(gòu)造函數(shù)不會對自己的變量初始化,會對自定義類型處理,自定義類型成員會去調(diào)用它的默認構(gòu)造函數(shù)!因為這里實例化對象也只能調(diào)用默認構(gòu)造函數(shù)!?。。ㄈ绻远x類型的構(gòu)造函數(shù)沒有顯示定義,也會是隨機值)。
接下來我們利用代碼詳細看看上面這段話:
示例1:默認生成的默認構(gòu)造函數(shù)
class Stack { public: private: int* _a; int _top; int _capacity; }; class MyQueue { public: // 默認生成構(gòu)造函數(shù)就可以用了 void push(int x) { } int pop() { } private: Stack _st1; Stack _st2; }; int main() { MyQueue q; q.push(1); //Stack st; system("pasue"); return 0; }
上面這段代碼是可以編譯過的,在Myqueue類中只有自定義類型,所以我們不需要寫構(gòu)造函數(shù),使用默認生成的即可。然后Myqueue類中聲明Stack類實例化對象時,會去調(diào)用Stack類的默認構(gòu)造函數(shù)(這里是自動生成的默認構(gòu)造函數(shù)),編譯通過!
這里咋們看監(jiān)視界面:
由于Stack的默認構(gòu)造函數(shù)是默認生成的,同樣不會對內(nèi)置類型成員變量做初始化,所以顯示是隨機值!
示例2:無參默認構(gòu)造
class Stack { public: Stack() { _a = nullptr; _top = _capacity = 0; } private: int* _a; int _top; int _capacity; }; class MyQueue { public: // 默認生成構(gòu)造函數(shù)就可以用了 void push(int x) { } int pop() { } private: Stack _st1; Stack _st2; }; int main() { MyQueue q; q.push(1); //Stack st; system("pasue"); return 0; }
這段代碼也是可以編譯過的,在Myqueue類中只有自定義類型,所以我們不需要寫構(gòu)造函數(shù),使用默認生成的即可。然后Myqueue類中聲明Stack類實例化對象時,會去調(diào)用Stack類的默認構(gòu)造函數(shù)(這里是咋們自己提供的默認構(gòu)造函數(shù)),編譯通過!
同樣的,我們看監(jiān)視界面:
由于Stack的默認構(gòu)造函數(shù)是是我們自己提供的,同時對內(nèi)置類型做了初始化,所以這里的各個值不再是隨機值!
示例3:全缺省默認構(gòu)造函數(shù)
class Stack { public: Stack(int capacity = 10) { _a = (int*)malloc(sizeof(int)*capacity); assert(_a); _top = 0; _capacity = capacity; } private: int* _a; int _top; int _capacity; }; class MyQueue { public: // 默認生成構(gòu)造函數(shù)就可以用了 void push(int x) { } int pop() { } private: Stack _st1; Stack _st2; }; int main() { MyQueue q; q.push(1); //Stack st; system("pasue"); return 0; }
這段代碼也是可以編譯過的,在Myqueue類中只有自定義類型,所以我們不需要寫構(gòu)造函數(shù),使用默認生成的即可。然后Myqueue類中聲明Stack類實例化對象時,會去調(diào)用Stack類的默認構(gòu)造函數(shù)(這里是咋們自己提供的全缺省默認構(gòu)造函數(shù)),編譯通過!
同樣的,觀察監(jiān)視界面:
我們通過全缺省默認構(gòu)造函數(shù)對各個值做出了初始化,因此不再是隨機值!
錯誤示例:有參構(gòu)造函數(shù)
class Stack { public: Stack(int capacity) { _a = (int*)malloc(sizeof(int)*capacity); assert(_a); _top = 0; _capacity = capacity; } private: int* _a; int _top; int _capacity; }; class MyQueue { public: // 默認生成構(gòu)造函數(shù)就可以用了 void push(int x) { } int pop() { } private: Stack _st1; Stack _st2; }; int main() { MyQueue q; q.push(1); //Stack st; system("pasue"); return 0; }
程序報錯!
因為我們在Stack類中提供了一個有參構(gòu)造函數(shù),這時Stack類中不再有默認構(gòu)造函數(shù),因此Myqueue的默認構(gòu)造函數(shù)無法調(diào)用Stack的默認構(gòu)造函數(shù),編譯不通過!
C++11還支持在聲明的時候給自定義類型變量賦一個缺省值:
class MyQueue { private: int _size = 0; Stack _st1; Stack _st2; };
總結(jié):如果一個類中的成員全是自定義類型,我們就可以不寫構(gòu)造函數(shù),就用默認生成的構(gòu)造函數(shù)。如果有內(nèi)置類型的成員,或者需要顯示傳參初始化,那么都要自己實現(xiàn)構(gòu)造函數(shù)。
1.2:構(gòu)造函數(shù)的分類
兩種分類方式:
- 按參數(shù)分為: 有參構(gòu)造和無參構(gòu)造
- 按類型分為: 普通構(gòu)造和拷貝構(gòu)造
二:析構(gòu)函數(shù)
2.1:概念
與構(gòu)造函數(shù)功能相反,析構(gòu)函數(shù)不是完成對象的銷毀,局部對象銷毀工作由編譯器完成。而對象在銷毀時會自動調(diào)用析構(gòu)函數(shù),完成類的一些資源清理工作
2.2:特性
語法:~類名(){}
1. 析構(gòu)函數(shù),沒有返回值也不寫void
2. 函數(shù)名稱與類名相同,在名稱前加上符號 ~
3. 析構(gòu)函數(shù)不可以有參數(shù),因此不可以發(fā)生重載
4. 程序在對象銷毀前會自動調(diào)用析構(gòu),無須手動調(diào)用,而且只會調(diào)用一次
class Person { public: //構(gòu)造函數(shù) Person(){ cout << "Person的構(gòu)造函數(shù)調(diào)用" << endl; } //析構(gòu)函數(shù) ~Person(){ cout << "Person的析構(gòu)函數(shù)調(diào)用" << endl; } }; void test01(){ Person p; } int main() { test01(); system("pause"); return 0; }
Person的構(gòu)造函數(shù)調(diào)用
Person的析構(gòu)函數(shù)調(diào)用
請按任意鍵繼續(xù). . .
如結(jié)果表示,在對象創(chuàng)建之前編譯器自動調(diào)用了析構(gòu)函數(shù)。
??同樣的,我們也可以自己提供析構(gòu)函數(shù)來對類上的一些資源完成清理工作。
示例:
typedef int DataType; class SeqList{ public: SeqList(int capacity = 10){ _pData = (DataType*)malloc(capacity * sizeof(DataType)); assert(_pData); _size = 0; _capacity = capacity; } ~SeqList(){ if (_pData){ free(_pData); _pData = nullptr; _capacity = 0; _size = 0; } } private: int * _pData; size_t _size; size_t _capacity; }; int main(){ SeqList Sq; system("pause"); return 0; }
如上述代碼,在構(gòu)造函數(shù)中在堆區(qū)開辟了空間,這時就需要我們自己提供析構(gòu)函數(shù)來釋放對應(yīng)的空間。
注意:默認生成的析構(gòu)函數(shù),內(nèi)置類型成員不做處理,自定義類型成員會去調(diào)用它的析構(gòu)函數(shù)
三:拷貝構(gòu)造函數(shù)
3.1:概念
在現(xiàn)實生活中我們會遇到兩個小孩長得一摸一樣,我們稱其為雙胞胎。
拷貝構(gòu)造,顧名思義就是在創(chuàng)建對象的時候,創(chuàng)建一個與原對象一摸一樣的新對象。
構(gòu)造函數(shù):參數(shù)列表只有單個形參,該形參是對本類類型對象的引用(一般用const修飾),在用已存在的類類型對象創(chuàng)建新對象時由編譯器自動調(diào)用。
如下列代碼:
class Person { public: //有參構(gòu)造函數(shù) Person(int a) { age = a; cout << "有參構(gòu)造函數(shù)!" << endl; } //拷貝構(gòu)造函數(shù) Person(const Person& p) { //<看這里,形參是對本類類型對象的引用 age = p.age; cout << "拷貝構(gòu)造函數(shù)!" << endl; } //析構(gòu)函數(shù) ~Person() { cout << "析構(gòu)函數(shù)!" << endl; } public: int age; }; void test01() { Person p1(18); //如果不寫拷貝構(gòu)造,編譯器會自動添加拷貝構(gòu)造,并且做淺拷貝操作 Person p2(p1); //Person p2 = Person(p1); cout << "p2的年齡為: " << p2.age << endl; } int main() { test01(); system("pause"); return 0; }
3.2:特性
拷貝構(gòu)造函數(shù)也是特殊的成員函數(shù),其有如下特征:
- 拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個重載形式。
- 拷貝構(gòu)造函數(shù)的參數(shù)只有一個,且必須使用引用傳參,使用傳值方式會引發(fā)無窮遞歸調(diào)用(因為傳值傳參也會調(diào)用拷貝構(gòu)造函數(shù))。
- 如果沒有顯示定義拷貝構(gòu)造函數(shù),系統(tǒng)生成默認的拷貝構(gòu)造函數(shù)。默認的拷貝構(gòu)造函數(shù)對象按內(nèi)存存儲按字節(jié)序完成拷貝,這種通常稱為淺拷貝,或者值拷貝。
??淺拷貝:
- 指向一塊空間,修改數(shù)據(jù)會相互影響。
- 這塊空間析構(gòu)時會釋放兩次,導(dǎo)致程序崩潰。
編譯器生成的默認拷貝函數(shù)已經(jīng)完成了字節(jié)序的值拷貝了,那我們還需要自己實現(xiàn)嗎?我們看一段代碼實例:
typedef int DataType; class SeqList{ public: SeqList(int capacity = 10){ _pData = (DataType*)malloc(capacity * sizeof(DataType)); assert(_pData); _size = 0; _capacity = capacity; } ~SeqList(){ if (_pData){ free(_pData); _pData = nullptr; _capacity = 0; _size = 0; } } private: int * _pData; size_t _size; size_t _capacity; }; int main(){ SeqList Sq1;//調(diào)用默認構(gòu)造函數(shù)初始化Sq1 SeqList Sq2(Sq1); system("pause"); return 0; }
代碼解釋:編譯器先調(diào)用默認構(gòu)造函數(shù)初始化Sq1,然后用類對象Sq1初始化類對象Sq2,我們使用編譯器提供的默認拷貝構(gòu)造函數(shù)實現(xiàn)淺拷貝,這時程序就會出現(xiàn)問題了!
雖然語法編譯能通過:
但是運行程序終究會是報錯的!
我們注意到在初始化Sq1的時候我們在堆區(qū)開辟了地址,如果我們這時淺拷貝初始化Sq2,那么在調(diào)用析構(gòu)函數(shù)的時候會造成對同一塊空間重復(fù)釋放,所以造成程序崩潰!
3.3:拷貝構(gòu)造函數(shù)調(diào)用時機
C++中拷貝構(gòu)造函數(shù)調(diào)用時機通常有三種情況:
1. 使用一個已經(jīng)創(chuàng)建完畢的對象來初始化一個新對象
class Person { public: Person() { cout << "無參構(gòu)造函數(shù)!" << endl; mAge = 0; } Person(int age) { cout << "有參構(gòu)造函數(shù)!" << endl; mAge = age; } Person(const Person& p) { cout << "拷貝構(gòu)造函數(shù)!" << endl; mAge = p.mAge; } //析構(gòu)函數(shù)在釋放內(nèi)存之前調(diào)用 ~Person() { cout << "析構(gòu)函數(shù)!" << endl; } public: int mAge; }; //1. 使用一個已經(jīng)創(chuàng)建完畢的對象來初始化一個新對象 void test01() { Person man(100); //p對象已經(jīng)創(chuàng)建完畢 Person newman(man); //調(diào)用拷貝構(gòu)造函數(shù) } int main() { test01(); system("pause"); return 0; }
2. 值傳遞的方式給函數(shù)參數(shù)傳值
//這里代碼都旨在說明目的,代碼不全! void doWork(Person p1) {//相當于Person p1 = p; // } void test02() { Person p; //無參構(gòu)造函數(shù) doWork(p); }
3. 以值方式返回局部對象
//這里代碼都旨在說明目的,代碼不全! Person doWork2(){ Person p1; cout << (int *)&p1 << endl; return p1; } void test03(){ Person p = doWork2(); cout << (int *)&p << endl; } int main() { test03(); system("pause"); return 0; }
這里可以看作Person p = p1;也相當于調(diào)用拷貝構(gòu)造函數(shù)。
3.4:構(gòu)造函數(shù)調(diào)用規(guī)則
默認情況下,c++編譯器至少給一個類添加3個函數(shù):
- 默認構(gòu)造函數(shù)(無參,函數(shù)體為空)
- 默認析構(gòu)函數(shù)(無參,函數(shù)體為空)
- 默認拷貝構(gòu)造函數(shù),對屬性進行值拷貝
構(gòu)造函數(shù)調(diào)用規(guī)則如下:
- 如果用戶定義有參構(gòu)造函數(shù),c++不在提供默認無參構(gòu)造,但是會提供默認拷貝構(gòu)造。
- 如果用戶定義拷貝構(gòu)造函數(shù),c++不會再提供其他構(gòu)造函數(shù)。
到此這篇關(guān)于C++類與對象深入之構(gòu)造函數(shù)與析構(gòu)函數(shù)詳解的文章就介紹到這了,更多相關(guān)C++類與對象內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++實現(xiàn)LeetCode(158.用Read4來讀取N個字符之二 - 多次調(diào)用)
這篇文章主要介紹了C++實現(xiàn)LeetCode(158.用Read4來讀取N個字符之二 - 多次調(diào)用),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07C語言實現(xiàn)大數(shù)據(jù)文件的內(nèi)存映射機制
這篇文章主要介紹了C語言實現(xiàn)大數(shù)據(jù)文件的內(nèi)存映射機制的相關(guān)資料,需要的朋友可以參考下2017-01-01C++ 詳解數(shù)據(jù)結(jié)構(gòu)中的搜索二叉樹
搜索二叉樹是一種具有良好排序和查找性能的二叉樹數(shù)據(jù)結(jié)構(gòu),包括多種操作,本篇只介紹插入,排序(遍歷),和刪除操作,重點是刪除操作比較復(fù)雜2022-04-04