????????
目錄
一、構造函數
1.1 構造函數的引入
1.2?構造函數的定義和語法
1.2.1 無參構造函數:
1.2.2 帶參構造函數
1.3?構造函數的特性
1.4 默認構造函數
二、析構函數
2.1 析構函數的概念
2.2 特性
????????如果一個類中什么成員都沒有,簡稱為空類。
????????空類中真的什么都沒有嗎?并不是,任何類在什么都不寫時,編譯器會自動生成以下6個默認成員函數。
????????默認成員函數:用戶沒有顯式實現,編譯器會生成的成員函數稱為默認成員函數。
class Date
{};
一、構造函數
1.1 構造函數的引入
????????對于Date類,有如下程序:
#include<iostream>
using namespace std;class Date
{
public:void Init(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;d1.Init(2025, 3, 5)return 0;
}
?????????對于Date類,可以通過Init公有方法給對象設置日期。但有時也會出現忘記Init初始化函數,而直接Push。為避免忘記,也為了方便程序撰寫,所以,C++規定了構造函數。
1.2?構造函數的定義和語法
????????構造函數是一種特殊的成員函數。雖然叫構造,但是其任務不是開空間,而是初始化對象。其特征如下:
- 構造函數名與類名相同;
- 無返回值,不需要寫void,void是空返回值;
- 對象實例化時編譯器自動調用對應的構造函數;
- 構造函數可以重載。
所以Date類構造函數可寫為:
1.2.1 無參構造函數:
#include<iostream>
using namespace std;class Date
{
public: Date(){_year = 1;_month = 1;_day = 1;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Print();//Date d2();//err//Date d1(2024, 1, 27);//err,沒有與參數列表匹配的構造函數實例return 0;
}
運行結果為:?
????????此外,C++規定,無參構造函數變量名后不能加小括號。Date d2();有可能是函數的聲明,返回類型是Date,因為這個地方要寫函數聲明的話,小括號中是要加參數類型的。
1.2.2 帶參構造函數
#include<iostream>
using namespace std;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(2025, 3, 5);return 0;
}
????????構造函數可以函數重載,也可以改寫為全缺省。
#include<iostream>
using namespace std;class Date
{
public:Date(){_year = 1;_month = 1;_day = 1;}Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Print{cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2(2025, 3, 5);return 0;
}
????????語法上,上述兩個函數也可以同時存在,因為函數重載,函數名雖然相同,但參數不同。但是在調用Date d1;時,會產生歧義,編譯器不知道要調用誰,是要調用無參的還是要調用全缺省的。所以一般情況下,我們不這樣寫。
1.3?構造函數的特性
????????1.C++規定,對象定義(實例化)的時候,必須調用構造函數。
????????2.構造函數是默認成員函數,默認成員函數的特征是:我們沒有顯示定義,編譯器會自動生成一個無參的;如果寫了,編譯器就不會生成。
????????但是,當我們這樣寫如下代碼以后,編譯器調用了默認生成的構造函數,但是什么也沒干,沒有初始化。
#include<iostream>
using namespace std;class Date
{
public:void func(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year = 1;int _month = 1;int _day;
};int main()
{Date d1;d1.Print();//A _aa;return 0;
}
運行過程為:
運行結果為:
????????說明了使用編譯器實現的默認構造函數出來初始化的數據是隨機值,不是0。
????????C++98規定,默認生成的構造函數,對于內置類型不做處理;對于自定義類型會直接調用他的默認構造函數。即編譯器對生成默認了構造函數,對int不做處理,但對于自定義成員類型A aa;要調用A的構造函數。
????????內置類型/基本類型? ? ? ?-? ? ? ?int/char/double/指針
????????自定義類型? ? ? ? ? ? ? ? ? ?-? ? ? ?struct/class
????????C++11對這個語法進行補丁,在聲明的位置給缺省值。即不給缺省值,編譯器什么都不管;給缺省值,就默認生成了構造函數,用缺省值去完成初始化。在類中,默認了Date(){int _year = 1;int _month = 1;};
1.4 默認構造函數
#include<iostream>
using namespace std;class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year = 1;int _month = 1;int _day;
};int main()
{Date d1;return 0;
}
????????當我們運行時發現上述代碼報錯:此處編譯報錯,提示沒有默認構造函數可以用。
????????很多人經常理解默認構造函數就是編譯器生成的那一個。編譯器默認生成的構造函數是默認構造函數,但只是其中之一。
????????無參的構造函數和全缺省構造函數也被稱為默認構造函數,且默認構造函數有且只能有一個。?一般情況下,建議優選全缺省構造函數。
總結:不需要傳參就可以調用的構造函數,都可以叫默認構造函數。
????????所以,上述代碼中,沒有無參,也沒有全缺省的默認構造函數,此時就需要編譯器就要生成一個默認構造函數。
????????但是編譯器默認生成的構造函數是有條件的,基于默認成員函數的特性:即沒有寫顯示定義的構造函數,編譯器才會自動生成一個無參的默認構造函數;一旦用戶顯示定義,編譯器就不會再生成。
? ? ? ? 為了使上述程序能夠通過,需要函數重載,顯式定義一個默認構造函數。
#include<iostream>
using namespace std;class Date
{
public:Date(){_year = 1;_month = 1;_day = 1;}Date(int year, int month, int day){_year = year;_month = month;_day = day;}Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year = 1;int _month = 1;int _day;
};int main()
{Date d1;return 0;
}
二、析構函數
2.1 析構函數的概念
????????內存泄漏是不會報錯的,所以經常會忘記destory,所以C++就有了析構函數。
????????析構函數:與構造函數功能相反,析構函數不是完成對對象本身的銷毀,局部對象銷毀工作是由編譯器完成的。而對象在銷毀時會自動調用析構函數,完成對象中資源的清理工作。相當于destory,把動態開辟的數組空間free掉,不清理的話會出現內存泄漏。所以:構造函數完成的不是創建,析構函數完成的也不是銷毀。
????????在函數名前加~,在C語言中,~表示按位取反,所以選用這個符號就表示和構造函數的功能是相反的。
2.2 特性
????????析構函數是特殊的成員函數,其特性如下:
- 析構函數名是類名前加字符“~”;
- 沒有參數也沒有返回值;
- 一個類只能有一個析構函數。若未顯式定義,系統會自動生成默認的析構函數。注意:析構函數不能重載;
- 對象生命周期結束時,C++編譯系統系統自動調用析構函數。
????????第4點類似于構造函數,構造函數在實例化的時候會自動調用。
#include<iostream>
using namespace std;class Time
{
public:~Time(){cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本類型(內置類型)int _year = 1970;int _month = 1;int _day = 1;// 自定義類型Time _t;
};int main()
{Date d;return 0;
}
????????程序運行結束后輸出:~Time()。
????????在main方法中根本沒有直接創建Time類的對象,為什么最后會調用Time類的析構函數?
????????因為:main方法中創建了Date對象d,而d中包含4個成員變量,其中_year,_month,_day三個是內置類型成員,銷毀時不需要資源清理,最后系統直接將其內存回收即可;而_t是Time類對象,所以在d銷毀時,要將其內部包含的Time類的_t對象銷毀,所以要調用Time類的析構函數。
????????但是:main函數中不能直接調用Time類的析構函數,實際要釋放的是Date類對象,所以編譯器會調用Date類的析構函數,而Date沒有顯式提供,則編譯器會給Date類生成一個默認的析構函數,目的是在其內部調用Time類的析構函數,即當Date對象銷毀時,要保證其內部每個自定義對象都可以正確銷毀main函數中并沒有直接調用Time類析構函數,而是顯式調用編譯器為Date類生成的默認析構函數。
????????注意:創建哪個類的對象則調用該類的析構函數,銷毀那個類的對象則調用該類的析構函數。
????????如果類中沒有申請資源時,析構函數可以不寫,直接使用編譯器生成的默認析構函數,比如
Date類;有資源申請時,一定要寫,否則會造成資源泄漏,比如Stack類。
#include<iostream>
using namespace std;class Stack
{
public: Stack(int capacity = 3){cout << "Stack(int capacity = 3)" << endl;_arry = (int*)malloc(capacity * sizeof(4));if(_arry == NULL){perror("malloc");}_capacity = capacity;}void Push(int x){_arry[_size] = x;_size++;}~Stack(){cout << "~Stack()" << endl;free(_arry);_arry = NULL;_size = 0;_capacity = 0;}private:int* _arry;int _capacity = 0;int _size = 0;
};//注意必須要有分號class MyQueue
{
private:Stack st1;Stack st2;int _size = 0;
};//注意必須要有分號int main()
{MyQueue q;return 0;
}
運行結果為:?
2.3 析構順序? ? ? ??
????????銷毀順序為:局部對象(后定義的先析構)->局部的靜態->全局對象(后定義的先析構)。程序證明如下:
class Date
{
public:Date(int year = 1){_year = year;}~Date(){cout << "~Date()->" << _year << endl;}
private:int _year ;int _month;int _day;
};void func()
{Date d3(3);static Date d4(4);
}Date d5(5);
static Date d6(6);int main()
{Date d1(1);Date d2(2);func();return 0;
}
????????static Date d3(3);為局部靜態,和上邊兩個的存儲區域不同,第3是存在靜態區的,雖然定義在了局部,但是生命周期是全局的。在main函數結束以后才會銷毀。所以在這之前要先把main函數中的局部變量先銷毀。