🎁個人主頁:工藤新一1
🔍系列專欄:C++面向對象(類和對象篇)
🌟心中的天空之城,終會照亮我前方的路
🎉歡迎大家點贊👍評論📝收藏?文章
文章目錄
- @[toc]
- C++類和對象(上)
- 一、類的定義
- 1.1類定義格式
- 1.2兼容結構體
- 二、實例化
- 2.1實例化概念
- 2.2對象大小
- 2.3內存對齊
- 三、this指針
- 3.1常量指針與指針常量
- 3.2面試真題
- 3.3淺識寄存器 esp/ebp
- 四、C++與C語言實現Stack對比
文章目錄
- @[toc]
- C++類和對象(上)
- 一、類的定義
- 1.1類定義格式
- 1.2兼容結構體
- 二、實例化
- 2.1實例化概念
- 2.2對象大小
- 2.3內存對齊
- 三、this指針
- 3.1常量指針與指針常量
- 3.2面試真題
- 3.3淺識寄存器 esp/ebp
- 四、C++與C語言實現Stack對比
C++類和對象(上)
一、類的定義
1.1類定義格式
-
class
為定義類的關鍵字,以棧為例:Stack
為類的名字,{}
中為類的主體,注意類定義結束時后?分號不能省略。類體中內容稱為類的成員:類中的變量稱為類的屬性或成員變量;類中的函數稱為類的方法或者成員函數 -
為了區分成員變量,?般習慣上成員變量會加?個特殊標識,如成員變量前?或者后?加
_
或者m
開頭,注意C++
中這個并不是強制的,只是?些慣例,具體看公司的要求 -
定義在類中的成員函數默認為
inline
(內聯)
#include<iostream>
using namespace std;
#include<cassert>typedef int STDataType;
class Stack
{
public://成員函數//1、對棧初始化void StackInit(int n = 4){array = (STDataType*)malloc(sizeof(STDataType) * n);if (array == nullptr){perror("malloc fail!");exit(1);}capacity = 4;top = 0;}//2、入棧操作void StackPush(STDataType x){//增容操作if (top == capacity){int newCapacity = capacity == 0 ? 4 : 2 * capacity;STDataRtpe* temp = (STDataType*)realloc(array, newCapacity * sizeof(STDataType));if (temp == nullptr){perror("realloc fail!");exit(1);}array = temp;capacity = newCapacity;}array[top++] = x;}//3、檢擦棧頂元素STDataRtpe StackTop(){assert(!StackEmpty());return array[top - 1];}//4、判空操作bool StackEmpty(){return top == 0;}//5、出棧操作void StackPop(){assert(!StackEmpty());/*斷言:條件為ture,程序繼續執行條件為false,程序停止運行*/top--;}//6、銷毀棧void StackDestroy(){if(array)free(array);array = nullptr;top = capacity = 0;}private://成員變量(多個變量的符合)STDataType* array;size_t capacity;size_t top;
};int main()
{Stack st;st.StackInit();return 0;
}
? 數據結構 ** 中的棧數據寫在 結構體中 ,棧操作實現方法在 結構體外,而在類中將數據(成員變量)與實現方法(成員函數)統一寫在類內**
1.2兼容結構體
C++
中struct
也可以定義類,C++
兼容C中struct
的?法,同時struct
升級成了類,明顯的變化是struct
中可以定義函數,?般情況下我們還是推薦?class
定義類- 在 C++ 中,
struct
的結構體成員默認持有公共權限public
,而class
的成員對象默認持有私有權限private
以鏈表為例:
/*C++ 將 struct 升級為了類1、結構體類中可以定義函數2、struct 名稱就可以代表數據類型
*///C++兼容 C 中 struct 的用法
typedef int LTDataType;typedef struct ListNodeC
{LTDataType val;struct ListNodeC* next;//不可以直接在結構體中 ListNodeC* next;}LTNode;//不需要 typedef 修飾,結構體名 ListNodeCPP 就是數據類型
struct ListNodeCPP
{//不僅可以定義變量,也可定義函數void LTInit(LTDataType x){val = x;next = nullptr;}private:LTDataType val;ListNodeCPP* next;
};int main()
{LTNode* node1 = NULL;struct ListNodeC* node2 = NULL;ListNodeCPP* node = nullptr;node->LTInit(1);return 0;
}
本質上:
- 結構體是數據的集合
- 類是數據和方法的集合
二、實例化
2.1實例化概念
-
?類類型在物理內存中創建對象的過程,稱為類實例化出對象
-
類是對象進行的?種抽象描述,是?個模型?樣的東西,限定了類有哪些成員變量,這些成員變量只 是聲明,沒有分配空間,?類實例化出對象時,才會分配空間
-
?個類可以實例化出多個對象,實例化出的對象 占?實際的物理空間,存儲類成員變量。打個? ?:類實例化出對象就像現實中使?建筑設計圖建造出房?,類就像是設計圖,設計圖規劃了有多 少個房間,房間??功能等,但是并沒有實體的建筑存在,也不能住?,?設計圖修建出房?,房 ?才能住?。同樣類就像設計圖?樣,不能存儲數據,實例化出的對象分配物理內存存儲數據
class Date
{
public:void Init(int year, int month, int day){this->year = year;this->month = month;this->day = day;}private://成員變量是類中的聲明(沒有開辟任何空間)int year;int month;int day;
};
int main()
{//通過類所實例化出的對象為成員變量開辟空間Date d1, d2;return 0;
}
2.2對象大小
- 實例對象中,只存儲成員變量地址,不存儲成員函數地址
? 分析?下類對象中哪些成員呢?類實例化出的每個對象,都有獨?的數據空間,所以對象中肯定包含 成員變量,那么成員函數是否包含呢?
? ?先 函數被編譯后是?段指令,在實例對象中沒辦法存儲,這些指令 存儲在?個單獨的區域(代碼段),那么實例對象中?要存儲的話,只能通過 成員函數的指針。
? 再分析?下,實例對象中是否有存儲指針的必要呢,Date
實例化 d1
和 d2
兩 個對象,d1
和 d2
都有各?獨?的成員變量 year/_month/_day
存儲各?的數據,但是 d1
和 d2
的成員函數 Init/Print
指針卻是?樣的,存儲在對象中就浪費了。
class Date
{
public:void Init(int year, int month, int day){this->year = year;this->month = month;this->day = day;}private://成員變量是類中的聲明(沒有開辟任何空間)int year;int month;int day;
};
int main()
{//通過類所實例化出的對象為成員變量開辟空間Date d1, d2;cout << sizeof(Date) << endl;cout << sizeof(d1) << endl;return 0;
}
?
匯編角度:函數調用,被定義結束后是一串指令,這串指令會存儲到一個單獨的區域(常量區,在操作系統的角度上:也稱代碼段)
?
? 成員變量需要獨立的空間存放各種來自外界傳入的值
C++//通過不同實例對象調用的 year 需要不同空間存儲獨立的值(本質:存儲地址不同)d1.year++;d2.year++;//成員函數 Print,所有對象共享同一份函數代碼,而非單獨存儲在某一個對象上d1.Print();d2.Print();
? 如果? Date
實例化100個對象,那么 成員函數指針 就重復存儲100次,太浪費了。這?需要再額外哆嗦?下,其實 函數指針 是不需要存儲的,函數指針是?個地址,調?函數被編譯成匯編指 令[call 地址],其實編譯器在編譯鏈接時,就要找到函數的地址,不是在運?時找, 只有動態多態是在運?時找,就需要存儲函數地址,這個我們以后會講解
- 函數在本文件中有定義 - 編譯階段確定函數地址
- 函數定義在外文件 - 鏈接階段確定函數
上面我們分析了 實例對象只存儲成員變量,C++ 規定類實例化的對象也要符合 **內存對齊 **的規則
2.3內存對齊
設計思路:以空間換時間,每個對象從對齊數的位置開始存放
-
第?個成員在與結構體偏移量為 0 的地址處
-
其他成員變量要對?到某個數字(對?數)的整數倍的地址處
-
注意:對?數=編譯器默認的?個對?數與該成員??的較?值
-
VS中默認的對?數為 8
-
結構體總??為:最?對?數(所有變量類型最?者與默認對?參數取最?)的整數倍
-
如果嵌套了結構體的情況,嵌套的結構體對?到??的最?對?數的整數倍處,結構體的整體?? 就是所有最?對?數(含嵌套結構體的對?數)的整數倍
class A
{
public:void Print(){cout << _ch << endl;}
private:char _ch;int _i;
};
class B
{
public:void Print(){//...}
};
class C
{ };
int main()
{cout << sizeof(A) << endl;//8cout << sizeof(B) << endl;//1cout << sizeof(C) << endl;//1return 0;
}
- 上?的程序運?后,我們看到沒有成員變量的B和C類對象的??是 1,為什么沒有成員變量還要給1個 字節呢?因為如果?個字節都不給,怎么表?對象存在過呢!所以這?給1字節,純粹是為了 占位標識 對象存在
面試經典問題:為什么需要內存對齊?
三、this指針
Date
類中有Init
與Print
兩個成員函數,函數體中沒有關于不同對象的區分,那當d1
調?Init
和Print
函數時,該函數是如何知道應該訪問的是d1
對象還是d2
對象呢?那么這?就要看到 C++ 給了 ?個隱含的this
指針解決這?的問題- C++ 規定不能在實參和 形參 的位置顯?的寫
this指針
(編譯時編譯器會處理),但是可以在函數體內顯 ?使?this指針
class Date
{
public:void Init(int year, int month, int day){this->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, d2;d1.Init(2025, 5, 1);d2.Init(2025, 5, 2);d1.Print();d2.Print();return 0;
}
思考:d1、d2 調用的是同一個 Print,執行的是同一個函數,參數相同,那為什么輸出的值不同?
我們先前調用同一個函數,執行出的結果不同,是因為我們傳入了不同的參數
編譯階段,編譯器為我們處理隱藏過程。實際上,我們調用同一個函數 Print()
執行出了不同結果,還是因為我們傳入了不同參數
同樣地,
this
本身無法被修改:this = nullptr(false)
3.1常量指針與指針常量
- 常量指針與指向常量的指針
驗證 this
指針指向 d1/d2
對象地址:
3.2面試真題
答案:
? 正常運行
Print() 編譯時確定地址,不存在 對空指針進行解引用的行為
對象 p
的作用:
- 搜查、核對成員函數的出處
- 指針所指向的對象(或對象)傳遞
this指針
-
編譯錯誤:語法問題,因此對于空指針的解引用(運行錯誤)一定不會觸及語法編譯的問題
-
鏈接錯誤:語法規則符合,但存在函數或變量只聲明,未定義行為
-
運行錯誤:行為/功能錯誤
答案:
? 運行崩潰
(*p).Print(); 同理
解決方法:使 指針p 指向一個有效對象
C++A aa;A* p = &aa;//或 A* p = new A();
? 常量區(語言層面) - 代碼段(或叫數據段 - 操作系統角度)(函數被編譯成指令后,存儲在常量區)
? 代碼段:將函數編譯好的代碼指令(因此代碼指令存儲在常量區/代碼段)
? this指針
所存放的區域中,this指針
存放在對象中這一選項或許會有些許疑慮,但很簡單,如果其存放在對象中,那么 sizoef(指針) == 4;
這就與我們前面提到的 sizeof(p)(或sizeof(Person))== 1;
背道而馳了,因此,可論證: this指針 并不存儲在實例對象中
this指針 是形式參數
,嚴格一點是存儲在 棧和寄存器 上
3.3淺識寄存器 esp/ebp
四、C++與C語言實現Stack對比
?向對象三?特性:封裝、繼承、多態,下?的對?我們可以初步了解?下封裝。 通過下?兩份代碼對?,我們發現 C++ 實現 Stack 形態上還是發?了挺多的變化,底層和邏輯上沒啥變化
以棧為例,C++
對棧的各項操作進行了嚴格封裝(減少了 C語言
多樣化,但易錯誤的代碼形態),而 C語言
則更依賴于程序員應對形式多樣化代碼能力的素質
CSTack st;//訪問棧頂元素,方式一(直接調用函數 - 推薦):st.STop();//方式二:st.arr[st.top - 1];方式二:存在一定的不安全性,比如數組越界、0 - 1 == -1 等情況
-
C++ 中數據和函數都放到了類??,通過 訪問限定符 進?了限制,不能再隨意通過對象直接修改數 據,這是 C++封裝 的?種體現,這個是最重要的變化。這?的 封裝 的本質是?種更嚴格規范的管理,避免出現亂訪問修改的問題 。當然 封裝 不僅僅是這樣的,我們后?還需要不斷的去學習
-
C++ 中有?些相對?便的語法,?如
Init
給的缺省參數會?便很多,成員函數每次不需要傳對象地址,因為 this指針 隱含的傳遞了,?便了很多,使?類型不再需要typedef
?類名就很?便
🌟 各位看官好,我是工藤新一1呀~
🌈 愿各位心中所想,終有所致!