寫在前面:大家好!我是【AI 菌】,一枚愛彈吉他的程序員。我
熱愛AI、熱愛分享、熱愛開源
! 這博客是我對學習的一點總結與思考。如果您也對深度學習、機器視覺、算法、C++、Python
感興趣,可以關注我的動態,我們一起學習,一起進步~
我的博客地址爲:【AI 菌】的博客
文章目錄
1. 類與對象
假設我們要編寫一個程序,來模擬人,如下圖這樣。
首先你可能想到的是,人具有一些屬性,比如:姓名、年齡、性別、興趣愛好等;
除此之外,還能做一些事情,比如:自我介紹、吃飯、鍛鍊、購物等,我們可以把做的事情統稱爲方法。
於是,在程序中,我們可以定義一種結構,這種結構裏包含人的屬性以及做的事情(方法),從而能夠簡單的模仿一個人。這種結構就是類。
注:方法就是屬於該類的函數。
(1) 類
類的結構如上圖所示,它包括屬性和方法兩個部分。
在使用類時,我們先要對其聲明。在聲明類時,需要記住以下三點:
- 使用關鍵字class,在class後定義一個類名
- 在{ }裏分別放置若干屬性、方法
- 注意結尾要加分號;
下面舉例聲明一個簡單的類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 在自由存儲區中實例化的,或者有指向對象的指針。就可以使用指針運算符 (->) 來訪問成員屬性和方法。
- 對象使用 new 在自由存儲區中實例化時,使用 (->) 來訪問成員。
Human* pJack = new Human()
pJack->Age = 18;
pJack->Introduce();
delete pTom;
- 當指針指向對象時,使用 (->) 來訪問成員。
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.在類中聲明
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天養成計劃】
由於水平有限,博客中難免會有一些錯誤,有紕漏之處懇請各位大佬不吝賜教!