1.類的定義
1.1類的定義格式
? class為定義類的關鍵字,Data為類的名字,{}中為類的主體,注意類定義結束時后?分號不能省略。類體中內容稱為類的成員:類中的變量稱為類的屬性或成員變量;類中的函數稱為類的?法或者成員函數。
? 為了區分成員變量,?般習慣上成員變量會加?個特殊標識,如成員變量前?或者后?加_或者m開頭,注意C++中這個并不是強制的,只是?些慣例,具體看公司的要求。
#include<iostream>
using namespace std;//類的定義
class Data
{
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()
{Data d;d.Init(2025, 3, 23);d.Print();return 0;
}
? C++中struct也可以定義類,C++兼容C中struct的?法,同時struct升級成了類,明顯的變化是struct中可以定義函數,?般情況下我們還是推薦?class定義類。
(需要注意的是struct和class兩種類有區別,下面我會提到)
struct Data
{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;
};
? 定義在類?的成員函數默認為inline。
1.2限定訪問符
? C++?種實現封裝的?式,?類將對象的屬性與?法結合在?塊,讓對象更加完善,通過訪問權限選擇性的將其接?提供給外部的??使?。
? public修飾的成員在類外可以直接被訪問;protected和private修飾的成員在類外不能直接被訪問,protected和private是?樣的,以后繼承章節才能體現出他們的區別。
? 訪問權限作?域從該訪問限定符出現的位置開始直到下?個訪問限定符出現時為?,如果后?沒有訪問限定符,作?域就到}即類結束。
? class定義成員沒有被訪問限定符修飾時默認為private,struct默認為public。
(這就是兩種類的區別)
? ?般成員變量都會被限制為private/protected,需要給別?使?的成員函數會放為public。
1.3類域
? 類定義了?個新的作?域,類的所有成員都在類的作?域中,在類體外定義成員時,需要使?::作?域操作符指明成員屬于哪個類域。
? 類域影響的是編譯的查找規則,下?程序中Init如果不指定類域Stack,那么編譯器就把Init當成全局函數,那么編譯時,找不到array等成員的聲明/定義在哪?,就會報錯。指定類域Stack,就是知道Init是成員函數,當前域找不到的array等成員,就會到類域中去查找。
//類域
#include<iostream>
using namespace std;class Stack
{
public:void Init(int n = 4);void Print(){cout << capacity << endl;}
private:int* array;int capacity;int top;
};void Stack::Init(int n)
{array = (int*)malloc(sizeof(int) * n);if (array == nullptr)//C++中的空指針{perror("malloc fail!\n");return;}capacity = n;top = 0;
}int main()
{Stack st;st.Init();st.Print();return 0;
}
2.實例化
2.1實例化的概念
? ?類類型在物理內存中創建對象的過程,稱為類實例化出對象。
? 類是對象進??種抽象描述,是?個模型?樣的東西,限定了類有哪些成員變量,這些成員變量只是聲明,沒有分配空間,?類實例化出對象時,才會分配空間。
? ?個類可以實例化出多個對象,實例化出的對象占?實際的物理空間,存儲類成員變量。
打個??:類實例化出對象就像現實中使?建筑設計圖建造出房?,類就像是設計圖,設計圖規劃了有多少個房間,房間??功能等,但是并沒有實體的建筑存在,也不能住?,?設計圖修建出房?,房?才能住?。同樣類就像設計圖?樣,不能存儲數據,實例化出的對象分配物理內存存儲數據。
//實例化
#include<iostream>
using namespace std;class Data
{
public:void Init(int year = 2020, int month = 3, int day = 23){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private://這里只是聲明,沒有開辟空間int _year;int _month;int _day;
};int main()
{//實例化出d1和d2Data d1;Data d2;d1.Init(2020, 2, 2);d1.Print();d2.Init(2023, 2, 3);d2.Print();return 0;
}
2.2對象的大小
分析一下類對象中哪些成員呢?類實例化出的每個對象,都有獨?的數據空間,所以對象中肯定包含成員變量,那么成員函數是否包含呢??先函數被編譯后是?段指令,對象中沒辦法存儲,這些指令存儲在?個單獨的區域(代碼段),那么對象中?要存儲的話,只能是成員函數的指針。再分析?下,對象中是否有存儲指針的必要呢,Date實例化d1和d2兩個對象,d1和d2都有各?獨?的成員變量_year/_month/_day存儲各?的數據,但是d1和d2的成員函數Init/Print指針卻是?樣的,存儲在對象中就浪費了。如果?Date實例化100個對象,那么成員函數指針就重復存儲100次,太浪費了。
所以對象中只存儲成員變量,而成員函數存儲在公共代碼區中:
而C++規定實例化的對象也要符合內存對齊規則:
內存對?規則
? 第?個成員在與結構體偏移量為0的地址處。
? 其他成員變量要對?到某個數字(對?數)的整數倍的地址處。
? 注意:對?數=編譯器默認的?個對?數與該成員??的較?值。
? VS中默認的對?數為8
? 結構體總??為:最?對?數(所有變量類型最?者與默認對?參數取最?)的整數倍。
? 如果嵌套了結構體的情況,嵌套的結構體對?到??的最?對?數的整數倍處,結構體的整體??就是所有最?對?數(含嵌套結構體的對?數)的整數倍。
就拿以下代碼舉例:
//計算類A的大小
class A
{
public:void Print();void Init(int n = 4);private:char c;int n;int a;
};
存儲如下:
當類中沒有成員變量的時候,對象的大小是多少呢?就拿以下代碼來說,類B和類C中都沒有成員變量,他們兩個類所創造出來的對象的大小是1個字節,之所以給1個字節是為了占位標識對象的存在。
#include<iostream>
using namespace std;class A
{
public:void Print(){cout << _year << endl;}
private:int _year;char _name;int _std;
};class B
{
public:void Print(){cout << "B" << endl;}
};class C
{};int main()
{A a;B b;C c;cout << sizeof(a) << endl;cout << sizeof(b) << endl;cout << sizeof(c) << endl;return 0;
}
3.this指針
3.1this指針的概念
? 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指針。
? this指針一般存儲在內存中的棧區,但一個對象調用成員函數,編譯器會把對象的地址(也就是this指針的值)壓入棧中。成員函數在執行時,會從棧中獲取this指針的值,從而能訪問該對象的成員。
//this指針
#include<iostream>
using namespace std;class Data
{
public://可以在函數體內寫this,實參和形參部分不能寫void Init(int year, int month, int day){this->_year = year;this->_month = month;this->_day = day;}void Print(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;}private:int _year;int _month;int _day;
};int main()
{Data d1;Data d2;d1.Init(2023, 3, 23);d1.Print();d2.Init(2025, 1, 4);d2.Print();return 0;
}
3.1.1題目練習
我們來判斷一下以下兩段代碼的運行結果是什么?
//代碼1
#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;
}//代碼2
#include<iostream>
using namespace std;class A
{
public:void Print(){cout << "A::Print()" << endl;cout << _a << endl;//不同處}private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}
以上兩端代碼的運行結果是:代碼1正常運行,代碼2運行奔潰。
為什么呢?因為我們看到,雖然兩端代碼的p指針雖然都是空指針,但代碼1并沒有訪問類中的成員變量,所以不存在對空指針進行引用的問題,但代碼2想要打印成員變量,這就涉及了對類中的成員變量的訪問,也就是代碼2的問題是對空指針進行引用操作,導致程序的錯誤訪問。
4.C語言和C++實現棧Stack對比
? C++中數據和函數都放到了類??,通過訪問限定符進?了限制,不能再隨意通過對象直接修改數據,這是C++封裝的?種體現,這個是最重要的變化。這?的封裝的本質是?種更嚴格規范的管理,避免出現亂訪問修改的問題。
? C++中有?些相對?便的語法,?如Init給的缺省參數會?便很多,成員函數每次不需要傳對象地址,因為this指針隱含的傳遞了,?便了很多,使?類型不再需typedef?類名就很?便
1.封裝性:
C語言:使用結構體和函數來實現棧,數據(變量)和操作(函數)是分開的,需要手動傳遞結構體指針來操作數據,封裝性較差。
C++:使用類將數據(成員變量)和操作(成員函數)封裝在一起,通過對象來調整成員函數,封裝性更好,隱藏了內部實現細節。
2.安全性:
C語言:由于數據和操作分離,用戶可能會直接訪問和修改結構體中的數據,從而破壞棧的完整性。
C++:可以將成員變量私有(private),只能通過類得到成員函數來訪問和修改成員變量,提高了數據的安全性。
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 = 0;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>using namespace std;
typedef int STDataType;
class Stack
{
public:// 成員函數 void Init(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申請空間失敗");return;}_capacity = n;_top = 0;}void Push(STDataType x){if (_top == _capacity){int newcapacity = _capacity * 2;STDataType* tmp = (STDataType*)realloc(_a, newcapacity *sizeof(STDataType));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;size_t _capacity;size_t _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;
}