【C++養成計劃】類與對象 || 構造函數 || 析構函數(Day10)

寫在前面:大家好!我是【AI 菌】,一枚愛彈吉他的程序員。我熱愛AI、熱愛分享、熱愛開源! 這博客是我對學習的一點總結與思考。如果您也對 深度學習、機器視覺、算法、C++、Python 感興趣,可以關注我的動態,我們一起學習,一起進步~
我的博客地址爲:【AI 菌】的博客


1. 類與對象

假設我們要編寫一個程序,來模擬人,如下圖這樣。

首先你可能想到的是,人具有一些屬性,比如:姓名、年齡、性別、興趣愛好等;

除此之外,還能做一些事情,比如:自我介紹、吃飯、鍛鍊、購物等,我們可以把做的事情統稱爲方法。
在這裏插入圖片描述
於是,在程序中,我們可以定義一種結構,這種結構裏包含人的屬性以及做的事情(方法),從而能夠簡單的模仿一個人。這種結構就是類。
注:方法就是屬於該類的函數。

(1) 類

類的結構如上圖所示,它包括屬性和方法兩個部分。
在使用類時,我們先要對其聲明。在聲明類時,需要記住以下三點:

  1. 使用關鍵字class,在class後定義一個類名
  2. 在{ }裏分別放置若干屬性、方法
  3. 注意結尾要加分號;

下面舉例聲明一個簡單的類Human:

class Human
{
	//屬性:
	string Name;
	int Age;
	string Gender;
	//方法:
	void Introduce();
	void eat(string food);
}

通過上面的例子,我們學會了用關鍵字class,創建一個簡單的類Human。在這裏,也可以稱Human是我們自己創建的一個數據類型,並在其中封裝了相應的屬性和函數。
注:封裝是指將數據以及使用他們的方法進行邏輯組合,這是面向對象編程的重要特徵。

(2) 對象

類相當於藍圖,只是聲明類並不會對程序的執行有何影響,就如同聲明一個函數而不去調用它一樣。在程序執行時,對象是類的化身。要使用類的功能,通常需要根據類實例化一個對象,並通過對象訪問類的屬性和方法。

類似於聲明一個變量,實例化一個Human的對象如下:

int a;  //聲明一個變量
Human Jack;  //實例化一個對象Jack

同樣也可以使用 new 爲Human對象動態地分配內存。

回憶一下,我們在 《面試官:指針都不會,我們不需要你這樣的人!》中,學習瞭如何用 new 爲int類型的數據動態分配內存。

int* pNum = new int;
delete pNum;

在這裏,Human作爲我們自己創建的數據類型。那麼使用 new 爲 Human 對象動態地分配內存如下:

Human* pHuman = new Human(); //動態分配內存給Human對象。new返回分配的內存地址給指針pHuman,也可以說pHuman指向分配的內存
delete pHuman; 

(3) 句點運算符(.)

使用 句點運算符(.) 可以訪問屬性和成員函數。
比如,還是上面的Human類,我們先實例化一個對象Jack。然後就可以通過 (.) 訪問它的屬性和成員函數。

Human Jack;
Jack.Age = 18;  //訪問屬性
Jack.Introduce();  //訪問成員函數

除此之外,當有一個指針pJack,它指向Human類的一個實例對象Jack,也可以通過 (.) 來訪問成員:

Human* pJack = new Human();
(*pTom).Introduce()

(4) 指針運算符 (->)

如果對象是使用 new 在自由存儲區中實例化的,或者有指向對象的指針。就可以使用指針運算符 (->) 來訪問成員屬性和方法。

  1. 對象使用 new 在自由存儲區中實例化時,使用 (->) 來訪問成員。
Human* pJack = new Human()
pJack->Age = 18;
pJack->Introduce();
delete pTom; 
  1. 當指針指向對象時,使用 (->) 來訪問成員。
Human Jack;
Human* pJack = &Jack;
pJack->Age = 18;
pJack->Introduce()

2. public 和 private

每個人都會有很多個人信息,其中有些可以公開,有些隱私不能公開。那麼在類中,我們也可以通過關鍵字public、private來指定哪些屬性和方法是公有的,哪些是私有的。
當類屬性和方法聲明爲公有時,就可以通過對象直接獲取它們。當聲明爲私有時,就只能在類的內部(或其友元)中訪問。
下面舉一個例子:在現實中,很多人不想公開自己的實際的年齡,因此Huaman類中Age是一個私有成員。當你想向外指出的年齡比實際小3歲時,就可以在公有的方法SetAge中,將歲數減3。

#include<iostream>
using namespace std;

class Human
{
private:
	int Age;
public:
	void GetAge(int InputAge)
	{
		Age = InputAge;
	}
	int SetAge()
	{
		return (Age-3);
	}		
};
int main()
{
	Human Tom;
	Tom.GetAge(20);
	
	Human Jack;
	Jack.GetAge(18);
	
	cout<<"Tom的年齡是:"<<Tom.SetAge()<<endl;
	cout<<"Jack的年齡是:"<<Jack.SetAge()<<endl;
	return 0;
} 

運行結果:
在這裏插入圖片描述
在程序中,如果直接通過 Tom.Age 來訪問Age,編譯時會報錯。因爲Age是私有成員。

3. 構造函數

簡單來說,構造函數是一種特殊的函數(方法),在創建對象時被調用

(1) 聲明與實現

構造函數有兩個特點:

  1. 函數名與類名相同。
  2. 函數不返回任何值。

構造函數有兩種聲明方式:既可在類中,也可以在類外。

1.在類中聲明
class Human
{
public:
	Human()
	{
		函數體;
	}
};

2.在類外聲明
class Human
{
public:
	Human();  //構造函數聲明
};
Human::Human()  //定義構造函數
{
	函數體;
}

程序中,(::) 被稱爲作用域運算符

(2) 如何使用構造函數

構造函數總是在創建對象時被調用,這讓構造函數成爲類成員變量初始化的理想場所。
下面舉個例子:使用構造函數初始化類成員變量

#include<iostream>
using namespace std;

class Human
{
private:
	string Name;
	int Age;
	
public:
	Human()
	{
		cout<<"構造函數:初始化變量Age"<<endl;
		Age=0;
	}
	void SetName(string InputName)
	{
		Name = InputName;
	} 
	void SetAge(int InputAge)
	{
		Age = InputAge;
	}
	void Introduce()
	{
		cout<<"I am " + Name<< " and am "<<Age<<" years old"<<endl;
	}
};

int main()
{
	Human Man;
	Man.SetName("Jack");
	Man.SetAge(18);
	
	Man.Introduce();
	return 0; 
}

運行結果:
在這裏插入圖片描述
可見,我們可以使用構造函數,用來初始化變量。

(3) 重載構造函數

與普通函數一樣,構造函數也能重載。因此可創建一個將姓名作爲參數的構造函數。如下所示:

class Human
{
public:
	Human()
	{
		默認構造函數;
	}
	Human(string InputName)
	{
		重載構造函數;
	}
}

下面舉一個實際的例子:定義了兩個不同的重載構造函數,在創建Human對象時要提供姓名或年齡:

#include<iostream>
using namespace std;

class Human
{
private:
	string Name;
	int Age;
	
public:
	Human()
	{
		cout<<"調用構造函數:初始化變量Age"<<endl;
		Age=0;
	}
	Human(string InputName)
	{
		cout<<"調用重載構造函數1:初始化變量Name"<<endl;
		Age = 0; 
		Name= InputName; 
	}
	Human(string InputName, int InputAge)
	{
		cout<<"調用重載構造函數2:初始化變量Age、Name"<<endl;
	}
	void SetName(string InputName)
	{
		Name = InputName;
	} 
	void SetAge(int InputAge)
	{
		Age = InputAge;
	}
	void Introduce()
	{
		cout<<"I am " + Name<< " and am "<<Age<<" years old"<<endl<<endl;;
	}
};

int main()
{
	Human Man;  //實例化對象
	Man.SetName("Jack");
	Man.SetAge(18);
	Man.Introduce();
	
	Human Woman("Lucy");  //實例化對象,並提供了一個參數
	Woman.SetAge(16);
	Woman.Introduce();
	
	Human Kid("Tom0", 6);   //實例化對象,並提供了兩個參數
	Kid.Introduce();
	return 0; 
}

運行結果:
在這裏插入圖片描述
這上面這個例子中,一共聲明瞭三個構造函數:1個默認構造函數、2個重載構造函數。

那麼,在實例化一個對象時,到底是自動調用哪一個構造函數呢?
從上面的例子可以得出答案:依據實例化對象時提供的參數,編譯器會自動調用相應的構造函數。比如實例化時沒有參數,就調用默認構造函數;有1個參數Name,則調用重載構造函數1;有兩個參數Name和Age,則調用重載構造函數2。

這個問題再拓展一下:當類中沒有默認構造函數,那麼在實例化對象時,必須提供對應的參數Name或Age。更具提供參數,選擇調用重載構造函數1或2。

注:如果對重載函數還不太熟悉的同學,得先加加餐:【C++養成計劃】深入淺出——函數(Day6)

(4) 帶默認值的構造函數

和普通函數一樣,構造函數也可以帶參數。如下所示:

class Human()
{
private:
	string Name;
	int Age;
public:
	Human(string InputName, int InputAge=18)
	{
		Name = InputName;
		Age = InputAge;
	}
};

這樣的話,我們在實例化對象的時候,就要兩種方式:

Human Man("Tom");
Human Woman("Lucy", 16); //這時16就會覆蓋默認值

4. 析構函數

與構造函數一樣,析構函數也是一種特殊的函數。不同的是,析構函數在對象銷燬時自動被調用。

聲明方式和構造函數也近似,只是在前面加了一個波浪號(~)。如下所示:

1.類內聲明
class Human
{
public:
	~Human()
	{
		析構函數;
	}
}2.類外聲明
class Human
{
public:
	~Human();
};
Human::~Human()
{
	構造函數;
}

何時使用析構函數
每當對象不再在作用域內或通過delete被刪除,進而被銷燬時,都將調用析構函數。這使得析構函數是重置變量、釋放動態分配內存和其他資源的理想場所。

小結:
在對象創建時,編譯器會自動調用構造函數;對象銷燬時,將自動調用構造函數。
注:構造函數、析構函數都是編譯器自動調用的。


相關文章我都放這裏了【C++21天養成計劃】

由於水平有限,博客中難免會有一些錯誤,有紕漏之處懇請各位大佬不吝賜教!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章