日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當(dāng)前位置:首頁(yè) > > C語(yǔ)言與CPP編程
[導(dǎo)讀]別誤會(huì),今天不是要寫(xiě)我對(duì)象的......這篇文章主要是聊聊我對(duì)于編程語(yǔ)言中「對(duì)象」的一些簡(jiǎn)單認(rèn)識(shí),Go!

別誤會(huì),今天不是要寫(xiě)我對(duì)象的......

這篇文章主要是聊聊我對(duì)于編程語(yǔ)言中「對(duì)象」的一些簡(jiǎn)單認(rèn)識(shí),Go!

一、面向過(guò)程 VS 面向?qū)ο?span style="display: none;">

為什么 C 叫面向過(guò)程(Procedure Oriented)的語(yǔ)言,而 Java、C++ 之類(lèi)叫面向?qū)ο螅∣bject Oriented)呢?

之前聽(tīng)到一個(gè)有趣的說(shuō)法:

在 C 語(yǔ)言中我們是這樣寫(xiě)代碼的:

function_a(yyy);
function_b(xxx);

從左往右看過(guò)去,最先看到的是函數(shù),也就是 Procedure,故叫做「Procedure Oriented」。

而在 Java 這類(lèi)語(yǔ)言我們通常是這樣的:

Worker?worker?=?new?Woker("小北");
worker.touchFish("5分鐘");
worker.coding("1小時(shí)");

第一眼看到的就是一個(gè)個(gè)的對(duì)象,所以叫做面向?qū)ο蟆窸bject Oriented」。

回到正題,在 C 語(yǔ)言,「數(shù)據(jù)」和「操作數(shù)據(jù)的函數(shù)」是互相分開(kāi)的,你并不知道數(shù)據(jù)和函數(shù)之間有什么關(guān)聯(lián),這在語(yǔ)言層面上是不支持的。

在 C 語(yǔ)言中,編程就是將一堆以功能為核心導(dǎo)向的函數(shù)進(jìn)行組合,依次調(diào)用這些函數(shù)就可以了。

這就叫面向過(guò)程,其實(shí)和我們思考問(wèn)題的方式是吻合的,比如要實(shí)現(xiàn)一個(gè)貪吃蛇,那面向過(guò)程的設(shè)計(jì)思路就是首先分析問(wèn)題的步驟:

1、開(kāi)始游戲

2、隨機(jī)生成食物

3、繪制畫(huà)面

4、接收輸入并改變方向

5、判斷是否碰到墻壁和食物等

6、...

而用面向?qū)ο蟮乃悸穭t是:

首先,將整個(gè)游戲拆解為一個(gè)個(gè)的實(shí)體:蛇、食物、障礙物、規(guī)則系統(tǒng)、動(dòng)畫(huà)系統(tǒng)。

然后分別去實(shí)現(xiàn)這些實(shí)體應(yīng)該具有的功能(即成員函數(shù)),然后你還要考慮不同實(shí)體之間如何交互和傳遞消息,說(shuō)白了就是調(diào)用關(guān)系和傳參。

比如規(guī)則系統(tǒng)接收蛇、食物、障礙物作為參數(shù),可以判定是否吃到食物或者碰到墻壁。

動(dòng)畫(huà)系統(tǒng)則可以接收蛇、食物、障礙物等作為參數(shù),然后在屏幕上動(dòng)態(tài)的顯示出來(lái)。

這樣做的好處便是,可以利用面向?qū)ο笥蟹庋b、繼承、多態(tài)性的特性,設(shè)計(jì)出低耦合的系統(tǒng),使系統(tǒng)更加靈活、更加易于維護(hù)。

好了,上面這段大概可以看做八股文,你分別用 C 和 Java/C++ 寫(xiě)過(guò)程序自然知道二者區(qū)別,沒(méi)寫(xiě)過(guò),我在這說(shuō)高內(nèi)聚、低耦合也沒(méi)啥用。

二、那么對(duì)象是如何實(shí)現(xiàn)的呢?

對(duì)象的本質(zhì)就是一堆的屬性(成員變量)和一系列的方法(成員函數(shù))組成,在講這個(gè)之前,先補(bǔ)充說(shuō)明一個(gè)「函數(shù)指針」。

我們都知道函數(shù)在 C/C++、Java 這類(lèi)語(yǔ)言中都不是一等公民,一等公民的意思就是能夠像其它整數(shù)、字符串變量一樣,可以被賦值或者作為函數(shù)參數(shù)、返回值等。

但是在 JS、Python 這類(lèi)動(dòng)態(tài)語(yǔ)言中,函數(shù)卻是一等公民,可以作為參數(shù)、返回值等等。

究其原因,這類(lèi)語(yǔ)言底層實(shí)現(xiàn)中,一切東西皆是對(duì)象,函數(shù)、整數(shù)、字符串、浮點(diǎn)數(shù)都是對(duì)象,函數(shù)才因此具備同其它基本類(lèi)型一樣的一等公民的身份。

但是!在 C/C++ 中函數(shù)雖然是二等公民, 但我們可以通過(guò)函數(shù)指針來(lái)變相的實(shí)現(xiàn)將函數(shù)用于變量賦值、函數(shù)參數(shù)、返回值場(chǎng)景。

三、函數(shù)指針是啥?

我們知道普通變量申明后,編譯器就會(huì)自動(dòng)分配一塊適合的內(nèi)存,那么函數(shù)也是同樣的,編譯的時(shí)候會(huì)將一個(gè)函數(shù)編譯好,然后放在一塊內(nèi)存中。

(上面這段說(shuō)法實(shí)際很不準(zhǔn)確,因?yàn)榫幾g器不會(huì)分配內(nèi)存,編譯好的代碼也是以二進(jìn)制的形式放在磁盤(pán)上,只有程序開(kāi)始運(yùn)行時(shí)才會(huì)加載到內(nèi)存)

如果我們把函數(shù)的首地址也存儲(chǔ)在某個(gè)指針變量里,就可以通過(guò)這個(gè)指針變量來(lái)調(diào)用所指向的函數(shù)了,這個(gè)存儲(chǔ)函數(shù)首地址的特殊指針就叫做「函數(shù)指針」。

比如有一個(gè)函數(shù)int func(int a);

我們?nèi)绾紊昝饕粋€(gè)可以指向func的函數(shù)指針呢?

int (*func_p)(int);

看起來(lái)有點(diǎn)奇怪,其實(shí)函數(shù)指針變量的聲明格式如同函數(shù)func的聲明一樣,只不過(guò)把 func換成了 (*func_p)罷了。

為什么要括號(hào)呢?因?yàn)椴灰ㄌ?hào)的話int *func_p(int);就是申明一個(gè)返回指針的函數(shù)了,括號(hào)就是為了避免這種歧義。

我們來(lái)多看幾個(gè)函數(shù)指針的申明吧:

int?(*f1)(int);?//?傳入int,返回int?
void?(*f2)(char*);?//傳入char指針,沒(méi)有返回值?
double*?(*f3)(int,?int);?//傳遞兩個(gè)整數(shù),返回?double指針

來(lái)看一個(gè)函數(shù)指針的具體用處吧:

#?include?

typedef?void?(*work)()?Work;?//?typedef?定義一種函數(shù)指針類(lèi)型

void?xiaobei_work()?{
?printf("小北工作就是寫(xiě)代碼");
}

void?shuaibei_work()?{
?printf("帥北工作就是摸魚(yú)")
}

void?do_work(Work?worker)?{
??worker();
}
int?main(void)
{
??Work?x_work?=?xiaobei_work;
??Work?s_work?=?shuaibei_work;
??do_work(x_work);
??do_work(s_work);
??return?0;
}

輸出:

小北工作就是寫(xiě)代碼

帥北工作就是摸魚(yú)

其實(shí)這里有點(diǎn)為了用函數(shù)指針而用了,不過(guò)大家應(yīng)該體會(huì)到了,函數(shù)指針最大的優(yōu)點(diǎn)就是將函數(shù)變量化了。

我們可以將函數(shù)作為參數(shù)傳遞給其它函數(shù),那么這里其實(shí)就有了多態(tài)的雛形,我們可以傳遞不同的函數(shù)來(lái)實(shí)現(xiàn)不同的行為。

void qsort(void* base, size_t num, size_t width, int(*compare)(const void*,const void*))

這是 C 標(biāo)準(zhǔn)庫(kù)中 qsort 函數(shù)的申明,它最后一個(gè)參數(shù)就要求傳入一個(gè)函數(shù)指針,這個(gè)函數(shù)指針負(fù)責(zé)比較兩個(gè) element。

因?yàn)閮蓚€(gè)元素的比較方式只有調(diào)用者才知道,所以這里需要以函數(shù)指針的形式告訴 qsort 如何去判定兩個(gè)元素的大小。

好了,函數(shù)指針就簡(jiǎn)單介紹到這里,接下來(lái)回到主題,對(duì)象。

四、對(duì)象

那么在 C 語(yǔ)言中如何簡(jiǎn)單模擬一個(gè)對(duì)象呢?

當(dāng)然只能靠結(jié)構(gòu)體啦,而成員函數(shù)就可以通過(guò)函數(shù)指針來(lái)實(shí)現(xiàn),其它的比如訪問(wèn)控制、繼承等我們暫時(shí)不考慮。

struct?Animal?{
????char?name[20];
????void?(*eat)(struct?Animal*?this,?char?*food);?//?成員方法?eat
????int?(*work)(struct?Animal*?this);????//?成員方法?工作
};

但是eatwork都還沒(méi)有任何具體實(shí)現(xiàn),所以我們可以在一個(gè)初始化函數(shù)中構(gòu)造這個(gè) Animal 對(duì)象。

void?eat(struct?Animal*?this,?char?*food)?{
????printf("%s?在吃?%s\n",?this->name,?food);
};

void?work(struct?Animal*?this)?{
????printf("%s?在工作\n",?this->name);
}
struct?Animal*?Init(const?char?*name)?{
????struct?Animal?*animal?=?(struct?Animal?*)malloc(sizeof(struct?Animal));
????strcpy(animal->name,?name);
????animal->eat?=?eat;
????animal->work?=?work;
????return?animal;
}

Init函數(shù)內(nèi)部我們就完成了“成員函數(shù)”的賦值和一些初始化工作,并且給 eatwork兩個(gè)函數(shù)指針都綁定了具體的實(shí)現(xiàn)。

接下來(lái)我們可以使用一下這個(gè)對(duì)象:

int?main()?{
?struct?Animal?*animal?=?Init("小狗");
?animal->eat(animal,?"牛肉");
?animal->work(animal);
?return?0;
}

輸出:

小狗 在吃 牛肉

小狗 在工作

為什么明明animal調(diào)用的eat方法卻還要把animal當(dāng)參數(shù)傳遞給eat方法呢,難道eat不知道是哪一個(gè)Animal調(diào)用的它嗎?

確實(shí)不知道,對(duì)象其實(shí)就是在內(nèi)存中一段有意義的區(qū)域,每一個(gè)不同的對(duì)象都有各自的內(nèi)存位置。

而他們的成員函數(shù)卻存放在代碼段,而且只會(huì)存在一份副本。

所以animal->eat(...)調(diào)用方式和直接調(diào)用eat(...),效果完全等同,那個(gè)animal存在的意義就是讓你從面向過(guò)程轉(zhuǎn)變?yōu)槊嫦驅(qū)ο笏伎?,將方法調(diào)用轉(zhuǎn)變?yōu)閷?duì)象間消息傳遞。

所以當(dāng)調(diào)用成員函數(shù)的時(shí)候,我們還需要傳入一個(gè)參數(shù) this,用來(lái)指代當(dāng)前是哪個(gè)對(duì)象在調(diào)用。

由于 C 語(yǔ)言不支持面向?qū)ο螅晕覀冃枰謩?dòng)將 animal 作為參數(shù)傳遞給 eat、work 函數(shù)。

如果是在 C++ 這種面向?qū)ο蟮恼Z(yǔ)言中,我們直接不用手動(dòng)傳遞這個(gè)參數(shù),就像下面這樣:

animal->eat(“牛肉”);
animal->work();

實(shí)際上這是編譯器幫我們?nèi)プ鲞@個(gè)事,上面這兩行代碼,經(jīng)過(guò)編譯器之后會(huì)變成下面這個(gè)樣子:

eat(animal,?"牛肉");
work(animal);

然后,編譯器還會(huì)在編譯階段默默地將 this 作為成員函數(shù)的一個(gè)形參添加到參數(shù)列表。

并且哪個(gè)對(duì)象調(diào)用的方法,那個(gè)對(duì)象就會(huì)被當(dāng)做參數(shù)賦值給this。

學(xué)習(xí) Java 的的同學(xué)也一定對(duì)這個(gè)this非常熟悉吧,Java 中和 C++ 中的 this 基本都是一樣的作用。

或者說(shuō),幾乎所有的面向?qū)ο笳Z(yǔ)言,都會(huì)存在一個(gè)類(lèi)似的機(jī)制,來(lái)將調(diào)用對(duì)象隱式的傳遞給成員函數(shù),比如 Python 中的對(duì)象定義:

class?Stu:
???def?__init__(self,?name,?age):
??????self.name?=?name
??????self.age?=?age
??????
???def?displayStu(self):
??????print?"Name?:?",?self.name,??",?Age:?",?self.age

可以看到每個(gè)成員函數(shù)第一個(gè)參數(shù)都必須叫self,這個(gè)self實(shí)際上就是和this是一樣的作用。

只有這樣,當(dāng)你在成員函數(shù)內(nèi)訪問(wèn)成員變量的時(shí)候,編譯器才知道你訪問(wèn)的是哪一個(gè)對(duì)象。

誒,別忙,按照這樣說(shuō),那豈不是,如果我在成員函數(shù)內(nèi)不訪問(wèn)任何成員變量,就不需要傳遞這個(gè)this指針?

或者說(shuō)可以傳遞一個(gè)空指針?

理論上確實(shí)成立,并且在 C++ 中也是可行的,比如下面這段代碼:

class?Stu{
public:
????void?Hello()?{
?????cout?<"hello?world"?<endl;
????}
private:
????char?*name;
????int?age;
????float?score;
};

由于,在 Hello 函數(shù)中沒(méi)有用到任何成員變量,所以我們甚至可以這樣玩:

Stu?*stu?=?new?Stu;
stu->Hello();?//?正常對(duì)象,正常調(diào)用
stu?=?NULL;
stu->Hello()?//?雖然?stu?為?NULL,但是依然不會(huì)發(fā)送運(yùn)行時(shí)錯(cuò)誤

這里實(shí)際上可以這樣看:

stu->Hello(); 等價(jià)于Hello(NULL);

由于在 Hello 函數(shù)內(nèi)部,沒(méi)有使用任何的成員變量,所以就不需要用 this 指針去定位成員變量的內(nèi)存位置,在這種情況下,調(diào)用對(duì)象為不為 NULL 其實(shí)是不重要的。

但是如果 Hello 函數(shù)訪問(wèn)了成員變量,比如:

void?Hello()?{
?cout?<"Hello?"?<this->name?<endl;
}

這里需要用到 this 去訪問(wèn) name 成員變量, 那么就會(huì)導(dǎo)致運(yùn)行時(shí)程序發(fā)生 coredump,因?yàn)槲覀冊(cè)L問(wèn)了一個(gè) NULL 地址,或者說(shuō)是基于 NULL 偏移一定位置的地址,這段空間絕對(duì)是沒(méi)有訪問(wèn)權(quán)限的。

之前,恰好也有位同學(xué)在群里問(wèn)了這個(gè)問(wèn)題:

這個(gè)問(wèn)題解釋就和上面的一樣,但是這個(gè)結(jié)論不能推廣到其它語(yǔ)言,比如 Java、Python,這些語(yǔ)言的虛擬機(jī)一般會(huì)做一些額外的檢查,比如判斷調(diào)用對(duì)象是否是空指針等,是的話就會(huì)觸發(fā)空指針異常。

而 C++ 就真的是很純粹的編譯成匯編,只要從匯編層面能跑通,那就沒(méi)問(wèn)題,所以才能利用這個(gè)“奇技淫巧”。

那寫(xiě)這篇文章得目的呢,就是想讓大家對(duì)「對(duì)象」有一個(gè)具體的認(rèn)識(shí),最好是明白對(duì)象在內(nèi)存中或者 JVM 中是如何布局的。

我以前就會(huì)覺(jué)得對(duì)象挺神奇的,一堆的功能,后來(lái)才后知后覺(jué),不就是一個(gè)結(jié)構(gòu)體再加上編譯器的語(yǔ)法糖嗎

好啦,對(duì)象的秘密都給大家解開(kāi)了,不給我點(diǎn)亮一個(gè)在看么?

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問(wèn)題,請(qǐng)聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請(qǐng)聯(lián)系該專(zhuān)欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請(qǐng)及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動(dòng)電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動(dòng)電源

在工業(yè)自動(dòng)化蓬勃發(fā)展的當(dāng)下,工業(yè)電機(jī)作為核心動(dòng)力設(shè)備,其驅(qū)動(dòng)電源的性能直接關(guān)系到整個(gè)系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動(dòng)勢(shì)抑制與過(guò)流保護(hù)是驅(qū)動(dòng)電源設(shè)計(jì)中至關(guān)重要的兩個(gè)環(huán)節(jié),集成化方案的設(shè)計(jì)成為提升電機(jī)驅(qū)動(dòng)性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機(jī) 驅(qū)動(dòng)電源

LED 驅(qū)動(dòng)電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個(gè)照明設(shè)備的使用壽命。然而,在實(shí)際應(yīng)用中,LED 驅(qū)動(dòng)電源易損壞的問(wèn)題卻十分常見(jiàn),不僅增加了維護(hù)成本,還影響了用戶(hù)體驗(yàn)。要解決這一問(wèn)題,需從設(shè)計(jì)、生...

關(guān)鍵字: 驅(qū)動(dòng)電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動(dòng)電源的公式,電感內(nèi)電流波動(dòng)大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計(jì) 驅(qū)動(dòng)電源

電動(dòng)汽車(chē)(EV)作為新能源汽車(chē)的重要代表,正逐漸成為全球汽車(chē)產(chǎn)業(yè)的重要發(fā)展方向。電動(dòng)汽車(chē)的核心技術(shù)之一是電機(jī)驅(qū)動(dòng)控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機(jī)驅(qū)動(dòng)系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動(dòng)汽車(chē)的動(dòng)力性能和...

關(guān)鍵字: 電動(dòng)汽車(chē) 新能源 驅(qū)動(dòng)電源

在現(xiàn)代城市建設(shè)中,街道及停車(chē)場(chǎng)照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進(jìn)步,高亮度白光發(fā)光二極管(LED)因其獨(dú)特的優(yōu)勢(shì)逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動(dòng)電源 LED

LED通用照明設(shè)計(jì)工程師會(huì)遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動(dòng)電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動(dòng)電源的電磁干擾(EMI)問(wèn)題成為了一個(gè)不可忽視的挑戰(zhàn)。電磁干擾不僅會(huì)影響LED燈具的正常工作,還可能對(duì)周?chē)娮釉O(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來(lái)解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動(dòng)電源

開(kāi)關(guān)電源具有效率高的特性,而且開(kāi)關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機(jī)重量也有所下降,所以,現(xiàn)在的LED驅(qū)動(dòng)電源

關(guān)鍵字: LED 驅(qū)動(dòng)電源 開(kāi)關(guān)電源

LED驅(qū)動(dòng)電源是把電源供應(yīng)轉(zhuǎn)換為特定的電壓電流以驅(qū)動(dòng)LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動(dòng)電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動(dòng)電源
關(guān)閉