柚子快報激活碼778899分享:c語言 C++類和對象(上)
??個人主頁:落葉
目錄
類的定義
類定義格式
訪問限定符
類域
實例化
實例化概念
對象??
this指針
C++和C語?實現(xiàn)Stack對?
C實現(xiàn)Stack代碼
C++實現(xiàn)Stack代碼
類的定義
類定義格式
class為定義類的關(guān)鍵字,Stack為類的名字,{}中為類的主體,注意類定義結(jié)束時后?分號不能省 略。類體中內(nèi)容稱為類的成員:類中的變量稱為類的屬性或成員變量;類中的函數(shù)稱為類的?法或 者成員函數(shù)。為了區(qū)分成員變量,?般習慣上成員變量會加?個特殊標識,如成員變量前?或者后?加_或者m 開頭,注意C++中這個并不是強制的,只是?些慣例,具體看公司的要求。C++中struct也可以定義類,C++兼容C中struct的?法,同時struct升級成了類,明顯的變化是 struct中可以定義函數(shù),?般情況下我們還是推薦?class定義類。定義在類?的成員函數(shù)默認為inline。
在類里,我們可以定義,成員變量和成員函數(shù),
#include
#include
class Stack
{
//成員函數(shù)
//成員變量
}; // 分號不能省略
下面這個代碼我們可以看到,棧的成員變量和成員函數(shù)。
#include
#include
class Stack
{
// 成員函數(shù)
//初始化
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;
}
// 成員變量
int* array;
size_t capacity;
size_t top;
}; // 分號不能省略
class為定義類的關(guān)鍵字,Stack為類的名字,{}中為類的主體,注意類定義結(jié)束時后?分號不能省 略。類體中內(nèi)容稱為類的成員:類中的變量稱為類的屬性或成員變量;類中的函數(shù)稱為類的?法或 者成員函數(shù)。C++中struct也可以定義類,C++兼容C中struct的?法,同時struct升級成了類,明顯的變化是 struct中可以定義函數(shù),?般情況下我們還是推薦?class定義類。定義在類?的成員函數(shù)默認為inline。
為了區(qū)分成員變量,?般習慣上成員變量會加?個特殊標識,如成員變量前?或者后?加_或者m 開頭,注意C++中這個并不是強制的,只是?些慣例,具體看公司的要求。
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
// 為了區(qū)分成員變量,?般習慣上成員變量
// 會加?個特殊標識,如_ 或者 m開頭
int _year; // year_ m_year
int _month;
int _day;
};
int main()
{
Date d;
d.Init(2024, 3, 31);
return 0;
}
C++升級了struct升級成了類。
#include
using namespace std;
// C++升級struct升級成了類
// 1、類??可以定義函數(shù)
// 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;
};
int main()
{
return 0;
}
訪問限定符
C++?種實現(xiàn)封裝的?式,?類將對象的屬性與?法結(jié)合在?塊,讓對象更加完善,通過訪問權(quán)限選擇性的將其接?提供給外部的??使?。public修飾的成員在類外可以直接被訪問;protected和private修飾的成員在類外不能直接被訪問,protected和private是?樣的,以后繼承章節(jié)才能體現(xiàn)出他們的區(qū)別。訪問權(quán)限作?域從該訪問限定符出現(xiàn)的位置開始直到下?個訪問限定符出現(xiàn)時為?,如果后?沒有訪問限定符,作?域就到?}即類結(jié)束。class定義成員沒有被訪問限定符修飾時默認為private,struct默認為public。?般成員變量都會被限制為private/protected,需要給別?使?的成員函數(shù)會放為public。
?protected保護和私有是一樣的。
公有可以在class外使用或修改,私有class里面才能使用或修改。
#include
#include
class Stack
{
public://公有
// 成員函數(shù)
//初始化
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;
}; // 分號不能省略
類域
類定義了?個新的作?域,類的所有成員都在類的作?域中,在類體外定義成員時,需要使? :: 作?域操作符指明成員屬于哪個類域。類域影響的是編譯的查找規(guī)則,下?程序中Init如果不指定類域Stack,那么編譯器就把Init當成全局函數(shù),那么編譯時,找不到array等成員的聲明/定義在哪?,就會報錯。指定類域Stack,就是知道Init是成員函數(shù),當前域找不到的array等成員,就會到類域中去查找。
//Stack.h文件
#include
using namespace std;
class Stack
{
public:
// 成員函數(shù)
void Init(int n = 4);
private:
// 成員變量
int* array;
size_t capacity;
size_t top;
};
//Stack.cpp文件
// 聲明和定義分離,需要指定類域
void Stack::Init(int n)
{
array = (int*)malloc(sizeof(int) * n);
if (nullptr == array)
{
perror("malloc申請空間失敗");
return;
}
capacity = n;
top = 0;
}
//test.cpp文件
int main()
{
Stack st;
st.Init();
return 0;
}
在Stack.cpp文件中,聲明和定義分離,需要使? :: 作?域操作符指明成員屬于哪個類域,Stack這個域Stack:: ,這樣就可以找到這個函數(shù)了。
實例化
實例化概念
?類類型在物理內(nèi)存中創(chuàng)建對象的過程,稱為類實例化出對象。類是對象進??種抽象描述,是?個模型?樣的東西,限定了類有哪些成員變量,這些成員變量只是聲明,沒有分配空間,?類實例化出對象時,才會分配空間。?個類可以實例化出多個對象,實例化出的對象,占?實際的物理空間,存儲類成員變量。打個??:類實例化出對象就像現(xiàn)實中使?建筑設(shè)計圖建造出房?,類就像是設(shè)計圖,設(shè)計圖規(guī)劃了有多少個房間,房間??功能等,但是并沒有實體的建筑存在,也不能住?,?設(shè)計圖修建出房?,房?才能住?。同樣類就像設(shè)計圖?樣,不能存儲數(shù)據(jù),實例化出的對象分配物理內(nèi)存存儲數(shù)據(jù)。
d1和d2是不同的對象,它們的成員變量不是一樣的,就像上面這個圖一樣,
舉例:
一張房子的圖紙,可以建很多套房子,建成房子后才可以住人。
就像類是房子一樣,要實例化對象,才可以使用。
成員函數(shù)也不是存在類里的,是存在棧里的,當類實例化出多個對象后,多個對象調(diào)用的都是同一個,成員函數(shù)。
using namespace std;
class Date
{
public:
//成員函數(shù)
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和d2
Date d1;
Date d2;
d1.Init(2024, 8, 6);
d2.Init(2025, 8, 7);
d1.Print();
d2.Print();
return 0;
}
對象??
分析?下類對象中哪些成員呢?類實例化出的每個對象,都有獨?的數(shù)據(jù)空間,所以對象中肯定包含成員變量,那么成員函數(shù)是否包含呢??先函數(shù)被編譯后是?段指令,對象中沒辦法存儲,這些指令存儲在?個單獨的區(qū)域(代碼段),那么對象中?要存儲的話,只能是成員函數(shù)的指針。再分析?下,對象中是否有存儲指針的必要呢,Date實例化d1和d2兩個對象,d1和d2都有各?獨?的成員變量_year/_month/_day存儲各?的數(shù)據(jù),但是d1和d2的成員函數(shù)Init/Print指針卻是?樣的,存儲在對象中就浪費了。如果?Date實例化100個對象,那么成員函數(shù)指針就重復存儲100次,太浪費了。這?需要再額外哆嗦?下,其實函數(shù)指針是不需要存儲的,函數(shù)指針是?個地址,調(diào)?函數(shù)被編譯成匯編指令[call地址],其實編譯器在編譯鏈接時,就要找到函數(shù)的地址,不是在運?時找,只有動態(tài)多態(tài)是在運?時找,就需要存儲函數(shù)地址,這個我們以后會講解。
上?我們分析了對象中只存儲成員變量,C++規(guī)定類實例化的對象也要符合內(nèi)存對?的規(guī)則。
內(nèi)存對?規(guī)則
第?個成員在與結(jié)構(gòu)體偏移量為0的地址處。其他成員變量要對?到某個數(shù)字(對?數(shù))的整數(shù)倍的地址處。注意:對?數(shù) = 編譯器默認的?個對?數(shù) 與 該成員??的較?值。VS中默認的對?數(shù)為8結(jié)構(gòu)體總??為:最?對?數(shù)(所有變量類型最?者與默認對?參數(shù)取最?)的整數(shù)倍。如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體對?到??的最?對?數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整體??就是所有最?對?數(shù)(含嵌套結(jié)構(gòu)體的對?數(shù))的整數(shù)倍。
我們來計算ABC實例化對象大小是多少。
using namespace std;
// 計算一下A/B/C實例化的對象是多大?
class A
{
public:
void Print()
{
cout << _ch << endl;
}
private:
char _ch;
int _i;
};
// 沒有成員變量的類對象,開1byte,占位,不存儲有效數(shù)據(jù)
// 標識對象的存在
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這個類,_ch是一個字節(jié)占用在0的位置,因為是按倍數(shù)來存的,int類型是4字節(jié)只能在4的倍數(shù)開始占用,
_i是4個字節(jié),從4開始往下占用4個字節(jié),大小是8個字節(jié)。
B這個類, 只有一個成員函數(shù),但是成員函數(shù)不是存放在類里的,是存放在棧里,所以成員函數(shù)不算類的大小。
沒有成員變量的類對象,開1byte,占位,不存儲有效數(shù)據(jù)標識對象的存在。
所以大小是1字節(jié)。
C這個類,沒有成員變量和成員函數(shù)
沒有成員變量的類對象,開1byte,占位,不存儲有效數(shù)據(jù)標識對象的存在。
所以大小是1字節(jié)。
上?的程序運?后,我們看到?jīng)]有成員變量的B和C類對象的??是1,為什么沒有成員變量還要給1個字節(jié)呢?因為如果?個字節(jié)都不給,怎么表?對象存在過呢!所以這?給1字節(jié),純粹是為了占位標識對象存在。
結(jié)果:
我們可以看到結(jié)果確實是正確的。
this指針
Date類中有 Init 與 Print 兩個成員函數(shù),函數(shù)體中沒有關(guān)于不同對象的區(qū)分,那當d1調(diào)?Init和Print函數(shù)時,該函數(shù)是如何知道應(yīng)該訪問的是d1對象還是d2對象呢?那么這?就要看到C++給了?個隱含的this指針解決這?的問題 a編譯器編譯后,類的成員函數(shù)默認都會在形參第?個位置,增加?個當前類類型的指針,叫做this指針。?如Date類的Init的真實原型為,void Init(Date* const this, int year,int month, int day)類的成員函數(shù)中訪問成員變量,本質(zhì)都是通過this指針訪問的,如Init函數(shù)中給_year賦值,this->_year = year;C++規(guī)定不能在實參和形參的位置顯?的寫this指針(編譯時編譯器會處理),但是可以在函數(shù)體內(nèi)顯?使?this指針。
在類里還有一個隱藏的this指針。
#include
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;
_month = month;
_day = day;
}
//void Print(Date* const this)
void Print()
{
//cout << this->_year << "/" << this->_month << "/" << this->_day << endl;
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
// 聲明
int _year;
int _month;
int _day;
};
int main()
{
// Date類實例化出對象d1和d2
Date d1;
Date d2;
//d1.Init(&d1, 2024, 8, 6);
//d2.Init(&d2, 2025, 8, 7);
d1.Init(2024, 8, 6);
d2.Init(2025, 8, 7);
//d1.Print(&d1);
//d2.Print(&d2);
d1.Print();
d2.Print();
return 0;
}
當我們傳參數(shù)給成員函數(shù)的時候,還隱藏傳著實例化的地址,
當然C++規(guī)定不能在實參和形參的位置顯?的寫this指針(編譯時編譯器會處理)。
我們可以看到這個,this接收實例化后的地址,,但是這個const修飾地址不能改變,但是可以修改。
當成員函數(shù)在使用的時候,lnit在訪問成員變量的時候,不是year = year這樣的,而是this->year = year這樣的,
傳過來的是實例化的地址,通過實例化地址來訪問成員變量,進行賦值,
Print那個打印函數(shù)也是一樣的,通過實例化地址進行打印。
下?通過兩個選擇題測試?下前?的知識學得如何?
1.下?程序編譯運?結(jié)果是()
A、編譯報錯 B、運?崩潰 C、正常運?
#include
using namespace std;
class A
{
public:
void Print()//this接收了p,但是p是空指針
{
//但是這里沒有對空指針進行訪問,所以不會報錯
//這里只是打印
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;//初始化為空
p->Print();//這里相當于把p的地址傳過去,這里雖然寫了p->,但是沒有解引用
return 0;
}
分析:成員函數(shù)在編譯時確定的,沒有存在對象中,所以這里雖然寫了p->,但是沒有解引用。
所以選擇【C.正常運行】
2.下?程序編譯運?結(jié)果是()
A、編譯報錯??B、運?崩潰? C、正常運?
#include
using namespace std;
class A
{
public:
void Print()//this接收了p,p是空指針
{
cout << "A::Print()" << endl;
//這里this->_a對空指針進行解引用了
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;//初始化為空
p->Print();//這里相當于把p的地址傳過去,這里雖然寫了p->,但是沒有解引用
return 0;
}
分析:成員函數(shù)這里,_a已經(jīng)對空指針解引用了,所以運行崩潰。
3. this指針存在內(nèi)存哪個區(qū)域的 ()
A. 棧??B.堆??C.靜態(tài)區(qū)??D.常量區(qū)??E.對象??
this是一個隱藏的形參,函數(shù)調(diào)用要建立棧,函數(shù)要存放在棧里,this是一個隱藏的形參,也是存放在棧里的。
在VS系列編譯器,this通過寄存器ecx傳遞。
C++和C語?實現(xiàn)Stack對?
?向?qū)ο笕?特性:封裝、繼承、多態(tài),下?的對?我們可以初步了解?下封裝。 通過下?兩份代碼對?,我們發(fā)現(xiàn)C++實現(xiàn)Stack形態(tài)上還是發(fā)?了挺多的變化,底層和邏輯上沒啥變 化。
C++中數(shù)據(jù)和函數(shù)都放到了類??,通過訪問限定符進?了限制,不能再隨意通過對象直接修改數(shù) 據(jù),這是C++封裝的?種體現(xiàn),這個是最重要的變化。這?的封裝的本質(zhì)是?種更嚴格規(guī)范的管 理,避免出現(xiàn)亂訪問修改的問題。當然封裝不僅僅是這樣的,我們后?還需要不斷的去學習。C++中有?些相對?便的語法,?如Init給的缺省參數(shù)會?便很多,成員函數(shù)每次不需要傳對象地 址,因為this指針隱含的傳遞了,?便了很多,使?類型不再需要typedef?類名就很?便在我們這個C++??階段實現(xiàn)的Stack看起來變了很多,但是實質(zhì)上變化不?。等著我們后?看STL 中的?適配器實現(xiàn)的Stack,?家再感受C++的魅?。
C實現(xiàn)Stack代碼
C語言的這個棧沒有封裝函數(shù),可以自己訪問到結(jié)構(gòu)體成員,進行修改。
這種情況很容易出現(xiàn)問題。
#include
#include
#include
#include
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++實現(xiàn)Stack代碼
向C++這種可以把成員變量進行私有,不能外部進行修改訪問,只能通過公有成員函數(shù)進行訪問修改,這樣比較安全。
#include
#include
using namespace std;
typedef int STDataType;
class Stack
{
public:
// 成員函數(shù)
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;
}
柚子快報激活碼778899分享:c語言 C++類和對象(上)
相關(guān)閱讀
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。
轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。