一、類的定義
1、類定義格式
? class為定義類的關鍵字,Stack為類的名字,{}中為類的主體,注意類定義結束時后面分號不能省
略。類體中內容稱為類的成員:類中的變量稱為類的屬性或成員變量; 類中的函數稱為類的方法或
者成員函數。
? 為了區分成員變量,?般習慣上成員變量會加?個特殊標識,如成員變量前面或者后面加_ 或者 m
開頭,注意C++中這個并不是強制的,只是?些慣例。
? C++中struct也可以定義類,C++兼容C中struct的用法,同時struct升級成了類,明顯的變化是
struct中可以定義函數,一般情況下我們還是推薦用class定義類。
? 定義在類面的成員函數默認為inline。
#include<iostream>
using namespace std;
class Stack
{
public:// 成員函數void Init(int n = 4){array = (int*)malloc(sizeof(int) * n);if (nullptr == array){perror("malloc申請空間失敗");return;}capacity = n;top = 0;}void Push(int x){// ...擴容array[top++] = x;}int Top(){assert(top > 0);return array[top - 1];}void Destroy(){free(array);array = nullptr;top = capacity = 0;}
private:// 成員變量int* array;size_t capacity;size_t top;
}; // 分號不能省略
int main()
{Stack st;st.Init();st.Push(1);st.Push(2);cout << st.Top() << endl;st.Destroy();return 0;
}
#include<iostream>
using namespace std;
// C++升級struct升級成了類
// 1、類??可以定義函數
// 2、struct名稱就可以代表類型
// C++兼容C中struct的?法
typedef struct ListNodeC
{struct ListNodeC* next;int val;
}LTNode;
// 不再需要typedef,ListNodeCPP就可以代表類型
struct ListNodeCPP
{void Init(int x){next = nullptr;val = x;}ListNodeCPP* next;int val;
};
2、訪問限定符
? C++?種實現封裝的方式,用類將對象的屬性與方法結合在?塊,讓對象更加完善,通過訪問權限
選擇性的將其接口提供給外部的用戶使用。
? public修飾的成員在類外可以直接被訪問;protected和private修飾的成員在類外不能直接被訪
問。
? 訪問權限作用域從該訪問限定符出現的位置開始直到下?個訪問限定符出現時為止,如果后面沒有
訪問限定符,作用域就到 }即類結束。
? class定義成員沒有被訪問限定符修飾時默認為private,struct默認為public。
? ?般成員變量都會被限制為private/protected,需要給別?使?的成員函數會放為public。
3、類域
? 類定義了?個新的作用域,類的所有成員都在類的作用域中,在類體外定義成員時,需要使用 :: 作
用域操作符指明成員屬于哪個類域。
? 類域影響的是編譯的查找規則,下面程序中Init如果不指定類域Stack,那么編譯器就把Init當成全
局函數,那么編譯時,找不到array等成員的聲明/定義在哪里,就會報錯。指定類域Stack,就是知
道Init是成員函數,當前域找不到的array等成員,就會到類域中去查找。
#include<iostream>
using namespace std;
class Stack
{
public:
// 成員函數void Init(int n = 4);
private:// 成員變量int* array;size_t capacity;size_t top;
};
// 聲明和定義分離,需要指定類域
void Stack::Init(int n)
{array = (int*)malloc(sizeof(int) * n);if (nullptr == array){perror("malloc申請空間失敗");return;}capacity = n;top = 0;
}
int main()
{Stack st;st.Init();return 0;
}
二、實例化
1、概念
? 用類類型在物理內存中創建對象的過程,稱為類實例化出對象。
? 類是對象進用?種抽象描述,是?個模型?樣的東西,限定了類有哪些成員變量,這些成員變量只
是聲明,沒有分配空間,?類實例化出對象時,才會分配空間。
? ?個類可以實例化出多個對象,實例化出的對象 占用實際的物理空間,存儲類成員變量。打個
比方:類實例化出對象就像現實中使用建筑設計圖建造出房子,類就像是設計圖,設計圖規劃了有多
少個房間,房間大小功能等,但是并沒有實體的建筑存在,也不能住人,用設計圖修建出房子,房
子才能住人。同樣類就像設計圖?樣,不能存儲數據,實例化出的對象分配物理內存存儲數據。
#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和d2Date d1;Date d2;d1.Init(2024, 3, 31);d1.Print();d2.Init(2024, 7, 5);d2.Print();return 0;
}
2、對象大小
分析?下類對象中哪些成員呢?類實例化出的每個對象,都有獨立的數據空間,所以對象中肯定包含
成員變量,那么成員函數是否包含呢?首先函數被編譯后是?段指令,對象中沒辦法存儲,這些指令
存儲在?個單獨的區域(代碼段),那么對象中非要存儲的話,只能是成員函數的指針。再分析?下,對
象中是否有存儲指針的必要呢,Date實例化d1和d2兩個對象,d1和d2都有各自獨立的成員變量
_year/_month/_day存儲各自的數據,但是d1和d2的成員函數Init/Print指針卻是?樣的,存儲在對象
中就浪費了。
C++規定類實例化的對象也要符合內存對齊的規則。
-
結構體的第?個成員對齊到和結構體變量起始位置偏移量為0的地址處
-
其他成員變量要對齊到某個數字(對齊數)的整數倍的地址處。
? 對齊數 = 編譯器默認的?個對齊數 與 該成員變量大小的較小值。VS 中默認的值為 8 Linux中 gcc 沒有默認對齊數,對
齊數就是成員自身的大小
- 結構體總大小為最大對齊數(結構體中每個成員變量都有?個對齊數,所有對齊數中最大的)的
整數倍。
- 如果嵌套了結構體的情況,嵌套的結構體成員對齊到自己的成員中最大對齊數的整數倍處,結構
體的整體大小就是所有最大對齊數(含嵌套結構體中成員的對齊數)的整數倍。
這么說有點晦澀難懂,接下來給大家幾個例子了解。
class MyClass1
{char c1;int i;char c2;
};
int main()
{MyClass cl;cout << sizeof(cl) << endl;return 0;
}
對于結構體第一個變量我們是不用考慮c1的偏差值,直接存放,根據對齊規則,i要放在4的倍數的位置,即偏差值為4的位置,c2是char類型所以可以隨便放(任何數都是1的倍數),所以這個在內存中是這么放的:
所以總大小為9但其成員最大對齊數是4,5不是4的倍數,所以最終內存大小為12字節。
class MyClass1
{char c1;int i;char c2;
};
class S4
{char c1;MyClass1 s1;int d;
};
int main()
{S4 s;cout << sizeof(s) << endl;return 0;
}
由上面知道MyClass1大小為12,其中它的成員最大對齊數為4,所以待會對齊到4的倍數那所以其在內存存放是這樣的:
總大小為20,其中最大對齊數為4,而20是4的倍數,所以這個大小為20字節。
最后,我們思考如果一個類沒有成員對象大小是怎么樣?
class A
{
public:void Print(){cout << _ch << endl;}
private:char _ch;int _i;
};
class B
{
public:void Print(){//...}
};
class C
{};
int main()
{A a;B b;C c;cout << sizeof(a) << endl;cout << sizeof(b) << endl;cout << sizeof(c) << endl;return 0;
}
上面的程序運行后,我們看到沒有成員變量的B和C類對象的大小是1,為什么沒有成員變量還要給1個
字節呢?因為如果?個字節都不給,怎么表示對象存在過呢!所以這里給1字節,純粹是為了占位標識
對象存在。
三、this指針
前面我們了解到在計算類的大小時我們是不用管函數的,函數是同一儲存在代碼區的,那么編譯器是如何識別我在哪個類里面調用了該函數的呢?其實就是this的作用
? Date類中有 Init 與 Print 兩個成員函數,函數體中沒有關于不同對象的區分,那當d1調用Init和
Print函數時,該函數是如何知道應該訪問的是d1對象還是d2對象呢?那么這里就要看到C++給了
?個隱含的this指針解決這里的問題
? 編譯器編譯后,類的成員函數默認都會在形參第?個位置,增加?個當前類類型的指針,叫做this
指針。比如Date類的Init的真實原型為, void Init(Date* const this, int year,int month, int day)
? 類的成員函數中訪問成員變量,本質都是通過this指針訪問的,如Init函數中給_year賦值, this-
>_year = year;
? C++規定不能在實參和形參的位置顯示的寫this指針(編譯時編譯器會處理),但是可以在函數體內顯
示使用this指針。
#include<iostream>
using namespace std;
class Date
{
public:// void Init(Date* const this, int year, int month, int day)//實際上為這樣void Init(int year, int month, int day){// this->_year = year;_year = year;this->_month = month;this->_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:// 這?只是聲明,沒有開空間int _year;int _month;int _day;
};
int main()
{// Date類實例化出對象d1和d2Date d1;Date d2;// d1.Init(&d1, 2024, 3, 31);//把d1的地址傳過去讓this接收d1.Init(2024, 3, 31);d1.Print();d2.Init(2024, 7, 5);d2.Print();return 0;
}
這里就有兩道非常有意思的題:
#include<iostream>
using namespace std;
class A
{
public:void Print(){cout << "A::Print()" << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();return 0;
}
上面這個程序是正常運行的,雖然有可能有些人會以為對空指針解引用會造成編譯錯誤,實際上并沒有對空指針經行解引用操作
#include<iostream>
using namespace std;
class A
{
public:void Print()//void Print((A* const this)//沒有對空指針解引用{cout << "A::Print()" << endl;}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();//相當于Print(p)return 0;
}
再來看下一個:
#include<iostream>
using namespace std;
class A
{
public:void Print()//void Print((A* const this){cout << "A::Print()" << endl;cout << _a << endl;//this->_a這進行了對空指針的解引用}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();//相當于Print(p)return 0;
}
這個就會出現編譯報錯,因為載打印類的成員對象_ a時對空指針進行了解引用。
#include<iostream>
using namespace std;
class A
{
public:void Print()//void Print((A* const this){cout << "A::Print()" << endl;cout << _a << endl;//this->_a這進行了對空指針的解引用}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();//相當于Print(p)return 0;
}
l;
cout << _a << endl;//this->_a這進行了對空指針的解引用
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();//相當于Print(p)
return 0;
}
這個就會出現編譯報錯,因為載打印類的成員對象_ a時對空指針進行了解引用。```c++
#include<iostream>
using namespace std;
class A
{
public:void Print()//void Print((A* const this){cout << "A::Print()" << endl;cout << _a << endl;//this->_a這進行了對空指針的解引用}
private:int _a;
};
int main()
{A* p = nullptr;p->Print();//相當于Print(p)return 0;
}