【C++要笑着学】类的默认成员函数详解 | 构造函数 | 析构函数 | 构造拷贝函数
?🔥 🔥 🔥 🔥 🔥??火速猛戳訂閱?👉??《C++要笑著學》? ?👈?趣味教學博客?🔥 🔥 🔥 🔥 🔥
? ?[ 本篇博客熱榜最高排名:7?]
寫在前面
?朋友們好啊,今天終于更新了。我是檸檬葉子C,本章將繼續(xù)講解C++中的面向對象的知識點,本篇主要講解默認成員函數(shù)中的構造函數(shù)、析構函數(shù)和拷貝構造函數(shù)。還是和以前一樣,我們將由淺入深地去講解,以 "初學者" 的角度去探索式地學習。會一步步地推進講解,而不是直接把枯燥的知識點倒出來,應該會有不錯的閱讀體驗。如果覺得不錯,可以 "一鍵三連" 支持一下博主!你們的關注就是我更新的最大動力!Thanks ? (・ω・)ノ
Ⅰ.? 默認成員函數(shù)
如果一個類中什么成員都沒有,我們稱之為 "空類" 。
? 但是空類中真的什么都沒有嗎?
?并不是的……
類有六個默認成員函數(shù),特殊的點非常多,后面我們會壹壹學習。
?對于默認成員函數(shù),如果我們不主動實現(xiàn),編譯器會自己生成一份。
? 他們有什么用呢?舉個例子:
比如我們在上一章里舉過的一個 Stack 的例子,
如果需要初始化和清理,"構造函數(shù)" 和 "析構函數(shù)" 就可以幫助我們完成。
構造函數(shù)就類似于 Init,而析構函數(shù)就類似于 Destroy。
還是和以前一樣,我們將先由淺入深地進行學習,我們先從 "構造函數(shù)" 開始講起。
Ⅱ. 構造函數(shù)
0x00 引入
打開宇宙第一編輯器,一起敲一敲看看 ~
📚 為了能夠更好地講解,我們來寫一個簡單的日期類,通過日期類來講解。
💬 Date.cpp
#include <iostream>class Date { public:void SetDate(int year, int month, int day) {_year = year;_month = month;_day = day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);}private:int _year;int _month;int _day; };int main(void) {Date d1;d1.SetDate(2022, 3, 8);d1.Print();Date d2;d2.SetDate(2022, 3, 12);d2.Print();return 0; }💡 對于 Date 類,我們可以通過我們寫的成員函數(shù)?SetDate 給對象設置內容。
?但是每次創(chuàng)建對象都要調用這個 SetDate ,是不是太雞兒煩了?
? 那有沒有什么辦法能在創(chuàng)建對象時,自動將我們要傳遞的內容放置進去呢?
?有!下面我們來隆重介紹一下 構造函數(shù)!
0x01 構造函數(shù)的概念
📚 構造函數(shù)是一個特殊的成員函數(shù),名字與類名相同,創(chuàng)建類類型對象時由編譯器自動調用。
能夠保證每個數(shù)據(jù)成員都有一個合適的初始值,并且在對象的生命周期內只調用一次。
?構造函數(shù)的意義:能夠保證對象被初始化。
構造函數(shù)是特殊的成員函數(shù),主要任務是初始化,而不是開空間。(雖然構造函數(shù)的名字叫構造)
0x02 構造函數(shù)的特性
📚 構造函數(shù)是特殊的成員函數(shù),主要特征如下:
① 構造函數(shù)的函數(shù)名和類名是相同的
② 構造函數(shù)無返回值
③ 構造函數(shù)可以重載
③?會在對象實例化時自動調用對象定義出來。
比如下面的代碼只要 ?就會自動調用,保證了對象一定是被初始化過的。
?我們直接來看看它是怎么用的!?
💬 構造函數(shù)的用法:
#include <iostream>class Date { public:/* 無參構造函數(shù) */Date() {_year = 0;_month = 1;_day = 1;}/* 帶參構造函數(shù) */Date(int year, int month, int day) {_year = year;_month = month;_day = day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);}private:int _year;int _month;int _day; };int main(void) {Date d1; // 對象實例化,此時觸發(fā)構造,調用無參構造函數(shù)d1.Print();Date d2(2022, 3, 9); // 對象實例化,此時觸發(fā)構造,調用帶參構造函數(shù)d2.Print();return 0; }🚩 運行結果如下:
?🔑 解讀:不給參數(shù)時就會調用無參構造函數(shù),給參數(shù)則會調用帶參構造函數(shù)。
📌 注意事項:
① 構造函數(shù)是特殊的,不是常規(guī)的成員函數(shù),不能直接調??。
#include <iostream>class Date { public:Date(int year = 1, int month = 0, int day = 0) {_year = year;_month = month;_day = day;} private:int _year;int _month;int _day; };int main(void) {Date d1;d1.Date(); // 不能這么去調,構造函數(shù)是特殊的,不是常規(guī)的成員函數(shù)!return 0; }🚩 運行結果:(報錯)
② 如果通過無參構造函數(shù)創(chuàng)建對象,對象后面不用跟括號,否則就成了函數(shù)聲明。
#include <iostream>class Date { public:Date(int year = 1, int month = 0, int day = 0) {_year = year;_month = month;_day = day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);} private:int _year;int _month;int _day; };int main(void) {//帶參這么調:加括號(),在括號中加參數(shù)列表Date d2(2022, 3, 9);Date d3(); // 這樣可以嗎? // 既然代參的調用加括號,在括號中加參數(shù)列表。// 那我不帶參,可不可以加括號呢?? 仍然不可以。// 這個對象實際上沒有被定義出來,這里會報錯。 // 編譯器不會識別,所以不傳參數(shù)就老老實實地 // Date d3; 不要 Date d3(); // 主要是編譯器沒法識別,所以這里記住不能這么干就行了。return 0; }③ 這里如果調用帶參構造函數(shù),我們需要傳遞三個參數(shù)(這里我們沒設缺省) 。
④?如果你沒有自己定義構造函數(shù)(類中未顯式定義),C++ 編譯器會自動生成一個無參的默認構造函數(shù)。當然,如果你自己定義了,編譯器就不會幫你生成了。
#include <iostream>class Date { public:/* 如果用戶顯式定義了構造函數(shù),編譯器將不再生成Date(int year, int month, int day) {_year = year;_month = month;_day = day;}*/void Print() {printf("%d-%d-%d\n", _year, _month, _day);}private:int _year;int _month;int _day; };int main(void) {Date d1; // 這里調用的是默認生成的無參的構造函數(shù)d1.Print();return 0; }🚩 運行結果如下:
🔑 沒有定義構造函數(shù),對象也可以創(chuàng)建成功,因此此處調用的是編譯器默認生成的構造函數(shù)。
0x03 默認構造函數(shù)
?無參構造函數(shù)、全缺省構造函數(shù)都被稱為默認構造函數(shù)。
并且默認構造函數(shù)只能有一個!
class Date { public:/* 全缺省構造函數(shù) - 默認構造函數(shù) */Date(int year = 1970, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}private:int _year;int _month;int _day; };📌 注意事項:
① 無參構造函數(shù)、全缺省構造函數(shù)、我們沒寫編譯器默認生成的無參構造函數(shù),都可以認為是默認成員函數(shù)。
② 語法上無參和全缺省可以同時存在,但如果同時存在會引發(fā)二義性:
#include <iostream>class Date { public:Date() {_year = 1970;_month = 1;_day = 1;}Date(int year = 1970, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}private:int _year;int _month;int _day; };int main(void) {Date d1; ?return 0; }🚩 運行結果如下:(報錯)
🔑 解讀:無參的構造函數(shù)和全缺省的構造函數(shù)都成為默認構造函數(shù),并且默認構造參數(shù)只能有一個,語法上他們兩個可以同時存在,但是如果有對象定義去調用就會報錯。
??推薦實現(xiàn)全缺省或者半缺省,因為真的很好用:
#include <iostream>class Date {public:/* 全缺省 */Date(int year = 0, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);} private:int _year;int _month;int _day; };int main(void) {Date d1; // 如果不傳,就是缺省值Date d2(2022, 1, 15);Date d3(2009);Date d4(2012, 4);d1.Print(); // 0-1-1d2.Print(); // 2022-1-15d3.Print(); // 2009-1-1d4.Print(); // 2012-4-1return 0; }🚩 運行結果如下:
0x04 構造函數(shù)的特性的測試
📚 任何一個類的默認構造函數(shù),只有三種:
① 無參的構造函數(shù)
② 全缺省的構造函數(shù)
③ 我們不寫,編譯器自己生成的構造函數(shù)
💬 如果你沒有自己定義構造函數(shù)(類中未顯式定義),
C++ 編譯器會自動生成一個無參的默認構造函數(shù)。
當然,如果你自己定義了,編譯器就不會幫你生成了。
#include <iostream>class Date { public:// 讓編譯器自己生成一個void Print() {printf("%d-%d-%d\n", _year, _month, _day);}private:int _year;int _month;int _day; };int main(void) {Date d1; // 這里調用的是默認生成的無參的構造函數(shù)d1.Print();return 0; }🚩 運行結果如下:
??好了,我們來好好探討探討這個問題!
在我們不是先構造函數(shù)的情況下,編譯器生成的默認構造函數(shù)。
似乎這看起來沒有什么鳥用啊,這不就是一堆隨機值嘛……
d1 對象調用了編譯器生成的默認函數(shù),但 d1 對象 year / month / day 依舊是隨機值,
也就是說這里編譯器生成的默認構造函數(shù)好像并沒有什么卵用。
🔑 解答:C++ 把類型分成內置類型(基本類型)和自定義類型。
內置類型就是語法已經(jīng)定義好的類型:如 int / char...,
自定義類型就是我們使用 class / struct / union / 自己定義的類型。
💬 看看下面的程序,就會發(fā)現(xiàn)。編譯器生成默認的構造函數(shù)會對自定類型成員 aa?調用的它的默認成員函數(shù):
#include <iostream> using namespace std;class Time { public:Time(){cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;} private:int _hour;int _minute;int _second; };class Date { private:// 基本類型(內置類型)int _year;int _month;int _day;// 自定義類型Time _t; };int main() {Date d;return 0; }🚩 運行結果如下:?
💬 測試:對自定義類型處理,會調用默認構造函數(shù)(不用參數(shù)就可以調的函數(shù))
#include<iostream> using namespace std;class A { public:// 默認構造函數(shù)(不用參數(shù)就可以調的)A() {cout << " A() " << endl;_a = 0;}private:int _a; };class Date { public: private:int _year;int _month;int _day;A _aa; // 對自定義類型處理,此時會調用默認構造函數(shù) A() {...} };int main(void) {Date d1;return 0; }🚩 運行結果如下:
C++?里面把類型分為兩類:內置類型(基本類型)和 自定義類型。
C++ 規(guī)定:我們不寫編譯器默認生成構造函數(shù),對于內置類型的成員變量,不做初始化處理。
但是對于自定義類型的成員變量會去調用它的默認構造函數(shù)(不用參數(shù)就可以調的)初始化。
如果沒有默認構造函數(shù)(不用參數(shù)就可以調用的構造函數(shù))就會報錯!
💬 為了驗證,這里我們故意寫個帶參的默認構造函數(shù),讓編譯器不默認生成:
#include <iostream> using namespace std;class A { public:// 如果沒有默認的構造函數(shù),會報錯。A(int a) { // 故意給個參cout << " A() " << endl;_a = 0;} private:int _a; };class Date { public: private:// 如果沒有默認構造函數(shù)就會報錯int _year;int _month;int _day;A _aa; };int main(void) {Date d1;return 0; }🚩 運行結果如下(報錯)
💬 我們不寫,讓編譯器默認生成一個:
#include<iostream> using namespace std;class A { public:// 讓編譯器默認生成 private:int _a; };class Date { public: private:int _year;int _month;int _day;A _aa; };int main(void) {Date d1;return 0; }?是隨機值沒錯,但是這是一種對自定義類型的 "處理" 。
?這里說個題外話,個人認為 C++里,我們不寫構造函數(shù)編譯器會默認生成的這個特性設計得不好(狗頭保命)……因為沒有對內置類型和自定義類型統(tǒng)一處理,不處理內置類型成員變量,只處理自定義類型成員變量。
Ⅲ. 析構函數(shù)
0x00 引入
?通過前面構造函數(shù)的學習,我們知道了一個對象是怎么來的了,
? 那一個對象又是怎么沒的呢?既然構造函數(shù)的本質是初始化,那清理的工作交給誰來干呢?
💡 交給專門擦屁股的 —— 析構函數(shù)!
以前我們玩數(shù)據(jù)結構的時候經(jīng)常忘記調用 destroy 函數(shù),但是現(xiàn)在我們有析構函數(shù)了!!!
多么振奮人心啊!話不多說讓我們開始講解!!!?
0x01 析構函數(shù)的概念
?析構函數(shù)與構造函數(shù)的功能相反。
構造函數(shù)是特殊的成員函數(shù),主要任務是初始化,而不是開空間;
析構函數(shù)也一樣,主要任務是清理,而不是做對象銷毀的工作。
(局部對象銷毀工作是由編譯器完成的)
📚 概念:對象在銷毀時會自動調用析構函數(shù),完成對象的一些資源清理工作。
0x02 析構函數(shù)的特性
?構造函數(shù)是特殊的成員函數(shù),主要特征如下:
① 析構函數(shù)名是在類名前面加上字符?
② 析構函數(shù)既沒有參數(shù)也沒有返回值(因為沒有參數(shù),所以也不會構成重載問題)
③ 一個類的析構函數(shù)有且僅有一個(如果不寫系統(tǒng)會默認生成一個析構函數(shù))
④ 析構函數(shù)在對象生命周期結束后,會自動調用。
(和構造函數(shù)是對應的構造函數(shù)是在對象實例化的時候自動調用)
💬 為了演示自動調用,我們來讓析構函數(shù)被調用時 "吱" 一聲:
#include <iostream> using namespace std;class Date { public:Date(int year = 1, int month = 0, int day = 0) {_year = year;_month = month;_day = day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);}~Date() {// Date 類沒有資源需要清理,所以Date不實現(xiàn)析構函都是可以的cout << "~Date() 吱~ " << endl; // 測試一下,讓他吱一聲}private:int _year;int _month;int _day; };int main(void) {Date d1;Date d2(2022, 3, 9);return 0; }🚩 運行結果:
額,之前舉得日期類的例子沒法很好地展示析構函數(shù)的 "魅力" ……
就像本段開頭說情景,我們拿 Stack 來舉個例子,這就很貼切了。
我們知道,棧是需要 destroy 清理開辟的內存空間的。
?這里我們讓析構函數(shù)來干這個活,簡直美滋滋!
💬 析構函數(shù)的用法:
#include<iostream> #include<stdlib.h> using namespace std;typedef int StackDataType; class Stack { public:/* 構造函數(shù) - StackInit */Stack(int capacity = 4) { // 這里只需要一個capacity就夠了,默認給4(利用缺省參數(shù))_array = (StackDataType*)malloc(sizeof(StackDateType) * capacity);if (_array == NULL) {cout << "Malloc Failed!" << endl;exit(-1);}_top = 0;_capacity = capacity;}/* 析構函數(shù) - StackDestroy */~Stack() { // 這里就用的上析構函數(shù)了,我們需要清理開辟的內存空間(防止內存泄漏)free(_array);_array = nullptr;_top = _capacity = 0;}private:int* _array;size_t _top;size_t _capacity; };int main(void) {Stack s1;Stack s2(20); // s2 棧 初始capacity給的是20(可以理解為"客制化")return 0; }🔑 解讀:我們在設置棧的構造函數(shù)時,定義容量 capacity 時利用缺省參數(shù)默認給個4的容量,這樣用的時候默認就是4,如果不想要4可以自己傳。
如此一來,就可以保證了棧被定義出來就一定被初始化,用完后會自動銷毀。以后就不會有忘記調用 destroy 而導致內存泄露的慘案了,這里的析構函數(shù)就可以充當銷毀的作用。
? 問一個比較有意思的問題,這里是先析構 s1 還是先析構 s2?
?既然都這樣問了,應該是先析構 s2 了 ~
析構的順序在局部的棧中是相反的,棧幀銷毀清理資源時 s2 先清理,然后再清理 s1 。
(不信的話可以去監(jiān)視一下 this 觀察下成員變量)
0x03?析構函數(shù)的特性的測試
又到了測試環(huán)節(jié),上號!
我們知道,如果沒寫析構函數(shù)編譯器會自動生成一個。
那生成的析構函數(shù)會做什么事情呢?它會幫我們 destroy 嘛?
?想屁吃?哪有這種好事。
如果我們不寫默認生成的析構函數(shù),結果和構造函數(shù)類似,
對于自定義類型的成員變量不作處理,對于自定義類型的成員變量會去調用它的析構函數(shù)。
#include<iostream> #include<stdlib.h> using namespace std; typedef int StackDataType;class Stack { public:Stack(int capacity = 4) {_array = (StackDataType*)malloc(sizeof(int*) * capacity);if (_array == NULL) {cout << "Malloc Failed!" << endl;exit(-1);}_top = 0;_capacity = capacity;}// ~Stack() {// free(_array);// _array = nullptr;// _top = _capacity = 0;// }private:int* _array;size_t _top;size_t _capacity; };int main(void) {Stack s1;Stack s2(20);return 0; }難道就不能幫我把這些事都干了嗎?幫我都銷毀掉不就好了?
不不不,舉個最簡單的例子,迭代器,析構的時候是不釋放的,因為不需要他來管,
所以默認不對內置類型處理是正常的,萬一誤殺了怎么辦,對吧。?
有人可能又要說了,這么一來默認生成的析構函數(shù)不就沒有用了嗎?
有用!他對內置類型的成員類型不作處理,會在一些情況下非常的有用!
比如說:???兩個棧實現(xiàn)一個隊列(LeetCode232) ,用C++可以非常的爽。
💬?自定義類型的成員變量調用它的析構函數(shù):
#include <iostream> using namespace std;class String { public:String(const char* str = "jack") {_str = (char*)malloc(strlen(str) + 1);strcpy(_str, str);}~String() {cout << "~String()" << endl;free(_str);} private:char* _str; };class Person { private:String _name;int _age; };int main() {Person p;return 0; }🚩 運行結果如下:
Ⅳ.? 拷貝構造函數(shù)
0x00 引入
我們在創(chuàng)建對象的時候,能不能創(chuàng)建一個與某一個對象一模一樣的新對象呢?
Date d1(2022, 3, 9); d1.Print();Date d2(d1); // 照著d1的模子做一個d2 d2.Print();當然可以,這時我們就可以用拷貝構造函數(shù)。
0x01 拷貝構造函數(shù)的概念
📚 拷貝構造函數(shù):只有單個形參,該形參是對本類類型對象的引用(一般常用 const 修飾),在用已存在的類類型對象創(chuàng)建新對象時由編譯器自動調用。
0x02 拷貝構造函數(shù)的特性
它也是一個特殊的成員函數(shù),所以他符合構造函數(shù)的一些特性:
① 拷貝構造函數(shù)是構造函數(shù)的一個重載形式。函數(shù)名和類名相同,沒有返回值。
② 拷貝構造函數(shù)的參數(shù)只有一個,并且必須要使用引用傳參!
? ? ??使用傳值方式會引發(fā)無窮遞歸調用!
💬 拷貝構造函數(shù)的用法:
#include <iostream>class Date { public:Date(int year = 0, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}/* Date d2(d1); */Date(Date& d) { // 這里要用引用,否則就會無窮遞歸下去_year = d._year;_month = d._month;_day = d._day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);}private:int _year;int _month;int _day; };int main(void) {Date d1(2022, 3, 9);Date d2(d1); // 拷貝復制// 看看拷貝成功沒d1.Print();d2.Print();return 0; }🚩 運行結果如下:
? 為什么必須使用引用傳參呢?
調用拷貝構造,需要先穿參數(shù),傳值傳參又是一個拷貝構造。
調用拷貝構造,需要先穿參數(shù),傳值傳參又是一個拷貝構造。
調用拷貝構造,需要先穿參數(shù),傳值傳參又是一個拷貝構造。
……
一直在傳參這里出不去了,所以這個遞歸是一個無窮無盡的。
💬 我們來驗證一下:
error:?invalid?constructor;?you?probably?meant?'Date?(const?Date&)'
這里不是加不加 const 的問題,而是沒有用引用導致的問題。
不用引用,他就會在傳參那無線套娃遞歸。至于為什么我們繼續(xù)往下看。
💬 拷貝構造函數(shù)加 const :
如果函數(shù)內不需要改變,建議把 const 也給它加上
class Date { public:Date(int year = 0, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}/* Date d2(d1); */Date(const Date& d) { // 如果內部不需要改變,建議加上const_year = d._year;_month = d._month;_day = d._day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);}private:int _year;int _month;int _day; };第一個原因:怕出錯,萬一你一不小心寫反了怎么辦?
/* Date d2(d1); */ Date(Date& d) {d._year = _year;d._month = _month;d._day = _day; }這樣會產(chǎn)生一個很詭異的問題,這一個可以被編譯出來的 BUG ,結果會變?yōu)殡S機值。
所以,這里加一個 const 就安全多了,這些錯誤就會被檢查出來了。
第二個原因:以后再講,因為涉及一些臨時對象的概念。
🔺?反正,不想深究的話就記住:如果函數(shù)體內不需要改變,建議把 const 加上就完事了。
0x03 關于默認生成的拷貝構造
?這里比較特殊,我們單獨領出來講。?
📚 默認生成拷貝構造:
① 內置類型的成員,會完成按字節(jié)序的拷貝(把每個字節(jié)依次拷貝過去)。
② 自定義類型成員,會再調用它的拷貝構造。
💬 拷貝構造我們不寫生成的默認拷貝構造函數(shù),對于內置類型和自定義類型都會拷貝處理。但是處理的細節(jié)是不一樣的,這個跟構造和析構是不一樣的!
#include<iostream> using namespace std;class Date {public:Date(int year = 0, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}// Date(Date& d) {// _year = d._year;// _month = d._month;// _day = d._day;// }void Print() {printf("%d-%d-%d\n", _year, _month, _day);} private:int _year;int _month;int _day; };int main(void) {Date d1(2002, 4, 8);// 拷貝復制Date d2(d1);// 沒有寫拷貝構造,但是也拷貝成功了d1.Print();d2.Print();return 0; }🚩 運行結果如下:
🔑 他這和之前幾個不同了,這個他還真給我解決了。
所以為什么要寫拷貝構造?寫他有什么意義?沒有什么意義。
?默認生成的一般就夠用了!
?當然,這并不意味著我們都不用寫了,有些情況還是不可避免要寫的
比如實現(xiàn)棧的時候,棧的結構問題,導致這里如果用默認的?拷貝構造,會翻車。
按字節(jié)把所有東西都拷過來會產(chǎn)生問題,如果?Stack?st1?拷貝出另一個?Stack?st2(st1)?
會導致他們都指向那塊開辟的內存空間,導致他們指向的空間被析構兩次,導致程序崩潰
然而問題不止這些……
?其實這里的字節(jié)序拷貝是淺拷貝,下面幾章我會詳細講一下深淺拷貝,這里的深拷貝和淺拷貝先做一個大概的了解。
🔺 總結:對于常見的類,比如日期類,默認生成的拷貝構造能用。但是對于棧這樣的類,默認生成的拷貝構造不能用。
Ⅴ.? 總結
默認成員函數(shù)有六只,本篇只介紹了三只,剩下的我們后面講。
類和對象部分知識很重要,所以我們來做一個簡單的總結 ~
0x00 構造函數(shù)?
初始化,在對象實例化時候自動調用,保證實例化對象一定被初始化。
構造函數(shù)是默認成員函數(shù),我們不寫編譯器會自己生成一份,我們寫了編譯器就不會生成。
我們不寫內置類型成員變量不處理。
對于內置類型成員變量不處理。
對于自定義類型的成員變量會調用它的默認構造函數(shù)。
// 我們需要自己實現(xiàn)構造函數(shù) class Date {int _year;int _month;int _day; };// 我們不需要自己實現(xiàn)構造函數(shù),默認生成的就可以 class MyQueue {Stack _pushST;Stack _popST; };0x01 析構函數(shù)
?完成對象中自愿的清理。如果類對象需要資源清理,才需要自己實現(xiàn)析構函數(shù)。
析構函數(shù)在對象生命周期到了以后自動調用,如果你正確實現(xiàn)了析構函數(shù),保證了類對象中的資源被清理。
什么時候生命周期到了?如果是局部變量,出了作用域。全局和靜態(tài)變量,整個程序結束。
我們不寫編譯器會默認生成析構函數(shù),我們實現(xiàn)了,編譯器就不會實現(xiàn)了。
對于內置類型成員變量不處理。
對于自定義類型的成員變量會調用它的析構函數(shù)。
// 沒有資源需要清理,不徐需要自己實現(xiàn)析構函數(shù) class Date {int _year;int _month;int _day; };// 需要自己實現(xiàn)析構函數(shù),清理資源。 class Stack {int* _a;int _top;int _capacity; };0x02 拷貝構造
使用同類型的對象去初始化實例對象。
參數(shù)必須是引用!不然會導致無窮遞歸。
如果我們不實現(xiàn),編譯器會默認生成一份默認的拷貝構造函數(shù)。
默認生成的拷貝構造:
① 內置類型完成按子繼續(xù)的值拷貝。 —— 淺拷貝
② 自定義類型的成員變量,會去調用它的拷貝構造。
// 不需要自己實現(xiàn),默認生成的拷貝構造,完成淺拷貝就能滿足需求 class Date {int _year;int _month;int _day; };// 需要自己實現(xiàn),因為默認生成的淺拷貝不能滿足需求。 // 我們需要自己實現(xiàn)深拷貝的拷貝構造,深拷貝我們后面會用專門的章節(jié)去講解。 class Stack {int* _a;int _top;int _capacity; }; #include <iostream> using namespace std;class Date { public:Date(int year = 1, int month = 0, int day = 0) {_year = year;_month = month;_day = day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);}~Date() {cout << "&Date()" << endl;}private:int _year;int _month;int _day; };int main(void) {Date d1;d1.Print();Date d2(2002);d2.Print();Date d3(2022, 3);d3.Print();Date d4(2022, 3, 9);d4.Print();return 0; }?🚩 運行結果如下:
參考資料:
Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .
百度百科[EB/OL]. []. https://baike.baidu.com/.
比特科技. C++[EB/OL]. 2021[2021.8.31]. .
📌 筆者:王亦優(yōu)
📃 更新: 2022.3.15
? 勘誤:Star丶北辰:拿棧舉例時malloc空間 sizeof 有誤【已修正】
📜 聲明: 由于作者水平有限,本文有錯誤和不準確之處在所難免,本人也很想知道這些錯誤,懇望讀者批評指正!
本章完。
總結
以上是生活随笔為你收集整理的【C++要笑着学】类的默认成员函数详解 | 构造函数 | 析构函数 | 构造拷贝函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 中国富人的身影:世界最大楼市泡沫在加拿大
- 下一篇: SQL中的表连接及子查询