C++類和物件(一)&&實現OFFSETOF巨集&&THIS指標
一.目錄
1.物件的相關知識
2.類的定義
3.類的例項化
4.類物件模型
5.模擬實現offsetof巨集
6.this指標
二.正文
1.物件的相關知識
C語言是面向過程的,關注的是過程,分析求解問題的步驟,通過函式呼叫逐步解決問題。
C++是面向物件的,關注的是物件,將一件事拆分成不同的物件,靠物件之間的互動完成。
物件:任何一個物件都應該具有兩個要素,即屬性和行為,物件是由一組屬性和行為構成的。如現實生活中的手機就是一個物件,它的屬性就是生產廠家,配置,顏色等等,行為就是它的功能。在一個系統中的多個物件之間通過一定的渠道相互聯絡,如下圖:
在C++中,每個物件都是由資料和函式(即操作程式碼)這兩部分構成的,資料體現了屬性,函式就是對資料進行操作的,以便實現某些功能。(通過三角形這個物件的邊長,通過數學公式(函式)計算出三角形的面積(實現功能)。
2.類的定義
C語言中,結構體中只能定義變數,在C++中,結構體中不僅可以定義變數,還可以定義函式。
C++中的類就是將物件的屬性和行為結合在一塊,通過訪問許可權將介面提供給外部使用者使用,也就是類的封裝屬性,使用者不需要知道具體的實現方法,進而提高了資料的安全性。
類的訪問限定符:
public(公有):修飾的成員變數在類外也可以被直接訪問
protected(保護):在類外不能被直接訪問
private(私有):在類外不能被直接訪問
類訪問限定符的作用域從該訪問限定符出現的位置到下一個訪問限定符出現時為止
注意:在C++中,為了相容C語言的一些特性,struct的功能和C語言相同,但增加了新的功能,即可以在struct中定義函式,struct和class相同,struct預設成員為public,private不加訪問限定符時預設成員為private。
訪問限定符只在編譯期間有作用,當資料 對映到記憶體的時候沒有任何訪問限定符上的區別
類的具體實現:
(1)關鍵字:class
(2)用法:class [classname],{}為類的主體,注意類定義結束後的封號
1 class Student 2 { 3 void TestFun(){//成員函式 4 } 5 int a;//成員變數 6 };
類的兩種定義方法:
(1)宣告和定義都放在類體中,注意:如果成員函式在類體中,則編譯器在編譯期間會將類的成員函式當做行內函數展開(不會在Debug版本下展開,而在釋出版本中展開)
1 class Student 2 { 3 public: 4 int Add(int math, int english){ 5 score=math+english; 6 cout<<a<<" "<<name<<" "<<endl; 7 } 8 public: 9 int score; 10 char *name; 11 };
(2)在類中申明(放在.h檔案中),在類外定義(放在.c檔案中)
//Student.h檔案 class Student { public: void Add(int math, int english); void PrintScore(); private: int score; }; //Student.c void Student::Add(int math, int english) { score=math + english; } void Student::PrintScore() { cout<<score<<" "<<endl; }
注意:這種方法在定義時需要用作用域限定符“::”指定定義的函式屬於這個類作用域
3.類的例項化
定義:用類型別建立物件的過程稱之為類的例項化
解釋:一個類可以建立多個物件,定義類的時候作業系統不會為其開闢記憶體空間,只有當用類型別定義了物件之後物件才會佔用實體地址空間,儲存類成員變數。打個比方,類就像現實生活中建築物的建築圖紙,而建築物就是用圖紙建的物件,圖紙中的的建築物並不真實存在,但建造(例項化)後的建築物真實存在於客觀世界。
1 class Student//類 2 { 3 public: 4 int Add(int math, int english){ 5 score=math+english; 6 } 7 public: 8 char *name; 9 int score; 10 int id; 11 }; 12 13 int main() 14 { 15 Student S1;//物件 16 S1.Add(89,94); 17 cout<<score<<" "<<endl; 18 return 0; 19 }
4.類物件模型
(1)如何計算類物件的大小?
由於類物件具有成員函式和成員變數,那麼該如何計算類的大小?這就是下面要講的。
(2)類物件的儲存方式猜測
a.如果成員函式和成員變數儲存在連續記憶體空間中,則當一個類的成員變數較多時,而且定義多個物件時,會將裡面的成員變數與成員函式都複製一份為其開闢記憶體,每個物件都會儲存一份程式碼,但是這些物件都共用同一個函式,相同程式碼儲存多次,浪費空間,因此不會為每個物件都複製一份程式碼。
b.還有另一種可能情況,如下圖所示情況,只儲存成員變數,成員函式放在公共的程式碼段
c.為了驗證上面兩種猜測,我們可以通過程式碼求得類物件的大小
1 class Student 2 { 3 pupblic: 4 void PrintScore(){ 5 cout<<score<<" "<<endl; 6 } 7 int Add(int math , int english) 8 { 9 score=math + english; 10 } 11 private: 12 int score; 13 } 14 15 int main() 16 { 17 Student S1; 18 cout<<sizeof(S1)<<" "<<endl; 19 reutrn 0; 20 }
通過上述程式碼可以求得類物件的大小,進而判斷出第二種猜測是正確的。即成員函式在公共程式碼區,不會為每個物件都儲存一份公共的程式碼。類的物件的大小求解方法和C語言中的結構體的大小相同。存在記憶體對齊的方式。
一個類的大小,實際上就是該類成員變數所佔記憶體之和,當然也要進行記憶體對齊,注意空類的大小,空類比較特殊,編譯器給空類一個位元組來唯一標識這個類。
(3)結構體中的記憶體對齊規則
a.第一個成員變數在與結構體偏移量為0的地址處
b.其他成員要對齊到某個數字(對齊數)的整數倍地址處。注意:對齊數=編譯器預設的對齊數與該成員變數的較小值(VS中的預設對齊數是8,gcc中的預設編譯器是4
c.結構體的總大小為:最大對齊數(所有變數的型別最大值與編譯器預設對齊數取最小)的整數倍處
d.如果嵌套了結構體,巢狀的結構體對齊到自己最大的整數倍處,結構體的整體的大小就是所有最大對齊數的整數倍(含有巢狀結構體的對齊數)
e.可以用#pragma peak(n)這個預處理指令設定預設的對齊數為n位元組對齊,當然,n應該是2的k次方,不然無法對齊。
f.通過offsetof這個巨集可以求解得某個成員相對於結構體的起始位置的偏移量offsetof(類名,成員名)
5.模擬實現offsetof巨集
1 #define offsetof(TYPE,MEMBER) (size_t)&((TYPE *)0)->MEMBER)
這個巨集可以根據下圖理解:
6.this指標
(1)在看this指標前先看一個例子:
1 class Student 2 { 3 public: 4 void PrintFun(){ 5 cout<<_gender<<" "<<endl; 6 cout<<_name<<" "<<endl; 7 cout<<_age<<" "<<endl; 8 } 9 void Setstudentinfo(char *name, char* gender, int age) 10 { 11 strcpy(_name,name); 12 strcpy(_gender,gender); 13 _age=age; 14 } 15 private: 16 char *_name; 17 char* _gender; 18 int _age; 19 }; 20 21 int main() 22 { 23 Student S1; 24 Student S2; 25 S1.Setstudentinfo("TOM","male",18); 26 S2.Setstudentinfo("perter","male",20); 27 S1.PrintFun(); 28 S2.PrintFun(); 29 return 0; 30 }
對於上述兩個類,有這樣一個問題:
Student類中有Setstudentinfo和PrintFun兩個成員函式,函式體中沒有關於不同物件的區分,而且這兩個成員函式儲存 在程式碼公共區,那麼當S1呼叫Setstudentinfo函式時,該函式如何知道應該設定S1物件而不是設定S2物件呢?
C++通過引入this指標解決了這個問題,即:C++編譯器給每個“成員函式”增加了一個隱藏的指標引數,讓該指標指向當前物件(函式執行時呼叫該函式的物件),在函式體中的所有成員變數的操作,都是通過this指標去訪問。只不過所有的操作對於使用者是透明的,即使用者不用傳遞該引數,編譯器自動完成。
(2)this指標的特性
a.this指標的型別:類型別 * const this
b.只能在“成員函式”內部使用
c.this指標本質上其實是一個成員函式的第一個引數,是物件呼叫成員函式時,將物件的地址傳給this指標,所以物件中不儲存this指標。
d.this指標是成員函式的第一個隱藏的指標形參,一般情況由編譯器通過ecx暫存器自動傳遞,不需要使用者傳遞。
注意:this指標不能作為左值,只能在“成員函式”內部使用。並不是所有的函式的this指標都用ecx暫存器傳遞,比如不定引數函式的this指標就用eax暫存器函式壓棧的方式傳遞地址。這是因為呼叫函式時的呼叫約定不同,有的函式前面加了_thiscall(通過ecx暫存器傳遞),而有的函式前面加了_cdecl(通過壓棧的方式傳遞)
this指標可以為空值,但如果是空值,則在成員函式內部不能訪問物件的成員變數。如下面的程式碼:
1 class A 2 { 3 public: 4 int Add(int left , int right){ 5 a=left+right; 6 } 7 void PrintA(){ 8 cout<<a<<endl; 9 } 10 private: 11 int a; 12 } 13 14 int main() 15 { 16 A *p=NULL;//定義一個類型別的指標,指向這個A類 17 p->Add(1,2);//此時類型別的指標是空值,呼叫它的成員函式的時候講NULL複製給this指 針,this指標為NULL,則執行Add函式時因this訪問a,所以會報錯。 18 p->PrintfA(); 19 return 0; 20 }