C語(yǔ)言:春節(jié)回家過(guò)年,我發(fā)現(xiàn)只有我沒(méi)有對(duì)象!
聚會(huì)
C語(yǔ)言春節(jié)回家過(guò)年,遇到了不少小伙伴:Java , Python, JavaScript,Ruby......
大家在大城市發(fā)展得都不錯(cuò),回到老家,聚到一起吃飯, 談天說(shuō)地,都是喜氣洋洋。
尤其是Python和JavaScript,更是成了明星,一個(gè)吹噓說(shuō)自己是人工智能的必備,另外一個(gè)炫耀說(shuō)自己是世界上最流行的語(yǔ)言,不信有某某語(yǔ)言流行度排行榜為證, 還有GitHub上的眾多項(xiàng)目云云。
老練的Java則是一直拿TIOBE排行榜說(shuō)事兒:“我已經(jīng)連續(xù)10多年排行第一了,高處不勝寒啊!”
提到TIOBE,Python更是得意:“我今年還被選為T(mén)IOBE的年度編程語(yǔ)言呢!”
雖然常年排名TIOBE第二,C語(yǔ)言有點(diǎn)黯然神傷,人類(lèi)用自己寫(xiě)的程序可真不少,可都是處于底層,在系統(tǒng)級(jí)編程,什么操作系統(tǒng),數(shù)據(jù)庫(kù),編譯器...... 與應(yīng)用層比起來(lái),沒(méi)那么光鮮亮麗。
現(xiàn)在很多人培訓(xùn)了Python, Java 就說(shuō)自己會(huì)編程了, 不懂指針,不懂內(nèi)存,不懂底層的基本原理, 那能算會(huì)編程嗎?
C語(yǔ)言開(kāi)始憤憤不平,悶頭吃菜,似乎要把這股郁悶之氣發(fā)泄到美味佳肴上去。
觥籌交錯(cuò)之間,Java 摟住C的肩膀,親切地說(shuō):“兄弟,你有對(duì)象了嗎?”
這下可捅了馬蜂窩,大家的眼光齊刷刷地聚集到C語(yǔ)言的身上。
C嚅囁了半天:“沒(méi)...... 沒(méi)有。”
“哈哈哈...... 我們都有對(duì)象,你這么大了還沒(méi)對(duì)象?!” Python笑道。
“是啊,一個(gè)沒(méi)有對(duì)象的編程語(yǔ)言還有什么前途?”JavaScript補(bǔ)刀,他原來(lái)沒(méi)有class的概念,是通過(guò)“原型”實(shí)現(xiàn)的OOP,最近幾年才在語(yǔ)法層面引入class關(guān)鍵字。
“我雖然沒(méi)有對(duì)象,但是有指針啊,功能非常強(qiáng)大。”
“指針?你說(shuō)的是那容易出錯(cuò)的指針嗎? 現(xiàn)在有誰(shuí)用指針啊?” JavaScript說(shuō)道。
“不會(huì)用指針,就不是真正的程序員!” C語(yǔ)言漲紅了臉。
餐桌的氣氛變得有些尷尬,捅了簍子的Java招呼著說(shuō):“來(lái)來(lái)來(lái),繼續(xù)喝酒。”
好不容易熬到聚餐結(jié)束,C語(yǔ)言回到了自己的家,家里冷冷清清,自己的“親爹”丹尼斯·里奇(Dennis Ritchie),有史以來(lái)最偉大的程序員之一, 已經(jīng)于2011年10月不幸去世。
桌子上擺著的一本《C程序設(shè)計(jì)語(yǔ)言》,那是丹尼斯·里奇唯一的遺著, 拿起這本書(shū),C不由悲從心來(lái)。
串門(mén)
C語(yǔ)言突然想起來(lái)對(duì)門(mén)的 Ken Thompson,那是Dennis Ritchie的“好基友”,他們倆一起創(chuàng)造了偉大的Unix操作系統(tǒng),獲得了計(jì)算機(jī)界的最高獎(jiǎng):圖靈獎(jiǎng)。
要不問(wèn)問(wèn)Ken? 為什么不讓我有對(duì)象?不讓我面向?qū)ο缶幊?
C來(lái)到Ken Thompson的門(mén)口,按了門(mén)鈴,門(mén)開(kāi)了,C語(yǔ)言一眼就看到Ken Thompson正在和Go玩得不亦樂(lè)乎,心中更是凄苦,Go才是人家的親兒子,我算老幾, 轉(zhuǎn)身便要離去。
Ken 卻從后面叫住了他:“小C啊,快進(jìn)來(lái),和你的兄弟Go玩一會(huì)兒。”
看到C滿(mǎn)臉沮喪,Ken也大為吃驚:“大過(guò)年的,怎么回事?”
C不滿(mǎn)地說(shuō):“當(dāng)年你們?yōu)槭裁床蛔屛矣袑?duì)象?”
“對(duì)象,什么對(duì)象? 奧,你是說(shuō)面向?qū)ο缶幊贪?其實(shí)吧你親爹把你設(shè)計(jì)出來(lái),主要是做系統(tǒng)級(jí)編程的,要的是貼近硬件,要的是效率,要那復(fù)雜玩意兒干啥?中看不中用,再說(shuō)了,你和Go一樣,不是有struct嗎? ” Ken 轉(zhuǎn)向Go,擠了擠眼睛。
“是啊是啊,struct很好用的!” Go馬上附和。
“但是struct也實(shí)現(xiàn)不了OOP啊, Python,JavaScript他們都嘲笑我! ”
“那你說(shuō)說(shuō),什么是OOP?”Ken問(wèn)道。
“嗯,就是封裝、繼承、多態(tài)吧? ” C回答到。
“好,我來(lái)給你掰扯掰扯,用C語(yǔ)言怎么實(shí)現(xiàn)封裝、繼承還有多態(tài)!”
封裝
Ken Thompson 真不愧是老司機(jī),唰唰唰迅速就寫(xiě)成了一段代碼。
他說(shuō)道:“我們先來(lái)說(shuō)說(shuō)封裝,這封裝就是把信息給隱藏起來(lái),你先看看這段代碼。”
shape.h
shape.c
main.c
這里定義了一個(gè)叫做Shape的結(jié)構(gòu)體,外界只能通過(guò)相關(guān)的函數(shù)來(lái)對(duì)這個(gè)Shape進(jìn)行操作,例如創(chuàng)建(Shape_create), 移動(dòng)(Shape_move), 還有獲取位置(Shape_getX)等,不能直接訪問(wèn)Shape的內(nèi)部數(shù)據(jù)結(jié)構(gòu)。
雖然這里沒(méi)有class這樣的關(guān)鍵字,數(shù)據(jù)結(jié)構(gòu)和相關(guān)操作是分開(kāi)寫(xiě)的,看起來(lái)不太完美, 但確實(shí)是實(shí)現(xiàn)了封裝。
C 看到Ken Thompson居然把那個(gè)指針的名稱(chēng)叫做self, 和Python的相同,不由得笑了起來(lái):“我明白了,那繼承該怎么做呢?”
繼承
Ken Thompson不吭聲,繼續(xù)寫(xiě)代碼。
大牛的風(fēng)格看來(lái)都是類(lèi)似的: 別瞎BB,給我上代碼。
這次定義了一個(gè)矩形(Rectangle)的結(jié)構(gòu)體,其中嵌套了Shape,難道這就實(shí)現(xiàn)了繼承? C有點(diǎn)疑惑。
Go小子在旁邊叫了起來(lái):“我明白了,在內(nèi)存中,他們是這樣的。”
通過(guò)這種組合的方式,也算是實(shí)現(xiàn)了繼承吧。
多態(tài)
這么輕松就實(shí)現(xiàn)了封裝和繼承,C語(yǔ)言感到很興奮, 但是多態(tài)怎么實(shí)現(xiàn)呢?
這時(shí)候又傳來(lái)了門(mén)鈴聲,Linus大神拎著一瓶酒進(jìn)來(lái),要找C小伙兒喝酒,看到這桌子上的代碼,立刻就明白了怎么回事。
他說(shuō)道:“別整那么多花里胡哨的東西,還多態(tài),不就是函數(shù)指針嘛! 我給你舉個(gè)例子。”
“這個(gè)結(jié)構(gòu)體包含了兩個(gè)函數(shù)指針,一個(gè)用來(lái)計(jì)算圖形的面積,另外一個(gè)把這個(gè)圖形畫(huà)出來(lái)。我們把這個(gè)結(jié)構(gòu)體叫做虛函數(shù)表。”
“這有什么用啊?”
“在你的Shape中,添加一個(gè)指向該函數(shù)表的指針就行了。” Linus回答。
C和Go都是一臉茫然。
“蠢材, 你們想想啊,當(dāng)你創(chuàng)建一個(gè)子類(lèi)對(duì)象的時(shí)候,比如Rectangle, 把那個(gè)虛函數(shù)指針vptr指向另外一組函數(shù),會(huì)怎么樣?”
兩人還是不懂,Linus只好繼續(xù)畫(huà)圖:
現(xiàn)在C有點(diǎn)明白了, 無(wú)論是Rectangle對(duì)象,還是Square對(duì)象,在調(diào)用Shape_area方法的時(shí)候, 都需要通過(guò)vptr這個(gè)指針找到虛函數(shù)表中的area方法,對(duì)于Rectangle,找到的是Rectangel_area方法,對(duì)于Square,找到的是Square_area方法。
struct Rectangle *r = Rectangle_create(5,5,10,10);
Shape_area((struct Shape *) r);
“其實(shí)吧,你的兄弟C++的多態(tài)實(shí)現(xiàn)原理也是類(lèi)似的!在運(yùn)行時(shí)查找真正的函數(shù)去執(zhí)行。” Ken 總結(jié)到。
“對(duì),這種函數(shù)指針的使用方法太常見(jiàn)了,在我的Linux操作系統(tǒng)中也會(huì)定義類(lèi)似的東西” Linus接口道,
“只要IO設(shè)備提供這幾個(gè)函數(shù)的實(shí)際定義,就可以將File結(jié)構(gòu)體的函數(shù)指針指向?qū)?yīng)的實(shí)現(xiàn),那就實(shí)現(xiàn)了用同一套接口操作不同的IO設(shè)備。”
C語(yǔ)言高興起來(lái):“哈哈,我就說(shuō)我的指針很厲害吧,這些全是通過(guò)指針來(lái)實(shí)現(xiàn)的。”
“是啊,別聽(tīng)Java, Python, JavaScript他們瞎BB,你也有對(duì)象,也能進(jìn)行面向?qū)ο蟮木幊?”
C語(yǔ)言說(shuō)道:“走,喝酒去!”
注: 本文的例子主要來(lái)源于https://www.state-machine.com/doc/AN_OOP_in_C.pdf 我做了修改。
【本文為51CTO專(zhuān)欄作者“劉欣”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過(guò)作者微信公眾號(hào)coderising獲取授權(quán)】




































