国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

這篇具有很好參考價(jià)值的文章主要介紹了C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

一、前言

從本文開始,我們就要正式來(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)用逐步解決問題

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
??對(duì)于C++是基于【面向?qū)ο蟆康模P(guān)注的是對(duì)象,將一件事情拆分成不同的對(duì)象,靠對(duì)象之間的交互完成。

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 可是呢,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)系

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

三、結(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++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】


  • 不過C++相較于C語(yǔ)言可不只是多了這一點(diǎn),C語(yǔ)言結(jié)構(gòu)體中只能定義變量,在C++中,結(jié)構(gòu)體內(nèi)不僅可以定義變量,也可以定義函數(shù)【但是這在C語(yǔ)言中,是不被允許的】

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

知道了上面這些,其實(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++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
通過上面所寫,使用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ī)范的定義

  1. 聲明和定義全部放在類體中,需注意:成員函數(shù)如果在類中定義,編譯器可能會(huì)將其當(dāng)成內(nèi)聯(lián)函數(shù)處理
  • 這也就是我們上面講到過有關(guān)【?!康倪@種定義,只需要將struct換成class即可,這種類的定義方式簡(jiǎn)單粗暴,也是我們平常用得最多的,自己練習(xí)代碼可以直接這樣使用,但其實(shí)在日常項(xiàng)目的開發(fā)中,不建議大家這樣使用?
  1. 類聲明放在.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)什么問題??

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 這就是在上面說(shuō)到的:成員函數(shù)名前需要加類名::,我們?cè)诿臻g的講解中有說(shuō)到過有關(guān)【作用域】的概念,在C++中,對(duì)于一個(gè)類體而言,其實(shí)就屬于一個(gè)作用域,將成員變量和成員函數(shù)包含在里面。那么此時(shí)要在另一個(gè)cpp的文件中訪問這個(gè)類中定義的成員變量的話也就是訪問Stack作用域中的內(nèi)容,就要加上【域作用限定符::】,就像下面這樣

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

成員變量命名規(guī)則

最后再來(lái)普及一點(diǎn),你可以自己觀察我上面在寫【棧】的時(shí)候?qū)?strong>成員變量的命名形式,前面都加上了_,可能你看起來(lái)會(huì)很別扭,但這卻是比較規(guī)范的一種定義形式

  • 其實(shí)你可以去看一看庫(kù)里面一些變量的命名方式,很多都是采用這種下劃線的方式進(jìn)行,原因其實(shí)就在于避免造成【成員變量】和【形參】的命名沖突從而引發(fā)歧義

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 可以看到,我在下面寫了一個(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;
};

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
通過觀察可以發(fā)現(xiàn),若是【成員變量】和【形參】的名字一樣的話,其實(shí)這個(gè)時(shí)候就會(huì)造成歧義,初始化的就不是當(dāng)前這個(gè)對(duì)象的成員變量了,如果你自己觀察就可以發(fā)現(xiàn),命名一樣的話,在VS中二者的字體都會(huì)變淡,這其實(shí)就是VS在提示你這樣的做法其實(shí)是無(wú)效的?

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

那要如何命名才是最規(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;

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

四、類的訪問限定符及封裝【?】

學(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)差異

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 光就上面這么說(shuō)你一定會(huì)覺得有些抽象,其實(shí)讀者可以將這個(gè)訪問限定符看作是一把【鎖】??,設(shè)想你家里裝了一把鎖,那么此時(shí)鎖住的就是外面的人,對(duì)家里的人是不會(huì)有影響的

接下去再來(lái)看看有關(guān)訪問限定符的一些特性指南

  1. public修飾的成員在類外可以直接被訪問
  2. protected和private修飾的成員在類外不能直接被訪問(此處protected和private是類似的)
  3. 訪問權(quán)限作用域從該訪問限定符出現(xiàn)的位置開始直到下一個(gè)訪問限定符出現(xiàn)時(shí)為止
  4. 如果后面沒有訪問限定符,作用域就到 } 即類結(jié)束。
  5. 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)看看

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 可以看出,即使我將類中的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)了,方便管理

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

這里先初步地講一下有關(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)了?還是只是聲明呢?

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 讀者一定要理解【聲明】和【定義】的區(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ì)有這么一筆錢給到你,但是這筆錢還沒真正到你的手里

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 不過呢,過了好幾天了,還是不見兄弟把錢打過來(lái),眼看就要交首付了,只能再給他打一個(gè)電話過去說(shuō):“兄弟,上次和你說(shuō)的那個(gè)錢怎么樣了,后天就要交首付了,你看能不能先打過來(lái)?!碑?dāng)你說(shuō)完這句話之后,其實(shí)就會(huì)出現(xiàn)兩種情況Ⅱ
    1. 你的兄弟回道:“哦哦,不好意思,最后手頭太忙可了,都給忘了,馬上給你轉(zhuǎn)過來(lái)?!贝藭r(shí)就聽到【支付寶到賬5萬(wàn)元】的聲音,那么這筆錢就真正地到你手里的,這是實(shí)實(shí)在在的,已經(jīng)存在了的事,指的就是【定義】
    2. 你的兄弟回道:“啊呀,這個(gè),真是不好意思啊,家里的錢都給媳婦管著呢??,它不同意我也辦法,對(duì)不住了兄弟,要不你再找找別人?!庇谑撬阈⌒囊硪淼貟斓袅穗娫挘銈z就沒有再聯(lián)系過,鐵瓷也不鐵了~

  • 對(duì)于上面的第二種情況,就很像平常在寫程序的時(shí)候出現(xiàn)鏈接錯(cuò)誤的情況,那就是【聲明了但是未定義】的這種行為。之前承諾了、聲明了,但是找你要的時(shí)候?qū)嶋H卻沒有
    • 對(duì)于函數(shù)而言就是有聲明找不到定義
    • 對(duì)于變量而言就是這個(gè)變量沒開空間
  • 所以對(duì)于這三個(gè)成員變量來(lái)說(shuō)只是一個(gè)聲明,不是定義,并沒有開出實(shí)際的空間

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
那怎樣才算定義呢?又是何時(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í)例化

  1. 類是對(duì)對(duì)象進(jìn)行描述的,是一個(gè)模型一樣的東西,限定了類有哪些成員,定義出一個(gè)類并沒有分配實(shí)際的內(nèi)存空間來(lái)存儲(chǔ)它;

    • 比如:入學(xué)時(shí)填寫的【學(xué)生信息表】??,表格就可以看成是一個(gè)類,來(lái)描述具體學(xué)生信息
      C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
    • 對(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),那么謎底也就揭曉了 ??謎底:山羊
  2. 一個(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í)的別墅,不存在具體的空間,因此是不能住人的??
      C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
    • 那要怎樣才能住人呢?也就是建筑師通過這張?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ì)象的??????
      C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
    • 實(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)行訪問的

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

六、類對(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ù)定義了?

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 可是剛才我們有看過,在實(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ī)則??
  1. 第一個(gè)成員在與結(jié)構(gòu)體偏移量為0的地址處。
  2. 其他成員變量要對(duì)齊到某個(gè)數(shù)字(對(duì)齊數(shù))的整數(shù)倍的地址處。
    注意:對(duì)齊數(shù) = 編譯器默認(rèn)的一個(gè)對(duì)齊數(shù) 與 該成員大小的較小值。
    VS中默認(rèn)的對(duì)齊數(shù)為8
  3. 結(jié)構(gòu)體總大小為:最大對(duì)齊數(shù)(所有變量類型最大者與默認(rèn)對(duì)齊參數(shù)取最?。┑恼麛?shù)倍。
  4. 如果嵌套了結(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ì)于structclass都可以去定義一個(gè)類,那么結(jié)構(gòu)體內(nèi)存對(duì)齊的規(guī)則也一樣適用。不過我們只會(huì)計(jì)算成員變量的大小,那就來(lái)先計(jì)算一下這個(gè)【year】、【month】、【day】的大小

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 通過畫圖以及運(yùn)行結(jié)果可以觀察,可以得出類的大小和我們計(jì)算的【成員變量】大小竟然是一致的,那【成員函數(shù)】呢?沒有算上去嗎?還是根本不計(jì)算在內(nèi)?

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

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)空間。那么如何解決呢?

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 下面是第二種設(shè)計(jì)方式。代碼只保存一份,在對(duì)象中保存存放代碼的地址,這種方式似乎看起來(lái)不錯(cuò),你認(rèn)為可行嗎?

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 再來(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)用

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 答案揭曉,那就是最后這一種,實(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ù)】

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 那其實(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é)果

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 通過觀察可以得知,似乎只算對(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、monthday進(jìn)行初始化。隨著每次的傳入的對(duì)象地址不同,this指針就會(huì)通過不同的地址去找到內(nèi)存中對(duì)應(yīng)的那塊地址中的成員變量,進(jìn)行精準(zhǔn)賦值

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 不過通過觀察可以發(fā)現(xiàn),似乎我們自己去加上這一個(gè)參數(shù)好像是行不通,編譯器報(bào)出了很多錯(cuò)誤,

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
看看下面這段話就知道為什么了??

  • 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)去

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

了解了this指針的基本原理后,我們來(lái)聊聊它的相關(guān)特性

  1. this指針的類型:類類型* const(Date* const),即成員函數(shù)中,不能給this指針賦值

    • 對(duì)于this指針來(lái)說(shuō),是被const常所修飾的,為【指針常量】,對(duì)于指針本身的指向是不可修改的,但是指針?biāo)赶虻膬?nèi)容可以通過解引用的方式來(lái)修改。如果不是很清楚這一塊可以看看常量指針與指針常量的感性理解

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  1. 只能在“成員函數(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ì)有問題的

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  1. 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)的
  2. 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ì)象的地址

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

3、this指針的感性理解

說(shuō)到了這么多有關(guān)this指針的特性,有些特性對(duì)大家來(lái)說(shuō)可能還是比較難以理解,接下去我會(huì)通過三個(gè)生活中的小場(chǎng)景帶你好好理解一下??

  1. 夏天到了,呆在家里一定會(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)打不開你的柜子】

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  1. 在大學(xué)生活中,每個(gè)人一定都有自己的校園卡,這張校園卡呢可以用來(lái)吃飯、洗澡、接水,甚至可以代替人臉識(shí)別,所以在這個(gè)校園中,校園卡就是你的身份象征,每個(gè)人都是唯一的

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  1. 住過小區(qū)的一定都知道,現(xiàn)在的小區(qū)管理制度是越來(lái)越嚴(yán)了,出入呢都需要這個(gè)門禁卡,才可以證明你的身份,是屬于這個(gè)小區(qū)的,防止外來(lái)人員入室盜竊,所以這個(gè)門禁卡就是你身份的象征【有沒帶門禁進(jìn)不去單元門的嗎??】

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
通過上面的三個(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è)查看

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

再來(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é)果:

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 看了上面的運(yùn)行結(jié)果,你是否感覺很吃驚??,為何沒有【運(yùn)行崩潰】呢?為什么可以對(duì)一個(gè)空指針去解引用呢?
  • 可以知道這個(gè)Print()是Date類的一個(gè)成員函數(shù),那既然是成員函數(shù)的話就可以調(diào)用this指針,我們來(lái)試試

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 可以看到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é)果:

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 很明顯,若是去調(diào)用Init()初始化函數(shù)的話就會(huì)發(fā)生空指針異常的問題,這是為什么呢?

??有同學(xué)說(shuō):一看就是這個(gè)this->的問題,很明顯的解引用嘛,去掉不就好了??

  • 然后繼續(xù)給他看了這個(gè),他便陷入了沉思??

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 這里要說(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)行呢?

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

好,看完了上面這一些,相信你對(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)行!??

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
這是什么呢???
C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 若是你完全不懂這一點(diǎn)的話,應(yīng)該再回過去看看指針的解引用那塊,難道一個(gè)指針去進(jìn)行指向->或者是解引用*就一定是在對(duì)指針進(jìn)行解引用嘛
  • 不,會(huì)不會(huì)解引用取決于要不要到指向的對(duì)象中去找成員變量,而不是看有沒有”->“。因?yàn)閷?duì)于編譯器來(lái)說(shuō),是將代碼的這些語(yǔ)法轉(zhuǎn)換為指令,我們要去看的是這些匯編指令

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 通過上面的觀察可以看出,從【匯編角度】而言,其實(shí)編譯器把->*解析成了同一種方式,至于內(nèi)部的邏輯是怎么樣的,上面已經(jīng)講過了,此處不再贅述

??最后,有位同學(xué)又提出了這樣的寫法,蠻不錯(cuò)的,給讀者分享一下

  • 你認(rèn)為下面這種直接用類名然后【域作用限定符::】的方式去訪問可行嗎?
Date::Print();

運(yùn)行結(jié)果如下:

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 對(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

【解析】:

  1. f1為全局函數(shù),不是類的成員函數(shù),因此沒有this指針
  2. f2為static函數(shù),放在公共代碼段,不屬于類,因此沒有this指針
  3. f3為友元函數(shù),不屬于類,因此沒有this指針
  4. 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é)果:

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】


  • 其實(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ǔ)言特性分析

  1. 數(shù)據(jù)和方法是分離的
    • 這一點(diǎn)其實(shí)很好觀察,當(dāng)我們使用C語(yǔ)言去寫一個(gè)棧的時(shí)候,是將存放數(shù)據(jù)的數(shù)組、棧頂指針、容量大小都放在結(jié)構(gòu)體中,其他算法接口再另外分離寫,二者的關(guān)聯(lián)性并不是很大

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  1. 數(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í)候過于自由,因此需要考慮到很多因素
    1. 需要知道底層的top的初始化
    2. 有越界的風(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)致出事故

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

  • 要知道,這個(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ī)范性

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】

上面的一些種種案例其實(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++在封裝這一塊的思想
  1. 數(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è)封裝的思想
      C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
    • 有去過西安的讀者應(yīng)該了解,這個(gè)六朝古都擁有世界八大奇跡之一的【秦始皇陵兵馬俑】,是中華文化的瑰寶。從上圖中我們可以看出,館內(nèi)將兵馬俑都封在了中間的坑里,而外層則是一群群的游客,它們只能在站臺(tái)上觀看,而不可以下到坑洞里去觸碰兵馬俑
    • 這其實(shí)指得就是一種【封裝】的思想,將兵馬俑全部保護(hù)起來(lái),外人無(wú)法輕易接觸到。若是不將這些兵馬俑保護(hù)起來(lái),誰(shuí)都可以接觸到,那么要不了一個(gè)月,兵馬俑上到處都會(huì)刻著“xxx到此一游”、“xxx愛xxx”,或者缺胳膊少腿
      C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】
  2. 控制訪問方式。【愿意給你訪問的共有,不愿意給你訪問的私有】

    • 第二點(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è)高架橋,你想要過去只能走上面的高架橋,這也就一勞永逸杜絕了闖紅燈的問題的,杜絕了安全隱患
  3. 調(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ì)比了structclass這兩個(gè)關(guān)鍵字,知道了原來(lái)在C++中結(jié)構(gòu)體可以這么玩,并且二者都可以用在來(lái)定義一個(gè)
  • 接下去呢我們就正式開始聊C++中的類,說(shuō)到了三種訪問限定符,puiblc、protectedprivate,若是加上了訪問限定符后,類內(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??

C++ | 深入淺出類的封裝思想【圖文案例,通俗易懂】文章來(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)!

本文來(lái)自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【大數(shù)據(jù)】深入淺出 Apache Flink:架構(gòu)、案例和優(yōu)勢(shì)

    【大數(shù)據(jù)】深入淺出 Apache Flink:架構(gòu)、案例和優(yōu)勢(shì)

    Apache Flink 是一個(gè)強(qiáng)大的開源流處理框架,近年來(lái)在大數(shù)據(jù)社區(qū)大受歡迎。它允許用戶實(shí)時(shí)處理和分析大量流式數(shù)據(jù),使其成為 欺詐檢測(cè) 、 股市分析 和 機(jī)器學(xué)習(xí) 等現(xiàn)代應(yīng)用的理想選擇。 在本文中,我們將詳細(xì)介紹什么是 Apache Flink 以及如何使用它來(lái)為您的業(yè)務(wù)帶來(lái)益處。

    2024年01月17日
    瀏覽(25)
  • 深入淺出C++ ——線程庫(kù)

    深入淺出C++ ——線程庫(kù)

    ??在C++11之前,涉及到多線程問題,都是和平臺(tái)相關(guān)的,比如windows和linux下各有自己的接口,這使得代碼的可移植性比較差。C++11中最重要的特性就是對(duì)線程進(jìn)行支持了,使得C++在并行編程時(shí)不需要依賴第三方庫(kù),而且在原子操作中還引入了 原子類 的概念。要使用標(biāo)準(zhǔn)庫(kù)中

    2024年02月03日
    瀏覽(98)
  • 【C++深入淺出】模版初識(shí)

    【C++深入淺出】模版初識(shí)

    目錄 一. 前言 二. 泛型編程 三. 函數(shù)模版? 3.1 函數(shù)模版的概念 3.2 函數(shù)模版的格式 3.3 函數(shù)模版的原理 3.4 函數(shù)模板的實(shí)例化 3.5 模板參數(shù)的匹配原則 四. 類模版 4.1 類模版的定義 4.2 類模版的實(shí)例化 ? ? ? ? 本期我們要介紹的是C++的又一大重要功能---- 模版 。通過模版,我們

    2024年02月08日
    瀏覽(105)
  • 深入淺出C++——C++的類型轉(zhuǎn)換

    在C語(yǔ)言中,如果 賦值運(yùn)算符左右兩側(cè)類型不同 ,或者形參與實(shí)參類型不匹配,或者 返回值類型與接收返回值類型不一致 時(shí),就需要發(fā)生類型轉(zhuǎn)化。 C語(yǔ)言中總共有兩種形式的類型轉(zhuǎn)換: 隱式類型轉(zhuǎn)換:編譯器在編譯階段自動(dòng)進(jìn)行轉(zhuǎn)換,不能轉(zhuǎn)就編譯失敗。 顯式類型轉(zhuǎn)換:

    2024年02月07日
    瀏覽(94)
  • 深入淺出C++ ——C++11

    深入淺出C++ ——C++11

    ????1998年是C++標(biāo)準(zhǔn)委員會(huì)成立的第一年,本來(lái)計(jì)劃以后每5年視實(shí)際需要更新一次標(biāo)準(zhǔn),C++國(guó)際標(biāo)準(zhǔn)委員會(huì)在研究C++ 03的下一個(gè)版本的時(shí)候,一開始計(jì)劃是2007年發(fā)布,所以最初這個(gè)標(biāo)準(zhǔn)叫C++07。但是到06年的時(shí)候,官方覺得2007年肯定完不成C++ 07,而且官方覺得2008年可能也

    2024年02月02日
    瀏覽(35)
  • 【C++深入淺出】類和對(duì)象下篇

    【C++深入淺出】類和對(duì)象下篇

    ? ? ? ? 老樣子,先來(lái)回顧一下上期的內(nèi)容:上期我們著重學(xué)了C++類中的六大 默認(rèn)成員函數(shù) ,并自己動(dòng)手實(shí)現(xiàn)了一個(gè) 日期類 ,相信各位對(duì)C++中的類已經(jīng)有了一定程度的了解。本期就是類和對(duì)象的最后一篇啦,終于要結(jié)束咯,吧唧吧唧 ? ? ? ? 話不多說(shuō),開吃咯?。?! 2.1

    2024年02月08日
    瀏覽(104)
  • 【C++深入淺出】初識(shí)C++中篇(引用、內(nèi)聯(lián)函數(shù))

    【C++深入淺出】初識(shí)C++中篇(引用、內(nèi)聯(lián)函數(shù))

    ? 目錄 一. 前言 二. 引用 2.1 引用的概念 2.2 引用的使用 2.3 引用的特性 2.4 常引用 2.5 引用的使用場(chǎng)景 2.6 傳值、傳引用效率比較 2.7 引用和指針的區(qū)別 ?三. 內(nèi)聯(lián)函數(shù) 3.1 內(nèi)聯(lián)函數(shù)的概念 3.2 內(nèi)聯(lián)函數(shù)的特性? ? ? ? ? 上期說(shuō)道,C++是在C的基礎(chǔ)之上,容納進(jìn)去了 面向?qū)ο缶幊?/p>

    2024年02月12日
    瀏覽(896)
  • 【C++】模板初階 【 深入淺出理解 模板 】

    【C++】模板初階 【 深入淺出理解 模板 】

    如何實(shí)現(xiàn)一個(gè)通用的交換函數(shù)呢? 使用函數(shù)重載雖然可以實(shí)現(xiàn) ,但是有一下幾個(gè)不好的地方: 重載的函數(shù) 僅僅是類型不同 ,代碼復(fù)用率比較低, 只要有新類型出現(xiàn)時(shí),就需要用戶自己增加對(duì)應(yīng)的函數(shù) 代碼的可維護(hù)性比較低,一個(gè)出錯(cuò)可能所有的重載均出錯(cuò) 那能否 告訴編

    2024年02月05日
    瀏覽(84)
  • 【C++】深入淺出STL之vector類

    【C++】深入淺出STL之vector類

    文章篇幅較長(zhǎng),越3萬(wàn)余字,建議電腦端訪問 大家好,在上一文中,我們重點(diǎn)介紹了 STL中的string類,明白了如何去操作字符串。 本文我們將要來(lái)介紹的是STL中的vector類 vector的文檔介紹 vector是表示可變大小數(shù)組的序列容器。 就像數(shù)組一樣,vector也采用的連續(xù)存儲(chǔ)空間來(lái)存儲(chǔ)元

    2024年02月13日
    瀏覽(85)
  • 【C++】深入淺出STL之string類

    【C++】深入淺出STL之string類

    文章篇幅較長(zhǎng),越7萬(wàn)余字,建議電腦端訪問 本文我們就要來(lái)說(shuō)一說(shuō)STL中的string類,這也是我們?cè)趯慍++代碼的時(shí)候使用最多的 首先要來(lái)談的一點(diǎn)就是為什么要學(xué)習(xí)string類 string 意為字符串,那回憶一下我們?cè)贑語(yǔ)言階段所學(xué)習(xí)的字符串,是以 \\\'\\0\\\' 結(jié)尾的一些字符的集合,為了操

    2024年02月15日
    瀏覽(89)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包