一、前言
從本文開始,我們就要正式來(lái)學(xué)習(xí)C++中的類和對(duì)象了,本文我將帶你一步步從C語(yǔ)言的結(jié)構(gòu)體
struct
到C++的類class
,真正搞懂有關(guān)C++的面向?qū)ο蟮娜筇卣髦?—— 封裝
- 作為讀者,可能你正在學(xué)習(xí)C語(yǔ)言,亦或者已經(jīng)開始學(xué)習(xí)C++了,也有可能你是一位C++的資深開發(fā)者或者其他領(lǐng)域的從業(yè)人員。不過這沒關(guān)系,都不會(huì)影響你閱讀本文??
- 可能你了解過面向?qū)ο蟮囊恍┱Z(yǔ)言,像Java、C#、python這些,也知道C++里面也有面向?qū)ο蟮囊恍┧枷耄悄貫楹斡挚梢詫懸恍〤語(yǔ)言的代碼,C語(yǔ)言大家一定都學(xué)過,是一門面向過程的語(yǔ)言,可是為何C++也可以跑C語(yǔ)言的代碼呢?
現(xiàn)在,我提出以下這幾個(gè)問題,看看你是否都了解??
- C++是一門面向?qū)ο蟮恼Z(yǔ)言嗎?它和面向過程有什么聯(lián)系?
- 面向?qū)ο蟮娜筇卣鳛椋悍庋b、繼承、多態(tài),你真的有去好好了解過什么是類的封裝嗎?它的好處在哪里?
- 類和結(jié)構(gòu)體之間似乎很像,它們存在什么關(guān)聯(lián)嗎?
- this指針了解多少?存放在哪里?是用來(lái)干嘛的?
接下去,就讓我們帶著疑惑,再度出發(fā),好好地探一探這些知識(shí),可能內(nèi)容會(huì)比較多,但我會(huì)盡量用生動(dòng)的語(yǔ)言和圖文并茂的方式,結(jié)合一些生活中的實(shí)際場(chǎng)景,讓你更好地理解每個(gè)知識(shí)點(diǎn)??
二、面向過程與面向?qū)ο?/h2>
??對(duì)于C語(yǔ)言而言,它完全是一門【面向過程】的語(yǔ)言。關(guān)注的是過程,分析出求解問題的步驟,通過函數(shù)調(diào)用逐步解決問題
??對(duì)于C++是基于【面向?qū)ο蟆康模P(guān)注的是對(duì)象,將一件事情拆分成不同的對(duì)象,靠對(duì)象之間的交互完成。
- 可是呢,C++為了兼容C,并沒有完全面向?qū)ο?,所以你才可以在一些C++的編譯器上去寫一些C語(yǔ)言的代碼
可是面向過程和面向?qū)ο笏鼈冎g的區(qū)別到底是怎樣的呢?可以通過一個(gè)在我們生活中最普遍的例子來(lái)說(shuō)明一下
- 若是現(xiàn)在公司要你寫一個(gè)外賣訂餐系統(tǒng),你呢只會(huì)C語(yǔ)言和C++,此時(shí)若你使用C語(yǔ)言去寫的話關(guān)注的就是從用戶下單到商家接單,最后到騎手送單這整個(gè)過程該如何去實(shí)現(xiàn);
- 但如果你使用的是C++這樣具有面向?qū)ο蟮恼Z(yǔ)言去編寫的話,那此時(shí)你要關(guān)注的就是各個(gè)對(duì)象之間會(huì)發(fā)生什么關(guān)系,對(duì)象有【用戶】、【商家】、【騎手】這三個(gè),那此時(shí)你要考慮的就是用戶下單到商家接單,最后到騎手送單,它們之間是誰(shuí)和誰(shuí)之間發(fā)生關(guān)系
三、結(jié)構(gòu)體與類
1、C++中結(jié)構(gòu)體的變化
- 之前在C語(yǔ)言中,我們?nèi)ザx一個(gè)結(jié)構(gòu)體都是這么去寫的,例如說(shuō)這里有一個(gè)鏈表結(jié)點(diǎn)的結(jié)構(gòu)體,一個(gè)是數(shù)據(jù)域,一個(gè)是指針域
struct ListNode {
int val;
struct ListNode* next;
};
- 在C++中,我們也可以這么去寫,上面說(shuō)到過C++兼容C,可是呢有一處卻可以發(fā)生變化。也就是在定義這個(gè)指針域的時(shí)候,可以不需要再寫
struct
了
struct ListNode {
int val;
ListNode* next;
};
- 通過下面兩幅圖的對(duì)比就可以很清楚地看在C++中確實(shí)在使用結(jié)構(gòu)體的時(shí)候不需要再去寫一遍
struct
這個(gè)關(guān)鍵字了,直接使用定義出來(lái)的結(jié)構(gòu)體即可;但是在C語(yǔ)言中沒有這樣的規(guī)定,所以是一定要寫的
- 不過C++相較于C語(yǔ)言可不只是多了這一點(diǎn),C語(yǔ)言結(jié)構(gòu)體中只能定義變量,在C++中,結(jié)構(gòu)體內(nèi)不僅可以定義變量,也可以定義函數(shù)【但是這在C語(yǔ)言中,是不被允許的】
知道了上面這些,其實(shí)就可以回憶我們之前在數(shù)據(jù)結(jié)構(gòu)中寫過的很多代碼,在結(jié)構(gòu)體中只是定義了一些成員變量,具體的函數(shù)都是寫在結(jié)構(gòu)體外,那現(xiàn)在知道了C++可以這么去做的話,是否可以將這些函數(shù)都寫到結(jié)構(gòu)體內(nèi)來(lái)呢?我們來(lái)試試看??
2、C++中結(jié)構(gòu)體的具體使用
下面我要使用C++去實(shí)現(xiàn)一個(gè)棧,如果有忘記的小伙伴可以再去回顧一下棧的一些知識(shí)
typedef int DataType;
struct Stack
{
void Init(size_t capacity)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _array)
{
perror("malloc申請(qǐng)空間失敗");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(const DataType& data)
{
// 擴(kuò)容...
_array[_size] = data;
++_size;
}
DataType Top()
{
return _array[_size - 1];
}
void Destroy()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
DataType* _array;
size_t _capacity;
size_t _size;
};
- 可以看到,雖然這個(gè)棧是使用C++去實(shí)現(xiàn)的,但其實(shí)和C語(yǔ)言沒有什么大致的區(qū)別,只是將這些接口函數(shù)放到了結(jié)構(gòu)體中而已。那此時(shí)便有同學(xué)問:這些變量為什么可以放在下面,不應(yīng)該在函數(shù)的上面就定義出來(lái)嗎?這點(diǎn)要注意了,這是在一個(gè)結(jié)構(gòu)體中,而不是外界的普通程序,不會(huì)像我們之前那樣需要先定義變量然后再去使用它,編譯器需要一個(gè)向上查找的過程
- 在C++的結(jié)構(gòu)體中,這個(gè)【成員變量】可以定義在結(jié)構(gòu)體 / 類的任何地方,你在何處去進(jìn)行引用都是可以的
定義出來(lái)這么一個(gè)棧的結(jié)構(gòu)體之后,我們就可以去使用了??
- 在C++中,調(diào)用一個(gè)數(shù)據(jù)結(jié)構(gòu)的算法接口不是像C語(yǔ)言必須要傳入當(dāng)前定義出來(lái)變量的地址,因?yàn)檫@些算法接口直接定義在了結(jié)構(gòu)體中,那一定可以知道這個(gè)是屬于誰(shuí)的。所以仔細(xì)觀察其實(shí)可以看出,原本我以C語(yǔ)言實(shí)現(xiàn)【?!康臅r(shí)候在每個(gè)算法接口前面都是有
Stack
,但是在C++這一塊,我卻一個(gè)都沒有加,這就是因?yàn)?mark>它們一定是屬于【?!康慕涌谒惴?,而不是其他數(shù)據(jù)結(jié)構(gòu):隊(duì)列、鏈表、二叉樹 - 那要如何去調(diào)用這個(gè)接口算法呢,很簡(jiǎn)單,回憶我們?cè)诮Y(jié)構(gòu)體章節(jié)所學(xué)習(xí)的,如何去訪問結(jié)構(gòu)體中的成員,就可以知道是使用
.
這個(gè)操作符,然后傳入對(duì)應(yīng)的參數(shù)即可
int main()
{
Stack s;
s.Init(10);
s.Push(1);
s.Push(2);
s.Push(3);
cout << s.Top() << endl;
s.Destroy();
return 0;
}
來(lái)看一下運(yùn)行結(jié)果??
通過上面所寫,使用C++去代替實(shí)現(xiàn)之前使用C語(yǔ)言寫的【棧】時(shí),發(fā)現(xiàn)完全沒問題,這下你應(yīng)該可以進(jìn)一步了解為何C++兼容C了,不過呢在C++中,這樣變量和函數(shù)存放在一起的結(jié)構(gòu)我們不叫做結(jié)構(gòu)體,而叫做【類】,可是對(duì)于類來(lái)說(shuō),在C++中也不常使用struct
這個(gè)關(guān)鍵字來(lái)定義,而是使用[class]
3、結(jié)構(gòu)體 --> 類
語(yǔ)法格式:
class className
{
// 類體:由成員函數(shù)和成員變量組成
}; // 一定要注意后面的分號(hào)
【注】:class
為定義類的關(guān)鍵字,ClassName
為類的名字,{}
中為類的主體,注意類定義結(jié)束時(shí)后面分號(hào)不能省略
- 類體中內(nèi)容稱為類的成員:類中的變量稱為類的屬性或成員變量; 類中的函數(shù)稱為類的方法或者成員函數(shù)
類的兩種定義方式
知道了一個(gè)類長(zhǎng)什么樣,接下去我們來(lái)聊聊一個(gè)類該如何去進(jìn)行規(guī)范的定義
- 聲明和定義全部放在類體中,需注意:成員函數(shù)如果在類中定義,編譯器可能會(huì)將其當(dāng)成內(nèi)聯(lián)函數(shù)處理
- 這也就是我們上面講到過有關(guān)【?!康倪@種定義,只需要將
struct
換成class
即可,這種類的定義方式簡(jiǎn)單粗暴,也是我們平常用得最多的,自己練習(xí)代碼可以直接這樣使用,但其實(shí)在日常項(xiàng)目的開發(fā)中,不建議大家這樣使用?
- 類聲明放在.h文件中,成員函數(shù)定義放在.cpp文件中,注意:成員函數(shù)名前需要加類名
::
- 重點(diǎn)來(lái)講一講這一種,這也叫做多文件形式的編寫,之間在C語(yǔ)言的學(xué)習(xí)中我們寫的【掃雷】和【三子棋】也是使用的這種分文件編寫,如果不了解的讀者一定要學(xué)會(huì)這種思想,在日常企業(yè)的開發(fā)中是經(jīng)常使用到的
stack.h
#pragma once
#include <iostream>
#include <stdlib.h>
using namespace std;
typedef int DataType;
struct Stack
{
void Init(size_t capacity);
void Push(const DataType& data);
DataType Top();
void Destroy();
DataType* _array;
size_t _capacity;
size_t _size;
};
stack.cpp
#include "stack.h"
void Init(size_t capacity)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _array)
{
perror("malloc申請(qǐng)空間失敗");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(const DataType& data)
{
// 擴(kuò)容...
_array[_size] = data;
++_size;
}
DataType Top()
{
return _array[_size - 1];
}
void Destroy()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
test.cpp
#include "stack.h"
int main()
{
Stack s;
s.Init(10);
s.Push(1);
s.Push(2);
s.Push(3);
cout << s.Top() << endl;
s.Destroy();
return 0;
}
- 以上就是有關(guān)C++中類的分文件編寫格式,其實(shí)和C語(yǔ)言的函數(shù)也相差不太多,不過從下圖可以看出,似乎是出了點(diǎn)什么問題??
- 這就是在上面說(shuō)到的:成員函數(shù)名前需要加類名::,我們?cè)诿臻g的講解中有說(shuō)到過有關(guān)【作用域】的概念,在C++中,對(duì)于一個(gè)類體而言,其實(shí)就屬于一個(gè)作用域,將成員變量和成員函數(shù)包含在里面。那么此時(shí)要在另一個(gè)
cpp
的文件中訪問這個(gè)類中定義的成員變量的話也就是訪問Stack作用域中的內(nèi)容,就要加上【域作用限定符::
】,就像下面這樣
成員變量命名規(guī)則
最后再來(lái)普及一點(diǎn),你可以自己觀察我上面在寫【棧】的時(shí)候?qū)?strong>成員變量的命名形式,前面都加上了
_
,可能你看起來(lái)會(huì)很別扭,但這卻是比較規(guī)范的一種定義形式
- 其實(shí)你可以去看一看庫(kù)里面一些變量的命名方式,很多都是采用這種下劃線的方式進(jìn)行,原因其實(shí)就在于避免造成【成員變量】和【形參】的命名沖突從而引發(fā)歧義
- 可以看到,我在下面寫了一個(gè)日期類,通過
Init()
這個(gè)函數(shù)對(duì)類中的成員變量去進(jìn)行一個(gè)初始化,觀察【成員變量】和【形參】可以發(fā)現(xiàn)我故意將它們寫成了一樣的,此時(shí)調(diào)用函數(shù)進(jìn)行初始化操作的時(shí)候會(huì)發(fā)生什么呢?
class Date {
public:
void Init(int year, int month, int day)
{
year = year;
month = month;
day = day;
}
int year;
int month;
int day;
};
通過觀察可以發(fā)現(xiàn),若是【成員變量】和【形參】的名字一樣的話,其實(shí)這個(gè)時(shí)候就會(huì)造成歧義,初始化的就不是當(dāng)前這個(gè)對(duì)象的成員變量了,如果你自己觀察就可以發(fā)現(xiàn),命名一樣的話,在VS中二者的字體都會(huì)變淡,這其實(shí)就是VS在提示你這樣的做法其實(shí)是無(wú)效的?
那要如何命名才是最規(guī)范的呢?
-
這個(gè)其實(shí)我說(shuō)了不算,要看你實(shí)際的開發(fā)的地方是如何規(guī)定的,如果是你自己的做開發(fā)的話,那建議就是【成員變量】改成
_變量名
或者是m_變量名
,但如果你在公司里面的話,內(nèi)部是如何規(guī)定的你怎么做就行了,這個(gè)沒有強(qiáng)制,只要?jiǎng)e造成相同即可 - 但是你一定在某些地方見過
this->year = year
這種寫法,確實(shí)這也可以,這里面就用到了C++類和對(duì)象中很重要的一塊叫做【this指針】,這里先不做詳解,見最后一個(gè)模塊哦??
this->year = year;
this->month = month;
this->day = day;
四、類的訪問限定符及封裝【?】
學(xué)習(xí)了上面的這些,你只是初步了解了什么是類,但是C++中的類遠(yuǎn)遠(yuǎn)不止將
struct
換成class
這簡(jiǎn)單,如果你自己觀察的話,可以發(fā)現(xiàn)我在上面的Date類中加了【public:】和【private:】這兩個(gè)東西,它們就叫做類的訪問限定符
1、C++中的三類訪問限定符
- 正式來(lái)說(shuō)一說(shuō)C++中的三類訪問限定符【public】【protected】和【private】
- 其中,對(duì)于
public
來(lái)說(shuō)指的是公有,表示從當(dāng)前public到收括號(hào)};
為止的所有成員變量或者是成員函數(shù)均為公有的,什么是公有呢?就是類內(nèi)類外都可以隨意調(diào)用訪問,不受限制 -
private
指的就是私有,這很直觀,對(duì)于共有來(lái)說(shuō)就是所有東西都是公開的,無(wú)論是誰(shuí)都可以訪問;那對(duì)于私有來(lái)說(shuō)便是無(wú)法訪問,誰(shuí)無(wú)法訪問呢?這里指的是外界無(wú)法訪問,但類內(nèi)還是可以訪問的,例如就是類內(nèi)的成員函數(shù)訪問這些成員變量是不受限制的 -
protected
指的是保護(hù),代表它會(huì)將類內(nèi)的這些東西保護(hù)起來(lái),外界無(wú)法訪問到。但對(duì)于這個(gè)限定來(lái)說(shuō)暫時(shí)可以把它當(dāng)成和private
是類同的,到了C++中的【多態(tài)】才會(huì)出現(xiàn)差異
- 光就上面這么說(shuō)你一定會(huì)覺得有些抽象,其實(shí)讀者可以將這個(gè)訪問限定符看作是一把【鎖】??,設(shè)想你家里裝了一把鎖,那么此時(shí)鎖住的就是外面的人,對(duì)家里的人是不會(huì)有影響的
接下去再來(lái)看看有關(guān)訪問限定符的一些特性指南
- public修飾的成員在類外可以直接被訪問
- protected和private修飾的成員在類外不能直接被訪問(此處protected和private是類似的)
- 訪問權(quán)限作用域從該訪問限定符出現(xiàn)的位置開始直到下一個(gè)訪問限定符出現(xiàn)時(shí)為止
- 如果后面沒有訪問限定符,作用域就到 } 即類結(jié)束。
- class的默認(rèn)訪問權(quán)限為private,struct為public(因?yàn)閟truct要兼容C)
- 主要還是來(lái)看一下這個(gè)第5點(diǎn),在C語(yǔ)言中,我們?cè)诮Y(jié)構(gòu)體中直接定義出一個(gè)成員變量的時(shí)候不需要去考慮是否可以訪問到,而是直接就去訪問了;但是在C++中,我們?cè)谠L問類中的一個(gè)成員變量的時(shí)候,是會(huì)受到限制的,我們可以來(lái)看看
- 可以看出,即使我將類中的
private
去掉的話,還是會(huì)存在【不可訪問】的現(xiàn)象,原因就是在于類內(nèi)的定義的內(nèi)容默認(rèn)訪問權(quán)限都是private
,外界是無(wú)法訪問到的
但一定會(huì)有同學(xué)有這么一個(gè)疑問,那在加上
[private]
關(guān)鍵字后,這個(gè)成員變量也是私有的呀,為什么可以對(duì)他們?nèi)ミM(jìn)行一個(gè)初始化呢?那不是訪問到了這些成員變量了
- 這一點(diǎn)要注意,當(dāng)我在初始化的時(shí)候,并沒有直接去訪問類內(nèi)的【成員變量】,而是調(diào)用了【成員函數(shù)】,在成員函數(shù)的內(nèi)部又調(diào)用了類內(nèi)的【成員變量】,上面有說(shuō)到過,對(duì)于私有的東西雖然類外是不可訪問的,但類內(nèi)還是可以訪問的,這個(gè)??只是鎖住了外來(lái)入侵者??,自己家里的人還是不受限制的
對(duì)于上面這一點(diǎn)來(lái)說(shuō),其實(shí)就又一些C++中類的封裝思想了,接下去我們就正式來(lái)談?wù)劽嫦驅(qū)ο蟮娜筇匦灾?—— 【封裝】
2、初探類的封裝??
【封裝思想】:用類將對(duì)象的屬性(數(shù)據(jù))與操作數(shù)據(jù)的方法結(jié)合在一塊,讓對(duì)象更加完善,通過訪問權(quán)限選擇性的將其接口提供給外部的用戶使用
- 封裝本質(zhì)上是一種管理,讓用戶更方便使用類。比如:對(duì)于電腦這樣一個(gè)復(fù)雜的設(shè)備,提供給用戶的就只有開關(guān)機(jī)鍵、通過鍵盤輸入,顯示器,USB插孔等,讓用戶和計(jì)算機(jī)進(jìn)行交互,完成日常事務(wù)。但實(shí)際上電腦真正工作的卻是CPU、顯卡、內(nèi)存等一些硬件元件
- 設(shè)想若是沒有將電腦中的一些元件給封裝起來(lái),就是將內(nèi)部的一些部件展現(xiàn)在用戶的眼前,那么用戶每次在將電腦開始的時(shí)候都需要那邊點(diǎn)一下,這邊開個(gè)開關(guān),使用起來(lái)就會(huì)很麻煩,所以可以看出,對(duì)于電腦來(lái)說(shuō),也是存在這么一個(gè)封裝的思想【很好地將內(nèi)部的細(xì)節(jié)給屏蔽起來(lái)了,方便管理】
這里先初步地講一下有關(guān)【類的封裝】思想,文章的后半部分會(huì)不斷地加強(qiáng)讀者對(duì)這塊的理解
五、類的實(shí)例化
當(dāng)我們寫好一個(gè)類之后,就要去把它給定義出來(lái),就好比在C語(yǔ)言中,我們要使用一個(gè)變量的話,也是要把它定義出來(lái)才行,才可以使用,例如:結(jié)構(gòu)體聲明好了之后就要將其定義出來(lái),否則是沒用的
1、變量的聲明與定義 - - 鐵瓷還會(huì)鐵嗎?
- 首先我要問你一個(gè)問題,下面的這三個(gè)成員變量是已經(jīng)被定義出來(lái)了?還是只是聲明呢?
- 讀者一定要理解【聲明】和【定義】的區(qū)別,對(duì)于聲明來(lái)說(shuō),只是告訴編譯器存在這么一個(gè)東西,但它不是實(shí)際存在于內(nèi)存中的;而對(duì)于定義來(lái)說(shuō)就是實(shí)實(shí)在在地把空間給開出來(lái),那么此時(shí)在內(nèi)存中就有它的一席之地了
可能就這么說(shuō)不太好理解,我們通過一個(gè)形象一點(diǎn)的案例來(lái)說(shuō)明??
- 你呢,背井離鄉(xiāng)在二線城市當(dāng)一個(gè)程序員??,工作了幾年也賺了不少錢,此時(shí)你就想把一直以來(lái)的出租屋換成一個(gè)嶄新的房子,想要在你所處的城市買個(gè)房,雖然交不起所有的錢,但首付還是可以的,不過呢還差那么幾萬(wàn)塊錢,于是呢就想到了你大學(xué)時(shí)候的室友,也是個(gè)鐵瓷很要好的朋友,想找他結(jié)點(diǎn)錢??
- 于是就打電話過去說(shuō):“兄弟呀,我最近想買個(gè)房,交個(gè)首付,不過手頭上還差個(gè)幾萬(wàn)塊錢,你看你有沒有一些不急著用的先借我點(diǎn),之后賺來(lái)了再還給你?!蹦锹牭轿羧盏暮糜堰@么一番話,便說(shuō):“可以可以,好兄弟開口,那必須幫忙!”于是呢他就這么答應(yīng)你了,不過也只是口頭答應(yīng),也就是一種承諾。這個(gè)口頭答應(yīng)其實(shí)指得就是【聲明】,你只能知道會(huì)有這么一筆錢給到你,但是這筆錢還沒真正到你的手里
- 不過呢,過了好幾天了,還是不見兄弟把錢打過來(lái),眼看就要交首付了,只能再給他打一個(gè)電話過去說(shuō):“兄弟,上次和你說(shuō)的那個(gè)錢怎么樣了,后天就要交首付了,你看能不能先打過來(lái)?!碑?dāng)你說(shuō)完這句話之后,其實(shí)就會(huì)出現(xiàn)兩種情況Ⅱ
- 你的兄弟回道:“哦哦,不好意思,最后手頭太忙可了,都給忘了,馬上給你轉(zhuǎn)過來(lái)?!贝藭r(shí)就聽到【
支付寶到賬5萬(wàn)元
】的聲音,那么這筆錢就真正地到你手里的,這是實(shí)實(shí)在在的,已經(jīng)存在了的事,指的就是【定義】 - 你的兄弟回道:“啊呀,這個(gè),真是不好意思啊,家里的錢都給媳婦管著呢??,它不同意我也辦法,對(duì)不住了兄弟,要不你再找找別人?!庇谑撬阈⌒囊硪淼貟斓袅穗娫挘銈z就沒有再聯(lián)系過,鐵瓷也不鐵了~
- 你的兄弟回道:“哦哦,不好意思,最后手頭太忙可了,都給忘了,馬上給你轉(zhuǎn)過來(lái)?!贝藭r(shí)就聽到【
- 對(duì)于上面的第二種情況,就很像平常在寫程序的時(shí)候出現(xiàn)鏈接錯(cuò)誤的情況,那就是【聲明了但是未定義】的這種行為。之前承諾了、聲明了,但是找你要的時(shí)候?qū)嶋H卻沒有
- 對(duì)于函數(shù)而言就是有聲明找不到定義
- 對(duì)于變量而言就是這個(gè)變量沒開空間
- 所以對(duì)于這三個(gè)成員變量來(lái)說(shuō)只是一個(gè)聲明,不是定義,并沒有開出實(shí)際的空間
那怎樣才算定義呢?又是何時(shí)開出空間,讓我們來(lái)瞧瞧【類對(duì)象的聲明與定義】??
2、類對(duì)象的聲明與定義 - - 別墅設(shè)計(jì)圖??
- 要實(shí)際地開出空間來(lái),其實(shí)指得就是要將這個(gè)類定義出來(lái),因?yàn)槟愕某蓡T變量是聲明在類里面的,那你把寫好的這個(gè)類定義出來(lái)后,【成員變量】也就對(duì)應(yīng)的在內(nèi)存中開出了一塊空間,它們是作為一個(gè)整體來(lái)進(jìn)行定義的
int main(void)
{
Date d; //類對(duì)象的實(shí)例化
return 0;
}
用類類型創(chuàng)建對(duì)象的過程,稱為類的實(shí)例化
-
類是對(duì)對(duì)象進(jìn)行描述的,是一個(gè)模型一樣的東西,限定了類有哪些成員,定義出一個(gè)類并沒有分配實(shí)際的內(nèi)存空間來(lái)存儲(chǔ)它;
- 比如:入學(xué)時(shí)填寫的【學(xué)生信息表】??,表格就可以看成是一個(gè)類,來(lái)描述具體學(xué)生信息
- 對(duì)于類來(lái)說(shuō)就像是謎語(yǔ)一樣,對(duì)謎底來(lái)進(jìn)行描述,謎底就是謎語(yǔ)的一個(gè)實(shí)例。例如謎語(yǔ):"年紀(jì)不大,胡子一把,主人來(lái)了,就喊媽媽“。這只是一個(gè)【描述】,但是實(shí)際要知道這個(gè),謎語(yǔ)在描述寫什么,這個(gè)類里面有什么東西,想要傳達(dá)出什么,就要將它實(shí)例化出來(lái),定義出來(lái),那么謎底也就揭曉了 ??謎底:山羊
- 比如:入學(xué)時(shí)填寫的【學(xué)生信息表】??,表格就可以看成是一個(gè)類,來(lái)描述具體學(xué)生信息
-
一個(gè)類可以實(shí)例化出多個(gè)對(duì)象,實(shí)例化出的對(duì)象占用實(shí)際的物理空間,存儲(chǔ)類成員變量
- 這個(gè)又怎么去理解呢?這里給讀者舉一個(gè)形象的例子:不知道你是否了解一個(gè)建筑物是如何從設(shè)計(jì)到建成的,重要要經(jīng)過很多的步驟,但是在一開始建筑師一定是要設(shè)計(jì)出一張【設(shè)計(jì)圖】來(lái),好對(duì)這個(gè)房子的整體樣式和架構(gòu)先有一個(gè)大致的輪廓,在后面才可以一步一步地慢慢實(shí)施建設(shè)計(jì)劃。
- 那其實(shí)對(duì)于這個(gè)類來(lái)說(shuō)就和【設(shè)計(jì)圖】是一樣的,比方說(shuō)現(xiàn)在我們要造一棟別墅??,那么一張圖紙??,即一個(gè)類中描述的就是這個(gè)別墅有幾層,多少個(gè)房間,門朝哪兒開,是一個(gè)大致的框架,不過呢這也就僅僅是一個(gè)設(shè)計(jì)圖罷了,還不是一個(gè)真實(shí)的別墅,不存在具體的空間,因此是不能住人的??
- 那要怎樣才能住人呢?也就是建筑師通過這張?jiān)O(shè)計(jì)圖,找?guī)讉€(gè)施工隊(duì)真實(shí)地將別墅設(shè)計(jì)出來(lái),那才可以真正地住人
- 但平常我們看到的那種高檔小區(qū)中,可不止一棟這樣的別墅,而是有幾十棟,難道設(shè)計(jì)師也要畫出幾十張?jiān)O(shè)計(jì)圖才能建完這些別墅嗎?當(dāng)然不是,對(duì)于一張?jiān)O(shè)計(jì)圖來(lái)說(shuō)是可以建造出無(wú)數(shù)的別墅,只要根據(jù)這個(gè)設(shè)計(jì)圖來(lái)建就行。那上面說(shuō)到對(duì)于設(shè)計(jì)圖來(lái)說(shuō)就是一個(gè)類,也就意味著一個(gè)類也是可以實(shí)例化出多個(gè)對(duì)象的??????
- 實(shí)例化出這個(gè)對(duì)象后也就實(shí)實(shí)在在地將空間給開出來(lái)了,那我們上面說(shuō)到過的【成員變量】,此時(shí)也開出了空間,就可以存放對(duì)應(yīng)的數(shù)據(jù)了
Date d;
d.year = 2023;
d.month = 3;
d.day = 18;
- 但對(duì)于下面這種形式去初始化成員變量是不行的,若是還沒有定義出一個(gè)對(duì)象的,成員變量不存在實(shí)際空間的,直接用類名去進(jìn)行訪問就會(huì)出錯(cuò),不過后面的文章中我會(huì)講到一種叫做靜態(tài)成員變量,用
static
進(jìn)行修飾,是可以的直接使用類名來(lái)進(jìn)行訪問的
六、類對(duì)象模型
1、成員函數(shù)是否存在重復(fù)定義?
- 上面,我們說(shuō)到了對(duì)于一個(gè)成員變量來(lái)說(shuō),若是類沒有被定義出來(lái)的話它是不存在具體空間的,那在一個(gè)類中除了成員變量外,還有【成員函數(shù)】,仔細(xì)觀察可以發(fā)現(xiàn),這似乎就已經(jīng)把成員函數(shù)定義出來(lái)了呀,那空間不是已經(jīng)存在了。 此時(shí)是外面再去實(shí)例化這個(gè)類的話,豈不是會(huì)造成重復(fù)定義了?
- 可是剛才我們有看過,在實(shí)例化出這個(gè)Date類的對(duì)象時(shí),并沒有報(bào)出重復(fù)定義這個(gè)錯(cuò)誤,而且在調(diào)用這個(gè)
Init()
和Print()
函數(shù)的時(shí)候也不會(huì)有什么問題,這是為何呢?難道這個(gè)【成員函數(shù)】和類沒什么關(guān)系嗎?它存在于類中嗎?
讓我們帶著這個(gè)疑問開始本模塊的學(xué)習(xí)
2、計(jì)算類的大小【結(jié)構(gòu)體內(nèi)存對(duì)齊】
要想知道這個(gè)類中存在多少東西,其實(shí)我們?nèi)ビ?jì)算一個(gè)它的大小即可
- 還記得結(jié)構(gòu)體內(nèi)存對(duì)齊嗎?忘記了就再去看看,下面是對(duì)應(yīng)的規(guī)則??
- 第一個(gè)成員在與結(jié)構(gòu)體偏移量為0的地址處。
- 其他成員變量要對(duì)齊到某個(gè)數(shù)字(對(duì)齊數(shù))的整數(shù)倍的地址處。
注意:對(duì)齊數(shù) = 編譯器默認(rèn)的一個(gè)對(duì)齊數(shù) 與 該成員大小的較小值。
VS中默認(rèn)的對(duì)齊數(shù)為8 - 結(jié)構(gòu)體總大小為:最大對(duì)齊數(shù)(所有變量類型最大者與默認(rèn)對(duì)齊參數(shù)取最?。┑恼麛?shù)倍。
- 如果嵌套了結(jié)構(gòu)體的情況,嵌套的結(jié)構(gòu)體對(duì)齊到自己的最大對(duì)齊數(shù)的整數(shù)倍處,結(jié)構(gòu)體的整體大小就是所有最大對(duì)齊數(shù)(含嵌套結(jié)構(gòu)體的對(duì)齊數(shù))的整數(shù)倍。
- 在C語(yǔ)言中,我們有去計(jì)算過一個(gè)結(jié)構(gòu)體的大小,那上面我們?cè)趯?duì)于結(jié)構(gòu)體和類做對(duì)比的時(shí)候說(shuō)到對(duì)于
struct
和class
都可以去定義一個(gè)類,那么結(jié)構(gòu)體內(nèi)存對(duì)齊的規(guī)則也一樣適用。不過我們只會(huì)計(jì)算成員變量的大小,那就來(lái)先計(jì)算一下這個(gè)【year】、【month】、【day】的大小
- 通過畫圖以及運(yùn)行結(jié)果可以觀察,可以得出類的大小和我們計(jì)算的【成員變量】大小竟然是一致的,那【成員函數(shù)】呢?沒有算上去嗎?還是根本不計(jì)算在內(nèi)?
3、探究類對(duì)象的存儲(chǔ)方式??
在看了上面驚人的一幕后,我們就來(lái)思考一下,對(duì)于這個(gè)類對(duì)象究竟是如何進(jìn)行存儲(chǔ)的。在下面,我給出了類對(duì)象存儲(chǔ)方式的三種設(shè)計(jì)方式,你認(rèn)為哪一種設(shè)計(jì)方式是正確的呢?
- 首先是第一種,也就是將類的【成員變量】和【成員函數(shù)】都存放在一起,其實(shí)對(duì)于每個(gè)類的成員變量來(lái)說(shuō)都是不一樣的,都有它們不同的空間,可調(diào)用的是同一份函數(shù)。如果按照此種方式存儲(chǔ),當(dāng)一個(gè)類創(chuàng)建多個(gè)對(duì)象時(shí),每個(gè)對(duì)象中都會(huì)保存一份代碼,相同代碼保存多次,浪費(fèi)空間。那么如何解決呢?
- 下面是第二種設(shè)計(jì)方式。代碼只保存一份,在對(duì)象中保存存放代碼的地址,這種方式似乎看起來(lái)不錯(cuò),你認(rèn)為可行嗎?
- 再來(lái)看看第三個(gè)設(shè)計(jì)方案。可以看到對(duì)于【成員變量】和【成員函數(shù)】完全就是分離了,存在了一個(gè)叫做公共代碼區(qū)的地方,類的所有函數(shù)都放在一個(gè)類函數(shù)成員表中
- 對(duì)于每一個(gè)對(duì)象來(lái)說(shuō)都是互相獨(dú)立的,里面只存放了各自的成員變量,而要找成員函數(shù)的話就要通過當(dāng)前這個(gè)類的對(duì)象去公共代碼區(qū)進(jìn)行調(diào)用
-
答案揭曉,那就是最后這一種,實(shí)現(xiàn)了成員與函數(shù)的分離,為什么要這么設(shè)計(jì)呢?上面其實(shí)有提到過,雖然每個(gè)對(duì)象的成員變量是不同的,各自各自的空間,但是對(duì)于成員函數(shù)來(lái)說(shuō),大家都是一樣的,例如這個(gè)
Init()
函數(shù),外界被定義出來(lái)的對(duì)象只需要調(diào)用一下這個(gè)函數(shù)去初始它自己的成員變量即可,不需要將其放在自己的類內(nèi)。 - 設(shè)想若是每個(gè)類中都寫一個(gè)這樣相同函數(shù)的話,此時(shí)每個(gè)對(duì)象就會(huì)變得非常龐大,也就是我不去調(diào)用這個(gè)函數(shù),只是將對(duì)象定義出來(lái)函數(shù)的空間就已經(jīng)會(huì)存在了,這樣的設(shè)計(jì)其實(shí)是不好的,所以我們應(yīng)該采取第三種做法
感性理解:私有場(chǎng)所與共有場(chǎng)所
但是這么說(shuō)一定是比較抽象了,我們?cè)偻ㄟ^一個(gè)生活小案例來(lái)理解一下
- 就用剛才說(shuō)到的這個(gè)別墅小區(qū)好了,那在每棟別墅里面都是房間的,像客廳、臥室、廚房、洗手間,每家每戶基本都有,但是呢每一家都有它們自己家庭的設(shè)計(jì),既然是個(gè)人別墅,那么一定不可能每棟房子的客廳、臥室、廚房、洗手間都在同一個(gè)位置吧,那就太單調(diào)了╮(╯▽╰)╭,這些房間呢值得就是【成員變量】
- 那在一個(gè)小區(qū)中,除了挨家挨戶的的私人領(lǐng)域外,一定會(huì)存在公共區(qū)域,在這些公共區(qū)域中,會(huì)有一些公共場(chǎng)所,例如像籃球場(chǎng)、咖啡館、游泳館、小賣部或是健身器材等等,對(duì)于這個(gè)公共區(qū)域你可以理解為【公共代碼區(qū)】,而對(duì)于這些公共場(chǎng)所、設(shè)施你可以理解為【成員函數(shù)】
- 那其實(shí)這就很形象了,【成員變量】是每個(gè)對(duì)象各自的,由于類的封裝特性別人無(wú)法輕易訪問,可是呢對(duì)于這個(gè)【成員函數(shù)】來(lái)說(shuō),是大家共有的,可以一起使用,所以不需要放在自己家里,除非你真的很有錢,在一個(gè)別墅小區(qū)再自己建一些私人的游泳池、籃球場(chǎng)和小賣部??
4、空類大小計(jì)算【面試考點(diǎn)?】
- 學(xué)習(xí)了如何去計(jì)算一個(gè)類之后,接下去請(qǐng)你來(lái)判別一下下面三個(gè)類的大小分別為多少
// 類中既有成員變量,又有成員函數(shù)
class A1 {
void f1() {}
private:
int a;
};
// 類中僅有成員函數(shù)
class A2 {
void f1(){}
};
// 類中什么都沒有---空類
class A3 {};
- 首先是類
A1
,有一個(gè)成員變量,那經(jīng)過上面的學(xué)習(xí)可以得知成員函數(shù)是不存在于類中,又因?yàn)檎驼?個(gè)字節(jié),所以很容易可以得知A3的大小為4 - 接下去對(duì)于類
A2
,只有一個(gè)成員函數(shù)f1()
,沒有成員變量,那【sizeof(A2)】的結(jié)果會(huì)是多少呢?一會(huì)看運(yùn)行結(jié)果后再來(lái)分析 - 接下去是類
A3
,對(duì)于這個(gè)類來(lái)說(shuō)既沒有成員函數(shù)也沒有成員變量,那它的大小會(huì)是多少呢?0嗎?
我們來(lái)看一下運(yùn)行結(jié)果
- 通過觀察可以得知,似乎只算對(duì)了第一個(gè)類A1的大小,但是前兩個(gè)類的大小為什么都是1呢?這相信讀者也是非常疑惑吧?立馬為你來(lái)解答??
- 一個(gè)類的大小,實(shí)際就是該類中”成員變量”之和,當(dāng)然要注意內(nèi)存對(duì)齊。但是對(duì)于空類的大小卻不太一樣,空類比較特殊,編譯器給了空類一個(gè)字節(jié)來(lái)唯一標(biāo)識(shí)這個(gè)類的對(duì)象【這1B不存儲(chǔ)有效數(shù)據(jù),為一個(gè)占位符,標(biāo)識(shí)對(duì)象被實(shí)例化定義出來(lái)了】
上面的這個(gè)概念在筆試面試中都有可能會(huì)涉及,準(zhǔn)備校招的同學(xué)要重視
七、this指針【?重點(diǎn)掌握?】
1、提問:如何區(qū)分當(dāng)前初始化對(duì)象?
- 繼續(xù)來(lái)回顧一下上面所寫的Date日期類,有三個(gè)成員變量和兩個(gè)成員函數(shù)
class Date {
public:
//定義
void Init(int year, int month, int day)
{
_year = year;
_year = month;
_year = day;
}
void Print()
{
cout << "year:" << _year << endl;
cout << "month:" << _year << endl;
cout << "day:" << _year << endl;
}
private:
int _year; //僅僅是聲明,并沒有開出實(shí)際的空間
int _month;
int _day;
};
- 那現(xiàn)在我定義出一個(gè)變量后開始傳遞數(shù)據(jù),然后初始化
d1
里面的【year】【month】【day】,然后在內(nèi)部Init()函數(shù)中使用_year = year
這樣的方式來(lái)進(jìn)行初始化,此時(shí)右邊的[year]
是外部傳遞進(jìn)來(lái)的2023,[_year]
是內(nèi)部的成員變量,但是仔細(xì)回憶一下,剛才我們有說(shuō)到這個(gè)[_year]
只是類內(nèi)部聲明的,并沒有被定義出來(lái)呀,那要怎么賦值初始化呢? - 有同學(xué)說(shuō):外面不是定義出這個(gè)對(duì)象d1了,那么三個(gè)成員變量的空間自然也就開出來(lái)了,是的,這沒錯(cuò)
Date d1;
d1.Init(2023, 3, 18);
- 可是現(xiàn)在呢,我又定義了一個(gè)對(duì)象,此時(shí)就會(huì)存在兩個(gè)對(duì)象d1和d2,然后分別去調(diào)用這個(gè)Init()函數(shù)來(lái)初始化自己的成員變量,那外部傳入實(shí)參的時(shí)候是可以分清的,但是傳入到內(nèi)部時(shí)
_year = year
中的[_year]
要怎么區(qū)分這是d1還是d2的成員變量呢?若有又定義了一個(gè)d3呢?如何做到精準(zhǔn)賦值無(wú)誤? - 在外部定義出來(lái)的對(duì)象調(diào)用的時(shí)候可以很明確是哪個(gè)對(duì)象調(diào)的,但是到了函數(shù)內(nèi)部又是辨別的呢?對(duì)于成員函數(shù)來(lái)說(shuō)存放在公共代碼區(qū),大家都可以調(diào)用,那即使調(diào)用了也沒有傳入當(dāng)前對(duì)象的地址呀,函數(shù)內(nèi)部怎么知道要給哪個(gè)對(duì)象初始化成員變量呢?
好,就讓我們帶著這些問題,進(jìn)入下一小節(jié)的學(xué)習(xí)??
Date d1;
Date d2;
d1.Init(2023, 3, 18);
d2.Init(2024, 3, 18);
2、深度探究this指針的各種特性【原理分析】
面對(duì)上面情況,其實(shí)就可以使用到C++中的
this指針
了,這個(gè)我在上面有提過一嘴,還有印象嗎
- 上面講了這么多不知讀者是否關(guān)注到我說(shuō)的一點(diǎn):外界無(wú)法傳入當(dāng)前對(duì)象的地址給到被調(diào)用的成員函數(shù)
- 那我現(xiàn)在要說(shuō)的是,其實(shí)這件事情是做了的,當(dāng)這個(gè)成員函數(shù)被調(diào)用的時(shí)候,編譯器就會(huì)自動(dòng)給在這個(gè)函數(shù)的最前面加上一個(gè)形參,他就是專屬于當(dāng)前類的一個(gè)指針,就是
this指針
//void Init(int year, int month, int day)
void Init(Date* this, int year, int month, int day)
- 那么形參部分改變了,實(shí)參也需要修改,那要傳遞什么呢?沒錯(cuò),就是當(dāng)前對(duì)象的地址
//d1.Init(2023, 3, 18);
d1.Init(&d1, 2023, 3, 18);
-
那么當(dāng)this接受了當(dāng)前對(duì)象的地址之后,編譯器就將代碼轉(zhuǎn)換成了下面這種形式,【this】在英文單詞中指的就是當(dāng)前,那么意思就很明確了,為當(dāng)前對(duì)象的
year
、month
和day
進(jìn)行初始化。隨著每次的傳入的對(duì)象地址不同,this指針就會(huì)通過不同的地址去找到內(nèi)存中對(duì)應(yīng)的那塊地址中的成員變量,進(jìn)行精準(zhǔn)賦值
- 不過通過觀察可以發(fā)現(xiàn),似乎我們自己去加上這一個(gè)參數(shù)好像是行不通,編譯器報(bào)出了很多錯(cuò)誤,
看看下面這段話就知道為什么了??
- C++編譯器給每個(gè)“非靜態(tài)的成員函數(shù)“增加了一個(gè)隱藏的指針參數(shù),讓該指針指向當(dāng)前對(duì)象(函數(shù)運(yùn)行時(shí)調(diào)用該函數(shù)的對(duì)象),在函數(shù)體中所有“成員變量”的操作,都是通過該指針去訪問。只不過所有的操作對(duì)用戶是透明的,即用戶不需要來(lái)傳遞,編譯器自動(dòng)完成
- 所以這個(gè)this指針我們是不可以加的,編譯器會(huì)自動(dòng)幫我們加上,并且傳遞當(dāng)前對(duì)象的地址
不過,雖然我們不能傳遞觀察,但可以通過這個(gè)隱藏的this指針來(lái)看看是否真的傳遞了當(dāng)前對(duì)象的地址進(jìn)去
了解了this指針的基本原理后,我們來(lái)聊聊它的相關(guān)特性
-
this指針的類型:類類型* const(
Date* const
),即成員函數(shù)中,不能給this指針賦值- 對(duì)于this指針來(lái)說(shuō),是被
const
常所修飾的,為【指針常量】,對(duì)于指針本身的指向是不可修改的,但是指針?biāo)赶虻膬?nèi)容可以通過解引用的方式來(lái)修改。如果不是很清楚這一塊可以看看常量指針與指針常量的感性理解
- 對(duì)于this指針來(lái)說(shuō),是被
- 只能在“成員函數(shù)”的內(nèi)部使用
- 這一點(diǎn)要牢記,對(duì)于this指針而言,只可以在類的成員函數(shù)內(nèi)部進(jìn)行使用,是不可以在外部進(jìn)行使用的,因?yàn)樗亲鳛橐粋€(gè)成員函數(shù)的形參,若是沒有傳遞給當(dāng)前對(duì)象地址的話,那么它的指向是不確定的,但當(dāng)進(jìn)入成員函數(shù)內(nèi)部時(shí),編譯器底層一定調(diào)用了這個(gè)this指針,為其傳遞了對(duì)象的地址,此時(shí)在內(nèi)部再去使用的話是不會(huì)有問題的
-
this指針本質(zhì)上是“成員函數(shù)”的形參,當(dāng)對(duì)象調(diào)用成員函數(shù)時(shí),將對(duì)象地址作為實(shí)參傳遞給this形參。所以對(duì)象中不存儲(chǔ)this指針
- 這一點(diǎn)上面也有強(qiáng)調(diào)過,this指針是作為形參來(lái)使用,那對(duì)于函數(shù)形參,我們?cè)诤瘮?shù)棧幀一文有深入研究過它是需要進(jìn)行壓棧的,那就要建立函數(shù)棧幀,可以很明確它就是存放在棧區(qū)的,而不是存放在對(duì)象中,這一點(diǎn)下面有一道面試題也是涉及到,再做細(xì)講
- 而且剛才在求解類的大小時(shí),通過結(jié)構(gòu)體內(nèi)存對(duì)齊可以很明確地看出除了【成員變量】之外的其他的東西都是不計(jì)算在內(nèi)的
-
this指針是“成員函數(shù)”第一個(gè)隱含的指針形參,一般情況由編譯器通過ecx寄存器自動(dòng)傳遞,不需要用戶傳遞
- 這一點(diǎn)我們可以通過匯編指令來(lái)看??
- 可以觀察到,傳遞進(jìn)函數(shù)
Init()
的參數(shù)都會(huì)被壓入棧中,不過可以觀察到,由于?!鞠冗M(jìn)后出】的性質(zhì),是從第4個(gè)參數(shù)開始?jí)簵5模羰前凑赵镜娜齻€(gè)參數(shù)來(lái)說(shuō)應(yīng)該會(huì)壓三次,但是看到2023被Push
進(jìn)去之后還有一個(gè)[d1]需要被lea(load effective address)
進(jìn)去,不過并不是直接加載,而是放到一個(gè)寄存器ecx中再加載,這個(gè)d1指的其實(shí)就是對(duì)象d1的地址 - 通過匯編指令可以把底層的邏輯看得很清楚,觀察到確實(shí)是存在this指針接受當(dāng)前調(diào)用對(duì)象的地址
3、this指針的感性理解
說(shuō)到了這么多有關(guān)this指針的特性,有些特性對(duì)大家來(lái)說(shuō)可能還是比較難以理解,接下去我會(huì)通過三個(gè)生活中的小場(chǎng)景帶你好好理解一下??
- 夏天到了,呆在家里一定會(huì)很熱,一天到晚打空調(diào)對(duì)身體又不好,此時(shí)就會(huì)想到去游泳館游泳,那去游泳的話肯定要換上專門的衣物,去哪里換呢?當(dāng)然是更衣室了,有過經(jīng)歷的同學(xué)一定知道當(dāng)你去更衣室換衣服的時(shí)候,前臺(tái)就會(huì)給你一個(gè)手環(huán),可以識(shí)別感應(yīng)里面的柜子,一個(gè)人一個(gè)柜子可以放置自己的私人物品。然后就把這個(gè)手環(huán)套在你的手上,最后當(dāng)你游完泳后要怎么知道那個(gè)是你的柜子呢?那是通過這個(gè)手環(huán)來(lái)進(jìn)行感應(yīng)打開柜門取出自己的衣物【這個(gè)手環(huán)就是用來(lái)識(shí)別的,別人的手環(huán)打不開你的柜子】
- 在大學(xué)生活中,每個(gè)人一定都有自己的校園卡,這張校園卡呢可以用來(lái)吃飯、洗澡、接水,甚至可以代替人臉識(shí)別,所以在這個(gè)校園中,校園卡就是你的身份象征,每個(gè)人都是唯一的
- 住過小區(qū)的一定都知道,現(xiàn)在的小區(qū)管理制度是越來(lái)越嚴(yán)了,出入呢都需要這個(gè)門禁卡,才可以證明你的身份,是屬于這個(gè)小區(qū)的,防止外來(lái)人員入室盜竊,所以這個(gè)門禁卡就是你身份的象征【有沒帶門禁進(jìn)不去單元門的嗎??】
通過上面的三個(gè)生活小案例,相信你對(duì)this指針一定有有了自己的理解
4、兩道奪命面試題??
本小節(jié)的內(nèi)容可能會(huì)讓你感到非??菰铮绻麤]有校招需求的讀者可以選擇跳過,當(dāng)然也可以瀏覽一下嘍??
this指針存放在哪里?
先來(lái)看看第一位同學(xué)的回答:
?? this指針是存放對(duì)象地址的,和對(duì)象存在關(guān)系。應(yīng)該是存放在對(duì)象中的把??
- 聽完了他這一番話,我差點(diǎn)沒拿起我的四十米大刀??掄過去(╯▔皿▔)╯,剛才我們講到了有關(guān)this指針的特性,現(xiàn)在再重復(fù)一遍。它是作為成員函數(shù)的隱含形參,既然是函數(shù)形參的話,那就需要壓棧、建立棧幀,所以這個(gè)this指針是存放在棧上的。
- 不過在VS中,編譯器會(huì)使用寄存器
ecx
進(jìn)行一個(gè)臨時(shí)保存,剛才我們也有通過匯編指令進(jìn)行一個(gè)查看
再來(lái)聽聽第二位同學(xué)的回答:
?? 剛才不是說(shuō)這個(gè)成員函數(shù)是存放在公共代碼區(qū)的嗎,那隱藏形參this是屬于這個(gè)函數(shù)的,為何沒有存放在公共代碼區(qū)呢?
- 這個(gè)問題其實(shí)問得不錯(cuò),不過這屬于一個(gè)知識(shí)混淆了,不要把【棧區(qū)】和【公共代碼區(qū)】混為一談
-
我們現(xiàn)在對(duì)一段程序進(jìn)行編譯,實(shí)例化出一個(gè)對(duì)象后這個(gè)對(duì)象就是存在【棧區(qū)】中的,但是成員函數(shù)不存放在其中,因?yàn)槌蓡T函數(shù)是屬于公共區(qū)域的一部分,所以在編譯完成之后,
call
指令的地址不在對(duì)象中找,而去【公共代碼區(qū)】中找,為什么要去這個(gè)公共區(qū)找呢?因?yàn)槌蓡T函數(shù)被編譯出來(lái)的這些指令(剛才看的指令)存放在這里面, 而成員函數(shù)內(nèi)部的一些形參、臨時(shí)變量
則不在這里面,它們都是存放在【棧區(qū)】中的。所以this指針不在【公共代碼區(qū)】,而在【棧區(qū)】 - 聽完我的這番話后,這個(gè)同學(xué)似乎就明白了一些東西
this指針可以為空嗎?
好,接下去我們?cè)賮?lái)看第二個(gè)面試題
??請(qǐng)問下面程序編譯運(yùn)行結(jié)果是? A、編譯報(bào)錯(cuò) B、運(yùn)行崩潰 C、正常運(yùn)行
class Date {
public:
//定義
//void Init(Date* this, int year, int month, int day)
void Init(int year, int month, int day)
{
cout << "this:" << this << endl;
this->_year = year;
this->_month = month;
this->_day = day;
}
void Print()
{
cout << "Print()" << endl;
}
private:
int _year; //僅僅是聲明,并沒有開出實(shí)際的空間
int _month;
int _day;
};
int main()
{
Date* p = nullptr;
p->Print();
}
運(yùn)行結(jié)果:
- 看了上面的運(yùn)行結(jié)果,你是否感覺很吃驚??,為何沒有【運(yùn)行崩潰】呢?為什么可以對(duì)一個(gè)空指針去解引用呢?
- 可以知道這個(gè)Print()是Date類的一個(gè)成員函數(shù),那既然是成員函數(shù)的話就可以調(diào)用this指針,我們來(lái)試試
- 可以看到this指針?biāo)邮盏降牡刂窞榭?,這很明確,我們?cè)谕饨缯{(diào)用這個(gè)函數(shù)的對(duì)象指針就是空的。那可以看出其實(shí)從調(diào)用到函數(shù)內(nèi)部的執(zhí)行完全沒有進(jìn)行一次解引用的操作,所以才不會(huì)引發(fā)空指針異常的問題
- 不過呢,就上面的這一個(gè)還考察不出一個(gè)人對(duì)this指針的理解,一般考了上面這個(gè)還會(huì)接連著考下面這個(gè)
p->Init(2023, 3, 19);
運(yùn)行結(jié)果:
- 很明顯,若是去調(diào)用
Init()
初始化函數(shù)的話就會(huì)發(fā)生空指針異常的問題,這是為什么呢?
??有同學(xué)說(shuō):一看就是這個(gè)this->
的問題,很明顯的解引用嘛,去掉不就好了??
- 然后繼續(xù)給他看了這個(gè),他便陷入了沉思??
- 這里要說(shuō)明一點(diǎn),本模塊一開始我們初談
this
指針的時(shí)候說(shuō)到, 在成員函數(shù)內(nèi)部調(diào)用成員變量時(shí),可以在前面加上this->
去指向,當(dāng)然也可以不加,因?yàn)槭悄J(rèn)帶有的,所以在這里無(wú)論是顯式地加上還是隱式讓他保留,都會(huì)產(chǎn)生一個(gè)【指針指向】,那我們?cè)贑語(yǔ)言的指針章節(jié)有談到,對(duì)于指針指向而言其實(shí)就是解引用,所以這才造成了出錯(cuò)
通過上面這兩個(gè)函數(shù)的調(diào)用,相信你對(duì)this指針傳參機(jī)制有了一些基本認(rèn)識(shí)。但還是沒有清楚這其中的原理,接下來(lái)我便帶你分析一下里面的原理
- 上面有說(shuō)到過,對(duì)于成員函數(shù)而言是不存在于類內(nèi)部的,而是存放于【公共代碼區(qū)】,所以對(duì)于上面的這兩個(gè)函數(shù)而言都不去類里找,即【棧區(qū)】里找,而是通過函數(shù)名修飾后的名稱去【公共代碼區(qū)】里找
- 對(duì)于
Print()
函數(shù)而言,并沒有涉及到訪問成員變量,那你可以把它理解為在別人家的小區(qū)里的公共藍(lán)球場(chǎng)??打了個(gè)籃球,那這其實(shí)是屬于公共的區(qū)域,而沒有闖入別人的私人領(lǐng)地 -
使用空指針去調(diào)用了一下這個(gè)函數(shù),此時(shí)傳遞這個(gè)空對(duì)象的地址給到成員函數(shù)內(nèi)部的【this】指針,然后我去打印了一下這個(gè)this指針,那也就是將這個(gè)對(duì)象的地址給打印出來(lái),因?yàn)樗且粋€(gè)空對(duì)象,所以結(jié)果是
00000000
。但是并沒有再做任何的事了,所以不會(huì)有問題 - 就比方說(shuō)你手里有一把剛從刀柄里拔出來(lái)的刀??,但也只是從刀柄里拔出來(lái)看看,并沒有那它去做任何壞事,傷害到別人,那也沒什么問題嘛╮(╯▽╰)╭
- 當(dāng)在調(diào)用這個(gè)
Init()
函數(shù)時(shí),也不是去【棧區(qū)】里找,而是去【公共代碼區(qū)】里找,也是一樣首先打印了一下傳入的空對(duì)象地址,不過接下來(lái)的操作就不對(duì)了!只要是要訪問當(dāng)前類的成員變量時(shí),都需要使用到this
指針來(lái)進(jìn)行指向,只是因?yàn)樗且粋€(gè)隱式形參罷了 - 打個(gè)比方,你又進(jìn)到別人家小區(qū)了,但是呢此時(shí)卻沒有經(jīng)過別人的同意隨意地躺在別人家沙發(fā)上??上,這個(gè)沙發(fā)就屬于別人的私有物品,也可以看做是成員變量,剛才我們并沒有訪問成員變量,所以是沒問題的,但是現(xiàn)在卻訪問了一個(gè)私有的東西,那報(bào)出錯(cuò)誤也是很正常的
??那有同學(xué)又說(shuō):那我在調(diào)用Init()
的時(shí)候不傳空對(duì)象不就好了,直接調(diào)用
Init(2023, 3, 19);
- 你覺得這樣可以嗎?我讓他罰站了半個(gè)小時(shí)~
-
上面我們說(shuō)到,若是一個(gè)定義出來(lái)的對(duì)象去調(diào)用成員函數(shù),會(huì)有一個(gè)隱藏的形參
this
接受傳遞過來(lái)的對(duì)象地址,以此去調(diào)用不同對(duì)象的成員變量。但是就上面這個(gè)調(diào)用,連執(zhí)行的對(duì)象都沒有,你覺得this
指針會(huì)接收到什么東西呢? - 通過運(yùn)行結(jié)果可以看出,連編譯都過不了,談何運(yùn)行呢?
好,看完了上面這一些,相信你對(duì)this指針的了解一定更加深刻了,我們?cè)賮?lái)看最后一個(gè)??
??還是調(diào)用剛才的Print()
函數(shù) A、編譯報(bào)錯(cuò) B、運(yùn)行崩潰 C、正常運(yùn)行
(*ptr).Print();
- 在學(xué)習(xí)了上面的兩道題后,相信你一定會(huì)覺得這是個(gè)【編譯報(bào)錯(cuò)】或者是【運(yùn)行奔潰】,不過結(jié)果卻大相徑庭。竟然完全沒有問題,可以正常運(yùn)行!??
這是什么呢???
- 若是你完全不懂這一點(diǎn)的話,應(yīng)該再回過去看看指針的解引用那塊,難道一個(gè)指針去進(jìn)行指向
->
或者是解引用*
就一定是在對(duì)指針進(jìn)行解引用嘛 -
不,會(huì)不會(huì)解引用取決于要不要到指向的對(duì)象中去找成員變量,而不是看有沒有
”->“
。因?yàn)閷?duì)于編譯器來(lái)說(shuō),是將代碼的這些語(yǔ)法轉(zhuǎn)換為指令,我們要去看的是這些匯編指令
- 通過上面的觀察可以看出,從【匯編角度】而言,其實(shí)編譯器把
->
和*
解析成了同一種方式,至于內(nèi)部的邏輯是怎么樣的,上面已經(jīng)講過了,此處不再贅述
??最后,有位同學(xué)又提出了這樣的寫法,蠻不錯(cuò)的,給讀者分享一下
- 你認(rèn)為下面這種直接用類名然后【域作用限定符
::
】的方式去訪問可行嗎?
Date::Print();
運(yùn)行結(jié)果如下:
- 對(duì)于這種調(diào)用形式,我們?cè)谏厦嫫鋵?shí)也提到過,只有靜態(tài)的成員函數(shù)或者是靜態(tài)的成員變量才可以用類名直接訪問,所以這樣是不可以的,也是一種沒有傳入當(dāng)前對(duì)象地址給this指針的形式
??為什么沒有讓她罰站呢,怎么能讓女生??罰站呢,是吧??
5、一道筆試題?
??下面程序段包含4個(gè)函數(shù),其中具有隱含this指針的是( )
?int f1();
class T
{
public:static int f2();
private:friend int f3();
protect:int f4();
};
【答案】:f4
【解析】:
- f1為全局函數(shù),不是類的成員函數(shù),因此沒有this指針
- f2為static函數(shù),放在公共代碼段,不屬于類,因此沒有this指針
- f3為友元函數(shù),不屬于類,因此沒有this指針
- f4為成員函數(shù),在類T中具有保護(hù)權(quán)限,因此有this指針
好,接下去就對(duì)上面的筆試和面試題所引申出來(lái)的知識(shí)點(diǎn)做一個(gè)總結(jié)與回顧,我們就進(jìn)入下一模塊
【總結(jié)一下】:
- 對(duì)于this指針而言,是存放在【棧區(qū)】的,雖然其是屬于成員函數(shù)的一個(gè)隱式形參,但是卻不和成員函數(shù)一樣存放在【公共代碼區(qū)】,對(duì)于成員函數(shù)而言只是編譯器將其解析后的匯編指令存放在公共區(qū)罷了,而對(duì)于函數(shù)內(nèi)部的形參和臨時(shí)變量,都還是存放在棧區(qū)的,是需要開辟棧幀、壓棧的↓
-
this指針可以為空,但能不能正常運(yùn)行取決于你如何去調(diào)用,僅僅是傳遞了空對(duì)象地址但沒有進(jìn)行解引用的話不會(huì)出現(xiàn)問題。但若是在成員函數(shù)內(nèi)部訪問成員變量的話,無(wú)論你有無(wú)給出
this->
,都會(huì)造成解引用從而導(dǎo)致空指針異常?的問題。 -
看一個(gè)指針是否產(chǎn)生了介意用不是光光看
->
或者*
,而是要去觀察底層的匯編指令如何執(zhí)行,這才是計(jì)算機(jī)內(nèi)部的真正執(zhí)行邏輯
八、C和C++實(shí)現(xiàn)棧的對(duì)比
好,講了這么多,相信讀者對(duì)C++的類這一塊一定有了自己的理解,本模塊,我將通過C語(yǔ)言和C++分別去實(shí)現(xiàn)一個(gè)【?!?,通過觀察來(lái)讓讀者看出C++到底是如何實(shí)現(xiàn)封裝的
1、代碼展示
C語(yǔ)言的相關(guān)代碼可以看這篇文章 鏈接,這里就不貼代碼了
主要來(lái)展示一下C++的代碼,下面是比較規(guī)范的聲明與定義分離的形式,可以先看看
stack.h
typedef int DataType;
class Stack
{
public:
void Init(size_t capacity = 4);
void Check_Capacity();
void Push(const DataType& data);
void Pop();
bool Empty();
DataType Top();
void Destroy();
private:
DataType* _array;
size_t _capacity;
size_t _top;
};
stack.cpp
#include "stack.h"
void Stack::Init(size_t capacity)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _array)
{
perror("malloc申請(qǐng)空間失敗");
return;
}
_capacity = capacity;
_top = 0;
}
void Stack::Check_Capacity()
{
if (_top == _capacity)
{
DataType* tmp = (DataType*)realloc(_array, sizeof(DataType) * _capacity * 2);
if (nullptr == tmp)
{
perror("fail realloc");
exit(-1);
}
_array = tmp;
_capacity = _capacity * 2;
}
}
void Stack::Push(const DataType& data)
{
// 擴(kuò)容
Check_Capacity();
_array[_top] = data;
++_top;
}
bool Stack::Empty()
{
return _top == 0;
}
void Stack::Pop()
{
assert(_top > 0);
assert(!Empty());
_top--;
}
DataType Stack::Top()
{
return _array[_top - 1];
}
void Stack::Destroy()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_top = 0;
}
}
test.cpp
int main(void)
{
Stack st;
st.Init();
st.Push(1);
st.Push(2);
st.Push(3);
size_t top = st.Top();
cout << top << endl;
st.Pop();
top = st.Top();
cout << top << endl;
st.Destroy();
return 0;
}
運(yùn)行結(jié)果:
- 其實(shí)除了C/C++之外,像其他語(yǔ)言例如Java都是可以實(shí)現(xiàn)一個(gè)棧的【鏈接】,只不過語(yǔ)言的立場(chǎng)、語(yǔ)法的細(xì)節(jié)不一樣而已
- 如果平常自己懶得去寫,直接使用庫(kù)里面給我們寫好的就行,C++和Java都有現(xiàn)成的庫(kù)給我們封裝好了,只需要調(diào)用一下需要的API即可,像我們后續(xù)會(huì)學(xué)習(xí)的STL中
stack
,里面就有上面所學(xué)的全部?jī)?nèi)容
2、C語(yǔ)言特性分析
- 數(shù)據(jù)和方法是分離的
- 這一點(diǎn)其實(shí)很好觀察,當(dāng)我們使用C語(yǔ)言去寫一個(gè)棧的時(shí)候,是將存放數(shù)據(jù)的數(shù)組、棧頂指針、容量大小都放在結(jié)構(gòu)體中,其他算法接口再另外分離寫,二者的關(guān)聯(lián)性并不是很大
-
數(shù)據(jù)訪問控制是自由的,不受限制
- 還有第二點(diǎn),就是在C語(yǔ)言中,我們?nèi)ピL問一個(gè)數(shù)據(jù)的時(shí)候,其實(shí)是比較自由的,不會(huì)受到過多的限制。
- 舉個(gè)例子,當(dāng)初我們?cè)跀?shù)據(jù)結(jié)構(gòu)中寫棧的
StackTop()
時(shí),產(chǎn)生了分歧,有的同學(xué)說(shuō)直接像下面這樣取就可以了
int top1 = st->a[st->top - 1];
- 但是呢有的同學(xué)卻覺得即使是再小的功能也應(yīng)該封裝成為一個(gè)函數(shù)的形式,之后再去進(jìn)行調(diào)用
int top2 = StackTop(&st);
- 我贊同了上面這種寫法,還記得為什么嗎?因?yàn)閷?duì)于外界來(lái)說(shuō)是無(wú)法知曉你底層的邏輯實(shí)現(xiàn)是怎樣的,若是寫成第一種形式的話,調(diào)用者就得知道當(dāng)前這個(gè)棧在初始化的時(shí)候
top
指針初始化的值是多少,是-1呢?還是0呢? - 但若是采用第二種寫法,調(diào)用者完全不需要關(guān)心這個(gè)函數(shù)內(nèi)部的實(shí)現(xiàn)細(xì)節(jié),只需要進(jìn)行調(diào)用即可,就會(huì)顯得很方便
asssert(top > 0);
- 不僅如此,若是采取第一種形式的話,訪問者不僅要知道底層top指針初始化為多少,而且還要知道當(dāng)前棧中還有多少數(shù)據(jù)了,因?yàn)槲覀冊(cè)谌m斣厍岸紩?huì)去使用一個(gè)
assert()
進(jìn)行檢查,此時(shí)若是這個(gè)棧頂指針<= 0
的話也就表明棧里面沒有元素了,再去通過數(shù)組訪問的話就會(huì)造成有越界的風(fēng)險(xiǎn)
【總結(jié)一下】;
-
使用C語(yǔ)言去進(jìn)行訪問的時(shí)候過于自由,因此需要考慮到很多因素
- 需要知道底層的top的初始化
- 有越界的風(fēng)險(xiǎn),不安全
C語(yǔ)言語(yǔ)法的松散性 - - 過紅綠燈還是慢一點(diǎn)吧??
雖說(shuō)使用C語(yǔ)言這樣去進(jìn)行訪問確實(shí)存在一些缺陷,那為什么標(biāo)準(zhǔn)委員會(huì)沒有改進(jìn)這一點(diǎn)呢?還是允許這樣的訪問。
- 這其實(shí)就是因?yàn)镃語(yǔ)言本身【語(yǔ)法的松散性】導(dǎo)致的,因?yàn)镃語(yǔ)言的語(yǔ)法限制不太嚴(yán)格,對(duì)變量的類型約束不嚴(yán)格,影響程序的安全性,對(duì)數(shù)組下標(biāo)越界不作檢查等。所以C語(yǔ)言其實(shí)較其他高級(jí)語(yǔ)言來(lái)說(shuō)其實(shí)更難掌握,要求使用者對(duì)代碼具備一定的的控制能力
- 那面對(duì)C語(yǔ)言中的一些缺陷,官方文檔中的也只是建議說(shuō)使用的時(shí)候不要這樣去做,像上面那樣直接訪問棧頂元素的方式【不做推薦】
但是推薦這個(gè)東西管用嗎?
- 舉一個(gè)很形象的例子,日常我們?cè)陂_車??經(jīng)過十字路口的時(shí)候,都會(huì)有很多紅綠燈來(lái)阻攔我們,此時(shí)就無(wú)法做到一路暢通,但是所謂的
”紅燈停,綠燈行,黃燈等一等”
真的起到了什么作用嗎? - 還是會(huì)存在大批行人闖紅燈的現(xiàn)象,總有人不遵紀(jì)守法,導(dǎo)致出事故
- 要知道,這個(gè)世界永遠(yuǎn)存在不確定的事,不可能所有的事情都愿你想得那么美好,就好比我們?nèi)粘T谧鲩_發(fā)的時(shí)候,總是需要考慮到各種各樣的問題,為什么?因?yàn)橛脩舻男袨槭遣淮_定的,可能哪一天就會(huì)做出你在開發(fā)是會(huì)后根本想不到的事,因?yàn)槲覀儗懘a時(shí)需要考慮到各方面的因素
所以可以看出來(lái)C語(yǔ)言存在一定的不嚴(yán)謹(jǐn)性,而且C語(yǔ)言還比較亂,尤其體現(xiàn)在學(xué)校的教科書和一些相關(guān)書籍中
- 在C++引用一文的,我有提到了很多學(xué)校的數(shù)據(jù)結(jié)構(gòu)教材中的一些代碼,其實(shí)是C和C++混編的,可是卻告訴讀者使用的是C語(yǔ)言是實(shí)現(xiàn),就是因?yàn)镃和C++之間有著一些聯(lián)系,所以很多讀者就會(huì)分不清哪個(gè)是C,那么是C++的代碼
- 不僅如此,在一些經(jīng)典書籍中,也會(huì)出現(xiàn)類似的情況,這里點(diǎn)名說(shuō)一本書叫做《大話數(shù)據(jù)結(jié)構(gòu)》。如果你有看過這本書的話會(huì)覺得它里面的一些講解和圖其實(shí)都蠻好的,也挺適合初學(xué)者,不過呢里面的一些
代碼
讓人看起來(lái)確實(shí)有點(diǎn)難受?? - 但凡你有去工作過的話,真的是看不上這本書的代碼,可以說(shuō)這個(gè)作者應(yīng)該是缺乏一些工程經(jīng)驗(yàn),代碼沒有規(guī)范性
上面的一些種種案例其實(shí)都可以說(shuō)明C語(yǔ)言在語(yǔ)法設(shè)計(jì)這一塊確實(shí)是有些松散了,導(dǎo)致缺乏經(jīng)驗(yàn)的初學(xué)者會(huì)遇到很多難題
3、C++的特性優(yōu)勢(shì)分析
再談?lì)惖姆庋b思想 - - 兵馬俑還是保護(hù)起來(lái)吧??
- 上面談到了由于C語(yǔ)言在語(yǔ)法設(shè)計(jì)這一塊存在松散性,因而導(dǎo)致了在使用的時(shí)候會(huì)有一些隨機(jī)性和不確定性,存在一定的風(fēng)險(xiǎn)。但是C++在這一塊卻做得很好,一起來(lái)看看C++在封裝這一塊的思想
-
數(shù)據(jù)和方法都封裝到類里面
- C++做得很好是因?yàn)樗⒉皇窍馛語(yǔ)言那樣變量和函數(shù)都分離開來(lái),而是將它們都封裝到一個(gè)類里,全部保護(hù)起來(lái)了,外界是無(wú)法隨意訪問的
- 初步談到類的封裝思想的時(shí),說(shuō)到【封裝】其實(shí)就是一種更好地控制,更好地進(jìn)行管理,現(xiàn)在我通過一個(gè)形象一點(diǎn)的案例再來(lái)描述一下這個(gè)封裝的思想
- 有去過西安的讀者應(yīng)該了解,這個(gè)六朝古都擁有世界八大奇跡之一的【秦始皇陵兵馬俑】,是中華文化的瑰寶。從上圖中我們可以看出,館內(nèi)將兵馬俑都封在了中間的坑里,而外層則是一群群的游客,它們只能在站臺(tái)上觀看,而不可以下到坑洞里去觸碰兵馬俑
- 這其實(shí)指得就是一種【封裝】的思想,將兵馬俑全部保護(hù)起來(lái),外人無(wú)法輕易接觸到。若是不將這些兵馬俑保護(hù)起來(lái),誰(shuí)都可以接觸到,那么要不了一個(gè)月,兵馬俑上到處都會(huì)刻著
“xxx到此一游”
、“xxx愛xxx”
,或者缺胳膊少腿
-
控制訪問方式。【愿意給你訪問的共有,不愿意給你訪問的私有】
- 第二點(diǎn)呢就是在類的封裝基礎(chǔ)上,限定了外界的【控制訪問方式】,若是想要給外界訪問的就設(shè)置為共有
public
,不想給外界訪問的就設(shè)置為私有private
- 就比如說(shuō)你去定義一個(gè)棧然后對(duì)其進(jìn)行初始化,此時(shí)不能直接訪問到這個(gè)類內(nèi)部的成員變量,類會(huì)向外部提供一個(gè)公共的接口對(duì)私有的成員變量去進(jìn)行一個(gè)訪問
Stack st; st.Init();
- 這個(gè)共有的接口是類里面寫好給你的,類寫得沒問題,那你用起來(lái)就不會(huì)有問題
- 所以在C++中,我們要去獲取棧頂元素的方式只有一種,那就是調(diào)用
Top()
成員函數(shù),不需要去管內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)是怎樣的,只需要調(diào)用就可以了 - 而內(nèi)部的數(shù)組和棧頂指針都設(shè)置為了私有,你是無(wú)法訪問到的
int top = StackTop(&st); //? int top = st->a[st->top - 1]; //?
講得通俗一點(diǎn),還是用我們上面講到過的紅綠燈例子??
- 行人老是闖紅燈怎么辦! 那就不讓他過去了,把紅綠燈給撤了,兩遍圍墻圍起來(lái)不能通過馬路,那怎么辦呢?就弄一個(gè)高架橋,你想要過去只能走上面的高架橋,這也就一勞永逸杜絕了闖紅燈的問題的,杜絕了安全隱患
- 第二點(diǎn)呢就是在類的封裝基礎(chǔ)上,限定了外界的【控制訪問方式】,若是想要給外界訪問的就設(shè)置為共有
-
調(diào)用函數(shù)比C語(yǔ)言要輕松一點(diǎn),不用傳入當(dāng)前對(duì)象的地址
- 如果你仔細(xì)觀察C++和C語(yǔ)言實(shí)行去實(shí)現(xiàn)一個(gè)棧,不僅是類的封裝這一塊發(fā)生了大的改動(dòng),而且代碼也簡(jiǎn)潔了不少,傳入的參數(shù)均少了一個(gè)
//C語(yǔ)言實(shí)現(xiàn) void PushStack(ST* st, STDataType x) PushStack(&st, 1); //C++實(shí)現(xiàn) void Stack::Push(const DataType& data) st.Push(1);
- 那這么一對(duì)比確實(shí)有同學(xué)發(fā)現(xiàn)C++為何不用傳當(dāng)前對(duì)象的地址過去呢?
- 那我想這位同學(xué)一定是忘了一個(gè)很重要的東西
--> this指針
。還記得this指針的原理嗎 ?它是成員函數(shù)的一個(gè)隱藏形參,在當(dāng)前對(duì)象調(diào)用成員函數(shù)時(shí),當(dāng)前對(duì)象的地址會(huì)連同其他參數(shù)一起壓入棧中,VS中則是使用寄存器ecx
來(lái)進(jìn)行臨時(shí)存放,最后再由成員函數(shù)中的this指針接受當(dāng)前正在調(diào)用函數(shù)對(duì)象的地址,以此來(lái)訪問不同對(duì)象的不同成員變量 - 所以可以看出:對(duì)于C語(yǔ)言來(lái)說(shuō),是需要我們顯式地傳入當(dāng)前對(duì)象的地址,是浮于水面的;對(duì)于C++來(lái)說(shuō),不需要我們?nèi)プ鲞@一件事,編譯器自動(dòng)會(huì)幫我們完成,是藏于水面下的
九、總結(jié)與提煉
到這里,就講完了類的封裝思想,我們來(lái)總結(jié)回顧一下??
- 首先我們聊到了【面向過程】與【面向?qū)ο蟆恐g的區(qū)別,初步感受了C語(yǔ)言和C++兩門語(yǔ)言特性的不同。為了引出C++中的類,便對(duì)比了
struct
和class
這兩個(gè)關(guān)鍵字,知道了原來(lái)在C++中結(jié)構(gòu)體可以這么玩,并且二者都可以用在來(lái)定義一個(gè)類 - 接下去呢我們就正式開始聊C++中的類,說(shuō)到了三種訪問限定符,
puiblc
、protected
和private
,若是加上了訪問限定符后,類內(nèi)的成員對(duì)外部來(lái)說(shuō)就存在不同的限制。初次講到了類的封裝思想,將對(duì)象的屬性(數(shù)據(jù))與操作數(shù)據(jù)的方法結(jié)合在一塊,起到了更好地管理 - 寫好一個(gè)類后,就可以用它定義出一個(gè)對(duì)象了,在這一模塊,通過形象地案例向讀者展示了【聲明】和【定義】之間的區(qū)別,如果要將一個(gè)東西定義出來(lái),就要實(shí)實(shí)在在為其開出一塊空間
- 將類實(shí)例化后,就可以通過這個(gè)對(duì)象去調(diào)用類中的向外提供的公有成員函數(shù)來(lái)說(shuō)來(lái)操控私有成員變量,但是在計(jì)算類的大小時(shí)卻發(fā)現(xiàn)【成員函數(shù)】似乎并不計(jì)算在內(nèi),通過探究發(fā)現(xiàn)原來(lái)其存在于一個(gè)公共代碼區(qū),為什么減少類內(nèi)部的負(fù)擔(dān),將大家都要使用的東西像小區(qū)中的公共設(shè)施一般放在一個(gè)【公共代碼區(qū)】,這樣誰(shuí)要用的時(shí)候通過對(duì)應(yīng)的地址找到然后去調(diào)用一下即可。可是成員函數(shù)內(nèi)部要如何知道是哪個(gè)對(duì)象來(lái)調(diào)用的我呢?
- 此時(shí)我們談到了一個(gè)重要知識(shí)點(diǎn) ——
this指針
,隨著每次的傳入的對(duì)象地址不同,隱式形參this指針就會(huì)通過不同的地址去找到內(nèi)存中對(duì)應(yīng)的那塊地址中的成員變量,進(jìn)行精準(zhǔn)賦值 - 最后,在學(xué)習(xí)了C++中的類后,便去實(shí)現(xiàn)了我們之前在數(shù)據(jù)結(jié)構(gòu)中寫過的【棧】,對(duì)二者進(jìn)行對(duì)比可以發(fā)現(xiàn),原來(lái)C++在封裝這一塊的思想確實(shí)考慮得很周到,很好地解決了C語(yǔ)言因?yàn)檎Z(yǔ)法松散而導(dǎo)致的各種不安全性,進(jìn)一步加深了對(duì)【封裝思想】的理解
以上就是本文要介紹的所有內(nèi)容,
Thanks for reading, see you next article
??文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-420095.html
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-420095.html
到了這里,關(guān)于C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!