文章目錄
- 一、類的定義
- 1、類定義格式
- 2、訪問限定符
- 3、類域
- 二、實例化
- 1、實例化概念
- 2、對象??
- 三、this指針
- 四、C++和C語?實現Stack對?
一、類的定義
1、類定義格式
- class為定義類的關鍵字,Stack為類的名字,{ }中為類的主體,注意類定義結束時后?分號不能省略。類體中內容稱為類的成員,類中的變量稱為類的屬性或成員變量,類中的函數稱為類的?法或者成員函數。
#include<iostream>
using namespace std;class Stack
{
public://成員函數void Push(int x){}void Pop(){}int Top(){return 0;}private://成員變量int* a;int top;int capacity;};int main()
{Stack st;st.Push(1);st.Pop();return 0;
}
-
為了區分成員變量,?般習慣上成員變量會加?個特殊標識,如成員變量前?或者后?加_或者 m開頭,注意C++中這個并不是強制的,只是?些慣例。
-
C++中struct也可以定義類,C++兼容C中struct的?法,同時struct升級成了類,明顯的變化是struct中可以定義函數,?般情況下我們還是推薦?class定義類。
#include<iostream>
using namespace std;struct Date
{
public:void Init(int year, int month, int day);private://用_標識成員變量 聲明int _year;int _month;int _day;
};void Date::Init(int year, int month, int day)
{_year = year;_month = month;_day = day;
}int main()
{struct Date d1;Date d2;Date d3;d2.Init(2025, 8, 1);return 0;
}
#include<iostream>
using namespace std;//兼容C的struct用法
typedef struct ListNodeC
{int val;struct ListNodeC* next;
}LTNodeC;//CPP
struct ListNodeCPP
{int val;ListNodeCPP* next;
};int main()
{ListNodeCPP node;return 0;
}
- 定義在類里?的成員函數默認為inline。
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* _a;int _top;int _capacity;
};void Stack::Init(int n)
{_a = (int*)malloc(sizeof(int) * n);if (_a == nullptr){perror("malloc fail");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(2025,5,1);d1.Print();d2.Init(2025, 7, 29);d2.Print();return 0;
}
2、對象??
類實例化出的每個對象,都有獨?的數據空間,所以對象中肯定包含成員變量,那么是否包含成員函數呢??先函數被編譯后是?段指令,對象中沒辦法存儲,這些指令存儲在?個單獨的區域(代碼段),因此只能存儲成員函數的指針。再分析?下,對象中是否有存儲指針的必要呢,Date實例化d1和d2兩個對象,d1和d2都有各?獨?的成員變量_year/_month/_day存儲各?的數據,但是d1和d2的成員函數Init/Print指針卻是?樣的,存儲在對象中就浪費了。如果?Date實例化100個對象,那么成員函數指針就重復存儲100次,太浪費了。
還要注意,其實函數指針是不需要存儲的,函數指針是?個地址,調?函數被編譯成匯編指令[call 地址],其實編譯器在編譯鏈接時,就要找到函數的地址,不是在運?時找,只有動態多態是在運?時找,就需要存儲函數地址。
上?我們分析了對象中只存儲成員變量,C++規定類實例化的對象也要符合內存對?的規則。
內存對?規則:
①:第?個成員位于結構體偏移量為0的地址處。
②:其他成員變量要對?到對?數整數倍的地址處。
注意:對?數=編譯器默認的?個對?數與該成員??的較?值,VS中默認的對?數為8。
③:嵌套的結構體對齊到自己成員中最大對齊數的整數倍。
④ :結構體總??為:最?對?數(含嵌套結構體的對齊數)的整數倍。
#include<iostream>
using namespace std;// 計算?下A/B/C實例化的對象是多大?
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;
}
實例化對象a的大小計算:
運行結果:
上?的程序運?后,我們看到沒有成員變量的B和C類對象的??是1,為什么沒有成員變量還要給1個字節呢?因為如果?個字節都不給,怎么表?對象存在過呢!所以這?給1字節,純粹是為了占位標識對象存在。
三、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;this->_month = month;this->_day = day;}//void Print(Date* const this)void Print(){cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2;//d1.Init(&d1,2025,5,1)d1.Init(2025, 5, 1);//d1.Print(&d1)d1.Print();//d2.Init(&d2,2025,10,1)d2.Init(2025, 10, 1);//d2.Print(&d2)d2.Print();return 0;
}
四、C++和C語?實現Stack對?
?向對象三?特性:封裝、繼承、多態,下?的對?我們可以初步了解?下封裝。
通過下?兩份代碼對?,我們發現C++實現Stack形態上還是發?了挺多的變化,底層和邏輯上沒啥變化。
-
C++中數據和函數都放到了類??,通過訪問限定符進?了限制,不能再隨意通過對象直接修改數據,這是C++封裝的?種體現,這個是最重要的變化。這?封裝的本質是?種更嚴格規范的管理,避免出現亂訪問修改的問題。
-
C++中有?些相對?便的語法,?如Init給的缺省參數會?便很多,成員函數每次不需要傳對象地址,因為this指針隱含的傳遞了,使?類型不再需要typedef來命名類名就很?便。
C實現Stack:
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;void STInit(ST* ps)
{assert(ps);ps->a = NULL;ps->top = ps->capacity = 0;
}void STDestroy(ST* ps)
{assert(ps);free(ps->a);ps->a = NULL;ps->top = ps->capacity = 0;
}void STPush(ST* ps, STDataType x)
{assert(ps);//檢查空間大小if (ps->top == ps->capacity){int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity = newcapacity;}//入棧ps->a[ps->top] = x;ps->top++;
}bool STEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}void STPop(ST* ps)
{assert(ps);assert(!STEmpty(ps));ps->top--;
}STDataType STTop(ST* ps)
{assert(ps);assert(!STEmpty(ps));return ps->a[ps->top - 1];
}int STSize(ST* ps)
{assert(ps);return ps->top;
}int main()
{ST s;STInit(&s);STPush(&s, 1);STPush(&s, 2);STPush(&s, 3);STPush(&s, 4);while (!STEmpty(&s)){printf("%d\n", STTop(&s));STPop(&s);}STDestroy(&s);return 0;
}
C++實現Stack:
#include<iostream>
#include<assert.h>
#include<stdbool.h>
using namespace std;typedef int STDataType;
class Stack
{
public://成員函數void Init(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (_a == nullptr){perror("malloc fail");return;}_capacity = n;_top = 0;}void Push(STDataType x){//檢查空間大小if (_top == _capacity){int newcapacity = _capacity * 2;STDataType* tmp = (STDataType*)realloc(_a, sizeof(STDataType) * newcapacity);if (tmp == NULL){perror("realloc fail");return;}_a = tmp;_capacity = newcapacity;}//入棧_a[_top++] = x;}void Pop(){assert(_top > 0);--_top;}bool Empty(){return _top == 0;}int& Top(){assert(_top > 0);return _a[_top - 1];}void Destroy(){free(_a);_a = nullptr;_top = _capacity = 0;}private://成員變量STDataType* _a;int _capacity;int _top;
};int main()
{Stack s;s.Init();s.Push(1);s.Push(2);s.Push(3);s.Push(4);while (!s.Empty()){printf("%d\n", s.Top());s.Pop();}s.Destroy();return 0;
}