10.類和對象
C語言結構體中只能定義變量,在C++中,結構體內不僅可以定義變量,也可以定義函數。
C++中定義類(結構體)的語法:
class className
{// 類體:由成員函數和成員變量組成}; ?// 一定要注意后面的分號
class為定義類的關鍵字,ClassName為類的名字,{}中為類的主體,注意類定義結束時后面分號不能省略。 類體中內容稱為類的成員:類中的變量稱為類的屬性或成員變量; 類中的函數稱為類的方法或者成員函數。(也可以使用struct定義類,但是有一點區別,下面會說到)
類的兩種定義方式:
1.聲明和定義全部放在類體中,需注意:成員函數如果在類中定義,編譯器可能會將其當成內聯函數處理。
2. 類聲明放在.h文件中,成員函數定義放在.cpp文件中,注意:成員函數名前需要加類名::
為方便書寫這里使用第一種,工程中使用第二種較好。
類的訪問限定符及封裝
C++實現封裝的方式:用類將對象的屬性與方法結合在一塊,讓對象更加完善,通過訪問權限選 擇性的將其接口提供給外部的用戶使用。
說明:
1. public修飾的成員在類外可以直接被訪問
2. protected和private修飾的成員在類外不能直接被訪問(此處protected和private是類似的)
3. 訪問權限作用域從該訪問限定符出現的位置開始直到下一個訪問限定符出現時為止
4. 如果后面沒有訪問限定符,作用域就到 } 即類結束。
5. class的默認訪問權限為private,struct為public(因為struct要兼容C)如下:struct的函數都是公有的,class從{開始到public之前的函數都是私有的。
注意:訪問限定符只在編譯時有用,當數據映射到內存后,沒有任何訪問限定符上的區別(主波沒理解,后續理解了再更新)
如何計算類對象的大小
如下面這個日期類中既可以有成員變量,又可以有成員函數,那么一個類的對象中包含了什么?如何計算 一個類的大小?
只保存成員變量,成員函數存放在公共的代碼段
從下圖可以得知,只計算成員變量的內存(按照內存對齊的方式計算:寫文章-CSDN創作中心),注意空類,空類比較特殊,編譯器給了空類一個字節來唯一標識這個類的對象。
11.this指針
先觀察下面這個代碼,d1和d2是如何調用Init和print函數完成初始化和打印的呢?
其實是存在一個隱藏的this指針,如下代碼的注釋:
class Date
{
public:void print(); // ==>void Date::print(Date *this)void Init(int year,int month,int day);// ==>void Date::Init(Date *this,int year=0, int month=1, int day=1)private:int _year;int _month;int _day;
};void Date::print() // void Date::print()==void Date::print(Date *this)
{// ==cout <<this->_year << '-' <<this->_month << '-' << this->_day << endl;cout << _year << '-' << _month << '-' << _day << endl;
}void Date::Init(int year=0, int month=1, int day=1) // ==>void Date::Init(Date *this,int year=0, int month=1, int day=1)
{_year = year;_month = month;_day = day;//this->_year = year;//this->_month = month;//this->_day = day;
}int main()
{Date d1, d2;d1.Init(2025, 8, 9); // ==> d1.Init(&d1,2025, 8, 9);d2.Init(2025, 8, 10);// ==> d2.Init(&d2,2025, 8, 10);d1.print(); // ==> d1.print(&d1);d2.print(); // ==> d2.print(&d2);return 0;
}
12.構造函數
構造函數是一個特殊的成員函數(作用是初始化對象的成員變量),名字與類名相同,創建對象時由編譯器自動調用,以保證每個成員變量都有一個合適的初始值,并且在對象整個生命周期內只調用一次。前面寫的日期類是使用了 Init 函數進行初始化,但是每創建一個對象就得調用這個函數才能初始化,現在使用構造函數就不需要自己手動調用函數初始化創建的對象。
特性:
1. 函數名與類名相同。
2. 無返回值。
3. 創建對象時編譯器自動調用對應的構造函數。
4. 構造函數可以重載。
如下日期類作為例子:
class Date
{
public:// 1.無參構造函數Date(){_year = 0;_month = 1;_day = 1;}// 2.帶參構造函數Date(int year, int month, int day) {_year = year;_month = month;_day = day;}// 參數可以全缺省//Date(int year=0, int month=1, int day=1) //{// _year = year;// _month = month;// _day = day;//}void print() // void Date::print()==void Date::print(Date *this){cout << _year << '-' << _month << '-' << _day << endl;}private:int _year;int _month;int _day;
};void TestDate()
{Date d1; // 調用無參構造函數// Date d1();不能加括號,否則就成了函數聲明d1.print();Date d2(2015, 1, 1); // 調用帶參的構造函數d2.print();
}
int main()
{TestDate();return 0;
}
13.析構函數
與構造函數功能相反,析構函數不是完成對對象本身的銷毀,局部對象銷毀工作是由 編譯器完成的。而對象在銷毀時會自動調用析構函數,完成對象中資源的清理工作。比如:在棧中我們在申請了堆上面申請了空間存儲棧的元素,此時調析構函數就是清理這部分空間,不在像棧那樣需要寫一個銷毀函數destro()。
特性:
1. 析構函數名是在類名前加上字符 ~。
2. 無參數無返回值類型。
3. 一個類只能有一個析構函數。若未顯式定義,系統會自動生成默認的析構函數。注意:析構函數不能重載
4. 對象生命周期結束時,C++編譯系統系統自動調用析構函數。(后創建的對象,在程序結束時會先調用它的析構函數)下圖A類先創建兩個對象接著B類創建兩個對象,main函數結束時,先調用B類對象的析構函數在調用A類對象的析構函數。
如果類中沒有申請資源時,析構函數可以不寫,直接使用編譯器生成的默認析構函數,比如 A,B類;有資源申請時,一定要寫,否則會造成資源泄漏,比如Stack類。(注意下圖打印的指針的地址,證明了特性的第四點)