日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

我们来谈谈面向指针编程的那些事

發(fā)布時(shí)間:2025/3/20 编程问答 21 豆豆
生活随笔 收集整理的這篇文章主要介紹了 我们来谈谈面向指针编程的那些事 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

面向?qū)ο缶幊?#xff0c;面向設(shè)計(jì)模式編 程(亦即設(shè)計(jì)模式),面向接口編程,面向模板編程(亦即泛型編程),面向函數(shù)編程(亦即函數(shù)式編程),面向多核時(shí)代的并行編程,面向大數(shù)據(jù)的機(jī)器學(xué)習(xí)編 程……這么多年,大家要面向的東西已經(jīng)夠多了,然而我看到的現(xiàn)象是,很多編程語(yǔ)言讓大家面向 xxx 的同時(shí)在竭力回避指針。我可不想面向這么多東西,所以我只好加入指針的黑暗勢(shì)力。我要不自量力的來(lái)寫(xiě)一篇《面向指針編程》作為投名狀,借以表示我與軟件世 界的光明勢(shì)力的徹底決裂。

這個(gè)世界上,提供指針的編程語(yǔ)言很少,這樣的語(yǔ)言有匯編語(yǔ)言、C/C++ 以及 Pascal 等。Pascal 我沒(méi)學(xué)過(guò)。匯編語(yǔ)言過(guò)于黑暗,我現(xiàn)在功力還不足以駕馭它。C++,我覺(jué)得它簡(jiǎn)直是黑暗勢(shì)力中的敗類(lèi)——它試圖掙脫指針,走向光明,結(jié)果卻出了一堆幺蛾子。 所以我還是俗套的選 C 語(yǔ)言來(lái)闡述指針的黑暗力量。

閱讀本文之前,請(qǐng)讀三遍 Unix 無(wú)名師說(shuō)的話:當(dāng)尊者 Ritchie 發(fā)明 C 時(shí),他將程序員放到緩沖溢出、堆損壞和爛指針 bug 的地獄中懲罰。然后自我安慰一下,如果地獄未能使我屈服,那么我會(huì)比地獄更黑暗更強(qiáng)大。

指針是什么?

內(nèi)存是以字節(jié)為單位的一個(gè)很大但是又經(jīng)常不夠用的空間。指針是內(nèi)存中 x 個(gè)連續(xù)的字節(jié)中存儲(chǔ)的數(shù)據(jù)——在 32 位的機(jī)器上,x 的值為 4;在 64 位機(jī)器上,x 值為 8。為了敘述的簡(jiǎn)便,本文只在 64 位的機(jī)器上談?wù)撝羔槨?/p>

指針是一種數(shù)據(jù),這沒(méi)什么稀奇的。從機(jī)器的角度來(lái)看,程序的一切是存放在數(shù)組中的數(shù)據(jù)。只有那些自作多情的程序猿才會(huì)像亞里士多德一樣自作多情的認(rèn) 為程序是由對(duì)象 + 方法或者許多函數(shù)復(fù)合而成的。事實(shí)上,從最遠(yuǎn)離機(jī)器的 Lisp 語(yǔ)言的角度來(lái)看,程序的一切也都是數(shù)據(jù),存放在表中的數(shù)據(jù)。如果忽視程序本身就是數(shù)據(jù)這個(gè)客觀事實(shí),程序猿們很容易就走上了形而上學(xué)的道路,然后他們會(huì)度 過(guò)漫長(zhǎng)的、罪惡的、痛苦的中世紀(jì),膜拜著一個(gè)又一個(gè)神棍,當(dāng)然期間也出現(xiàn)了幾位圣·奧古斯丁。

那么,指針中存儲(chǔ)著什么數(shù)據(jù)?內(nèi)存地址。

內(nèi)存是以字節(jié)為單位的空間,其中每個(gè)字節(jié)都伴隨著一個(gè)地址,這個(gè)地址機(jī)器賦予的,并不是我們的程序編制的。你可以將整個(gè)內(nèi)存空間想象成一棟大樓,將字節(jié)想象為大樓中每個(gè)房間,將每個(gè)字節(jié)的地址想象為房間的門(mén)牌號(hào),于是指針中存儲(chǔ)的數(shù)據(jù)就類(lèi)似于門(mén)牌號(hào)。

如果你從未學(xué)過(guò) C 語(yǔ)言,讀到此處可能會(huì)問(wèn),我們?yōu)槭裁匆趦?nèi)存中存儲(chǔ)內(nèi)存地址?不知你是否住過(guò)賓館。在正規(guī)的賓館里,每個(gè)房間的門(mén)后都會(huì)貼著逃生路線圖,圖中『存儲(chǔ)』了該 賓館與你的房間同一樓層內(nèi)的全部房間的門(mén)牌號(hào)以及它們的布局。如果你住酒店時(shí)從來(lái)也不看逃生路線圖,那么從現(xiàn)在開(kāi)始,入住酒店后第一件事就是認(rèn)真的看一下 它,關(guān)鍵時(shí)刻它能救你一命。在內(nèi)存中存儲(chǔ)內(nèi)存地址,雖然不是救你性命的,但是可以藉此構(gòu)造與賓館逃生路線圖相似的抽象事物——內(nèi)存數(shù)據(jù)的抽象與復(fù)合。

內(nèi)存空間的有名與無(wú)名

現(xiàn)在來(lái)看兩行 C 代碼:

  • int?foo?=?10;?
  • int?*bar?=?&foo;?
  • foo?是什么?foo?表示一個(gè)內(nèi)存地址。foo?前面的?int?是數(shù)據(jù)類(lèi)型修飾,它表示?foo?是內(nèi)存中 4 個(gè)連續(xù)字節(jié)的首字節(jié)地址( 64 位機(jī)器上,int?類(lèi)型的數(shù)據(jù)長(zhǎng)度為 4 個(gè)字節(jié))。C 編譯器總是會(huì)根據(jù)某個(gè)內(nèi)存地址相應(yīng)的類(lèi)型來(lái)確定以該內(nèi)存地址起始的一段連續(xù)字節(jié)中所存儲(chǔ)的數(shù)據(jù)的邏輯意義。因此,當(dāng)我們用?int?類(lèi)型來(lái)修飾?foo,編譯器就會(huì)認(rèn)為以?foo?開(kāi)始的連續(xù) 4 個(gè)字節(jié)中存儲(chǔ)的數(shù)據(jù)是一個(gè)整型數(shù)據(jù)。在上述代碼中,這個(gè)整型數(shù)據(jù)是?10,我們通過(guò)賦值運(yùn)算符?=?將這個(gè)整型數(shù)保存到內(nèi)存中以?foo?地址開(kāi)始的連續(xù) 4 個(gè)字節(jié)中。

    從此刻開(kāi)始,要記住一個(gè)事實(shí),那就是?C 語(yǔ)言中所有的變量名,本質(zhì)上都是內(nèi)存地址。之所以不直接使用內(nèi)存地址,而是使用一些有意義的名字,這就類(lèi)似于沒(méi)人愿意用你的身份證號(hào)來(lái)稱(chēng)呼你,大家更愿意用你的姓名來(lái)稱(chēng)呼你。

    由于 C 語(yǔ)言認(rèn)為數(shù)據(jù)的長(zhǎng)度是由其類(lèi)型確定的。例如,int?類(lèi)型的數(shù)據(jù)長(zhǎng)度是 4 個(gè)字節(jié),char?類(lèi)型的數(shù)據(jù)長(zhǎng)度是是 1 個(gè)字節(jié),用戶自定義的?struct?類(lèi)型的數(shù)據(jù)長(zhǎng)度則是根據(jù)實(shí)際情況而待定。在這種情況下,所有表示內(nèi)存地址的名字,它們實(shí)質(zhì)上表示的是內(nèi)存中各種類(lèi)型數(shù)據(jù)存儲(chǔ)空間的起始地址——專(zhuān)業(yè)一點(diǎn),就是基地址。凡是用名字來(lái)表示基地址的內(nèi)存空間,我們就將其稱(chēng)為有名的內(nèi)存空間。

    再來(lái)看?bar?是什么?bar?是內(nèi)存地址的名字,由于?bar?前面有個(gè)?*?號(hào),這表示我們打算在以 bar 為基地址的連續(xù) 8 個(gè)字節(jié)中存儲(chǔ)一個(gè)內(nèi)存地址(別忘了,我們是在 64 位機(jī)器上,指針數(shù)據(jù)的長(zhǎng)度是 8 個(gè)字節(jié))——foo?所表示的那個(gè)地址,亦即?&foo。在這里,?&?是取值符,它會(huì)對(duì)?foo?說(shuō),你甭給我耍花樣了,老實(shí)交代你的身份證號(hào)!在*?之前還有?int,這意味著在以 bar 為基地址的連續(xù) 8 個(gè)字節(jié)中存儲(chǔ)的那個(gè)內(nèi)存地址是某個(gè)用于存儲(chǔ)整型數(shù)據(jù)的內(nèi)存空間的基地址。

    由于?bar?是某個(gè)內(nèi)存空間的基地址,而這個(gè)內(nèi)存空間中存儲(chǔ)的是一個(gè)內(nèi)存地址,所以?bar?就是所謂的指針。在這里,我們可以認(rèn)為?bar?是對(duì)某塊以?foo?為基地址的內(nèi)存空間的『引用』,也就是在一個(gè)房間號(hào)為?bar?的房間里存儲(chǔ)了房間號(hào)foo。按照 C 語(yǔ)言教材里常用的說(shuō)法,可將?int *bar = &foo?這件事描述為『指針?bar?指向了整型變量?foo』,然而事實(shí)上內(nèi)存里哪有什么針,哪有什么指向?一切都是內(nèi)存空間的引用。在上面的例子里,我們是用?foo?來(lái)直接引用某個(gè)內(nèi)存空間,然后又使用?bar?來(lái)間接引用某個(gè)內(nèi)存空間。

    在上面的例子里,bar?引用的是一個(gè)有名的內(nèi)存空間。那么有沒(méi)有無(wú)名的內(nèi)存空間呢?看下面的代碼:

  • int?*bar?=?malloc(sizeof(int));?
  • malloc(sizeof(int))?就是一個(gè)無(wú)名的內(nèi)存空間,因?yàn)樗且粋€(gè)表達(dá)式,而這個(gè)表達(dá)式描述的是一系列行 為,行為需要借助動(dòng)詞來(lái)描述,而無(wú)法用名詞來(lái)描述。比如『我在寫(xiě)文章』,這種行為無(wú)法只使用名詞來(lái)描述,必須借助動(dòng)詞。任何會(huì)終止的行為都可表示為一系列 的狀態(tài)的變化,也就是說(shuō)任何會(huì)終止的行為都會(huì)產(chǎn)生一個(gè)結(jié)果,而這個(gè)結(jié)果可以用名詞來(lái)描述。例如?malloc(sizeof(int))?這個(gè)行為就是可終止的,它的結(jié)果是它在內(nèi)存所開(kāi)辟 4 個(gè)字節(jié)的空間的基地址,這個(gè)基地址是沒(méi)有名字的,所以它就是個(gè)無(wú)名的基地址,因此它對(duì)應(yīng)的內(nèi)存空間就是無(wú)名的內(nèi)存空間,但是如果我們想訪問(wèn)這個(gè)空間,就必須為它取個(gè)名字,當(dāng)我們用?bar?指針引用它的基地址時(shí),它就變成有名的了。

    C 語(yǔ)言的創(chuàng)始人—— Dennis Ritchie 與 Brian Kernighan 將帶名字的存儲(chǔ)空間稱(chēng)為對(duì)象(Object)——并非『面向?qū)ο缶幊獭恢械膶?duì)象,然后將指代這個(gè)對(duì)象的表達(dá)式稱(chēng)為左值(lvalue)。也就是說(shuō),在 C 語(yǔ)言中,上例中的?foo?與?bar?都是左值,因?yàn)樗鼈兛偸悄軌虺霈F(xiàn)在賦值符號(hào)的左側(cè)。

    看下面的代碼:

  • int?foo?=?10;?
  • int?*bar?=?&foo;?
  • printf("%d",?*bar);?
  • 第三行的?printf?語(yǔ)句中的?*bar?也是一個(gè)左值,因?yàn)樗复艘粋€(gè)有名字的存儲(chǔ)空間,這個(gè)存儲(chǔ)空間的名字就叫做*bar。這個(gè)存儲(chǔ)空間其實(shí)就是以?foo?為基地址的存儲(chǔ)空間。在表達(dá)式?*bar?中,?*?號(hào)的作用是解引用,就是將以?bar為基地址的內(nèi)存空間中存儲(chǔ)的內(nèi)存地址取出來(lái),然后去訪問(wèn)這個(gè)內(nèi)存地址對(duì)應(yīng)的內(nèi)存空間。由于?*bar?的類(lèi)型是?int,所以程序自身就可以知道要訪問(wèn)的是以?*bar?為基地址的 4 個(gè)字節(jié),因此它可以準(zhǔn)確無(wú)誤的將整型數(shù)據(jù)?10?取出來(lái)并交給printf?來(lái)顯示。

    指針最黑暗之處在于,當(dāng)你拿到了一塊內(nèi)存空間的基地址之后,你可以借助這個(gè)基地址隨意訪問(wèn)內(nèi)存中的任何區(qū)域!也就是說(shuō),你可以從通過(guò)指針獲得內(nèi)存空 間的入口,然后你可以讓你的程序在內(nèi)存中(棧空間)隨便逛,隨便破壞,然后你的程序可能就崩潰了。你的程序如果隱含緩沖區(qū)溢出漏洞,它甚至可被其他程序控 制著去執(zhí)行一些對(duì)你的系統(tǒng)非常不利的代碼,這就是所謂的緩沖區(qū)溢出攻擊。C 語(yǔ)言不提供任何緩沖區(qū)保護(hù)機(jī)制,能否有效保護(hù)緩沖區(qū),主要取決于你的 C 編程技藝。

    現(xiàn)在我們寫(xiě) C 程序時(shí),基本上不需要擔(dān)心自己的程序會(huì)遭遇緩沖區(qū)溢出攻擊。因?yàn)橹挥心切┍粡V泛使用的 C 程序才有這種風(fēng)險(xiǎn);如果很不幸,你寫(xiě)的 C 程序真的被很多人使用了,那也不需要太擔(dān)心。《深入理解計(jì)算機(jī)系統(tǒng)》在 3.12 節(jié)『存儲(chǔ)器的越界引用和緩沖區(qū)溢出』中告訴我們,現(xiàn)代操作系統(tǒng)對(duì)程序運(yùn)行時(shí)所需要的棧空間是隨機(jī)生成的,導(dǎo)致攻擊者很難獲得??臻g中的某個(gè)確定地址,至少 在 Linux 系統(tǒng)中是這樣子。C 語(yǔ)言編譯器提供了棧破壞檢測(cè)——至少在 GCC 中是這樣,其原理就是程序的??臻g放置了一只『金絲雀』,程序在運(yùn)行中一旦發(fā)現(xiàn)有襲擊『金絲雀』的可恥代碼,它就會(huì)異常終止。處理器層面也對(duì)可執(zhí)行代碼所 在的內(nèi)存區(qū)域進(jìn)行了限定,這樣攻擊者很難再向程序的棧空間插入攻擊系統(tǒng)的可執(zhí)行代碼了。

    棧與堆

    如果我說(shuō) C 語(yǔ)言是一種部分支持垃圾內(nèi)存回收的語(yǔ)言……你可能會(huì)認(rèn)為我腦子壞掉了。事實(shí)上,C 語(yǔ)言中的所有的局部變量包括指針超出作用域時(shí),它們所占據(jù)的存儲(chǔ)空間都會(huì)被『回收』。這算不算內(nèi)存垃圾回收?

    從 C 程序的角度來(lái)看,內(nèi)存并非一個(gè)以字節(jié)為單位的一個(gè)很大但是又經(jīng)常不夠用的空間,不是一個(gè),而是兩個(gè)。其中一個(gè)空間叫棧,另一個(gè)空間叫堆。可被 C 程序『回收』存儲(chǔ)空間是??臻g。也就是說(shuō),在一個(gè)函數(shù)中,所有的局部變量所占據(jù)的存儲(chǔ)空間屬于棧空間??赡茉僬f(shuō)的學(xué)術(shù)一點(diǎn),就是所有的左值都在??臻g(我 不確定這樣說(shuō)到底是不是正確)。

    當(dāng)一個(gè)函數(shù)運(yùn)行結(jié)束,它所占據(jù)的棧空間就不再屬于它了,而是將會(huì)被一個(gè)新的待運(yùn)行的函數(shù)占據(jù)。所以,從本質(zhì)上說(shuō),C 程序?qū)?臻g的回收都不屑一顧,因?yàn)樗静换厥?#xff0c;而是舊的數(shù)據(jù)會(huì)被新的數(shù)據(jù)覆蓋。

    堆空間,我們?cè)诔绦蚶餆o(wú)法直接訪問(wèn),只能借助指針。因?yàn)槎芽臻g的內(nèi)存地址可被指針引用。例如,當(dāng)使用?malloc?分配空間時(shí),所分配空間的基地址總是保存在一個(gè)位于棧空間的指針中的。

    棧空間通常遠(yuǎn)遠(yuǎn)小于堆空間,即便如此也幾乎不會(huì)出現(xiàn)某個(gè)函數(shù)會(huì)耗盡棧空間的現(xiàn)象。如果這種現(xiàn)象出現(xiàn)了,那只能證明造出這種現(xiàn)象的程序猿應(yīng)該繼續(xù)學(xué)習(xí) C 語(yǔ)言了。??臻g被耗盡,往往是因?yàn)橛行┏绦虮緛?lái)是寫(xiě)成遞歸,但可能是代碼寫(xiě)錯(cuò)了,導(dǎo)致遞而不歸;還有一種可能是遞歸層次太深,這時(shí)可以想辦法在堆空間中模 擬一個(gè)棧來(lái)解決。還有一種情況就是在函數(shù)中定義了很大的數(shù)組,導(dǎo)致棧空間放不下……這種情況總是可以靠分配堆空間來(lái)解決。

    數(shù)據(jù)的抽象

    當(dāng)你具備了一些 C 編程基礎(chǔ),并且能夠理解上文中的內(nèi)容,那么你就可以對(duì)各種類(lèi)型的數(shù)據(jù)進(jìn)行抽象了。

    我們?yōu)槭裁匆獙?duì)數(shù)據(jù)進(jìn)行抽象?《計(jì)算機(jī)程序的構(gòu)造和解釋》的第 2 章的導(dǎo)言部分給出了很好的答案,即:許多程序在設(shè)計(jì)時(shí)就是為了模擬復(fù)雜的現(xiàn)象,因?yàn)樗鼈兙统3P枰獦?gòu)造出一些運(yùn)算對(duì)象,為了能夠模擬真實(shí)世界中的現(xiàn)象的各個(gè)方面,需要將運(yùn)算對(duì)象表示為一些組件的復(fù)合結(jié)構(gòu)。

    下面來(lái)對(duì)自行車(chē)鏈的任意一個(gè)鏈節(jié)進(jìn)行模擬:

  • struct?chain_node?{?
  • ????????struct?chain_node?*prev;?
  • ????????struct?chain_node?*next;?
  • ????????void?*shape;?
  • };?
  • 然后我們可以造出 3 個(gè)鏈節(jié),然后可以造出世界上最短的車(chē)鏈:

  • struct?chain_node?a,?b,?c;?
  • ?
  • a.next?=?&b;?
  • b.prev?=?&a;?
  • ?
  • b.next?=?&c;?
  • c.prev?=?&b;?
  • ?
  • c.next?=?&a;?
  • a.prev?=?&c;?
  • 如果再多造一些鏈節(jié),就可以得到周長(zhǎng)大一些的車(chē)鏈,也能夠制造出各種形狀的多邊形,但是最好是借助無(wú)名的內(nèi)存空間。下面的代碼可以創(chuàng)建一條具有 1000 個(gè)鏈節(jié)的鏈條:

  • struct?chain_node?*head?=?malloc(sizeof(struct?chain_node));?
  • struct?chain_node?*tail?=?head;?
  • for?(int?i?=?0;?i?<?1000;?i++)?{?
  • ????????struct?chain_node?*new_tail?=?malloc(sizeof(struct?chain_node));?
  • ????????tail->next?=?new_tail;?
  • ????????new_tail->prev?=?tail;?
  • ????????tail?=?new_tail;?
  • }?
  • tail->next?=?head;?
  • head->prev?=?tail;?
  • 如果我們將前面那個(gè)示例中的?a,b,?c?視為三角形的三個(gè)頂點(diǎn),那么我們所創(chuàng)造的三個(gè)鏈節(jié)構(gòu)成的鏈條就變成了一個(gè)三角形。同理,上述所創(chuàng)建的 1000 個(gè)鏈節(jié)的鏈條就變成了一個(gè) 1000 條邊首尾相接的多邊形。如果學(xué)過(guò)拓?fù)鋵W(xué),那么自然可以發(fā)現(xiàn)任何與圓環(huán)同胚的結(jié)構(gòu)都可以基于?struct chain_node?這種數(shù)據(jù)結(jié)構(gòu)模擬出來(lái),而我們所仰仗的東西僅僅是將三個(gè)指針?lè)庋b到一個(gè)結(jié)構(gòu)體中。

    事實(shí)上,struct chain_node?中的第三個(gè)指針?void *shape?還沒(méi)被用到。這是一個(gè)?void *?類(lèi)型的指針,是喜歡用 C 代碼玩各種抽象的程序猿的最?lèi)?ài),因?yàn)樗芤萌魏晤?lèi)型數(shù)據(jù)所在內(nèi)存空間的基地址。這就意味著?struct chain_node?可以借助?shape?指針獲得強(qiáng)大的擴(kuò)展能力。

    現(xiàn)在,我要制造一種很簡(jiǎn)陋的鏈節(jié),它的形狀僅僅是一個(gè)矩形的小鐵片,上面打了兩個(gè)小圓孔。我將它的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)為:

  • struct?point?{?
  • ????????double?x;?
  • ????????double?y;?
  • };?
  • ?
  • struct?rectangle?{?
  • ????????double?width;?
  • ????????double?height;?
  • };?
  • ?
  • struct?circle?{?
  • ????????struct?point?*center;?
  • ????????double?radius;?
  • };?
  • ?
  • struct?chain_node_shape?{?
  • ????????struct?rectangle?*body;?
  • ????????struct?circle?*holes[2]?;?
  • };?
  • 基于這些數(shù)據(jù)結(jié)構(gòu),我就可以寫(xiě)出一個(gè)專(zhuān)門(mén)用來(lái)制造矩形小鐵片的函數(shù):

  • struct?chain_node_shape?*?
  • create_chain_node_shape(struct?circle?*c1,?
  • ????????????????????????struct?circle?*c2,?
  • ????????????????????????struct?rectangle?*rect)?
  • {?
  • ????????struct?chain_node_shape?*ret?=?malloc(sizeof(struct?chain_node_shape));?
  • ????????ret->body?=?rect;?
  • ????????ret->holes[0]?=?c1;?
  • ????????ret->holes[1]?=?c2;?
  • ????????return?ret;?
  • }?
  • 然后再為?create_chain_node_shape?所接受的兩種參數(shù)寫(xiě)出相應(yīng)的構(gòu)造函數(shù):

  • struct?circle?*?
  • create_circle(struct?point?*center,?double?radius)?
  • {?
  • ????????struct?circle?*ret?=?malloc(sizeof(struct?circle));?
  • ????????ret->center?=?center;?
  • ????????ret->radius?=?radius;?
  • ????????return?ret;?
  • }?
  • ?
  • struct?rectangle?*?
  • create_rectangle(double?w,?double?h)?
  • {?
  • ????????struct?rectangle?*ret?=?malloc(sizeof(struct?rectangle));?
  • ????????ret->width?=?w;?
  • ????????ret->height?=?h;?
  • ????????return?ret;?
  • }?
  • 為了讓?create_circle?更方便使用,最好再創(chuàng)建一個(gè)?struct point?的構(gòu)造函數(shù):

  • struct?point?*?
  • create_point(double?x,?double?y)?
  • {?
  • ????????struct?point?*ret?=?malloc(sizeof(struct?point));?
  • ????????ret->x?=?x;?
  • ????????ret->y?=?y;?
  • ????????return?ret;?
  • }?
  • 一切所需要的構(gòu)件都已準(zhǔn)備完畢,現(xiàn)在可以開(kāi)始生產(chǎn)某種特定型號(hào)的鏈節(jié)了,即:

  • struct?chain_node?*?
  • create_chain_node(void)?
  • {?
  • ????????double?radius?=?0.5;?
  • ????????double?left_x?=?1.0;?
  • ????????double?left_y?=?1.0;?
  • ????????struct?point?*left_center?=?create_point(left_x,?left_y);?
  • ????????struct?circle?*left_hole?=?create_circle(left_center,?radius);?
  • ????????double?right_x?=?9.0;?
  • ????????double?right_y?=?1.0;?
  • ????????struct?point?*right_center?=?create_point(right_x,?right_y);?
  • ????????struct?circle?*right_hole?=?create_circle(right_center,?radius);?
  • ????????struct?rectangle?*body?=?create_rectangle(10.0,??2.0);?
  • ?
  • ????????struct?chain_node?*ret?=?malloc(sizeof(struct?chain_node));?
  • ????????ret->prev?=?NULL;?
  • ????????ret->next?=?NULL;?
  • ????????ret->shape?=?create_chain_node_shape(left_hole,?right_hole,?body);?
  • ????????return?ret;?
  • }?
  • 最后再將制造鏈條的代碼略作修改:

  • struct?chain_node?*head?=?create_chain_node();?
  • struct?chain_node?*tail?=?head;?
  • for?(int?i?=?0;?i?<?1000;?i++)?{?
  • ????????struct?chain_node?*new_tail?=?create_chain_node();?
  • ????????tail->next?=?new_tail;?
  • ????????new_tail->prev?=?tail;?
  • ????????tail?=?new_tail;?
  • }?
  • tail->next?=?head;?
  • head->prev?=?tail;?
  • 現(xiàn)在我們所模擬的車(chē)鏈與現(xiàn)實(shí)中的車(chē)鏈已經(jīng)有些形似了。上述代碼雖然有些冗長(zhǎng),下文會(huì)對(duì)其進(jìn)行重構(gòu),現(xiàn)在先來(lái)總結(jié)一下上述代碼中指針的用法。

    仔細(xì)觀察上述代碼中我們所定義的結(jié)構(gòu)體,它們的共同特征是:所有非 C 內(nèi)建的數(shù)據(jù)類(lèi)型都是結(jié)構(gòu)體類(lèi)型,當(dāng)它們作為某個(gè)結(jié)構(gòu)體成員類(lèi)型時(shí)均被聲明為指針類(lèi)型。為什么要這樣?如果你真的打算問(wèn)這個(gè)問(wèn)題,那么就請(qǐng)你觀察一下上述的 5 個(gè)create_xxx?函數(shù),你會(huì)發(fā)現(xiàn)這些?create?函數(shù)的參數(shù)與返回值也都是結(jié)構(gòu)體類(lèi)型的指針。將這些現(xiàn)象綜合起來(lái),可以得出以下結(jié)論:

  • 將結(jié)構(gòu)體指針作為函數(shù)的參數(shù)與返回值,可以避免函數(shù)調(diào)用時(shí)發(fā)生過(guò)多的內(nèi)存復(fù)制。

  • 當(dāng)一個(gè)結(jié)構(gòu)體類(lèi)型作為其他結(jié)構(gòu)體的成員類(lèi)型時(shí),將前者聲明為指針類(lèi)型,可以在后者的?create?函數(shù)中避免繁瑣的解引用。

  • void *?指針可以引用任意類(lèi)型的數(shù)據(jù)存儲(chǔ)空間的基地址。例如在?create_chain_node?函數(shù)的定義中,我們將一個(gè)struct chain_node_shape?類(lèi)型的指針賦給了?void *?類(lèi)型的指針?shape。

  • 這三條結(jié)論是指針在數(shù)據(jù)抽象中的慣用手法,它不僅關(guān)系到數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì),也關(guān)系到數(shù)據(jù)結(jié)構(gòu)的構(gòu)造與銷(xiāo)毀函數(shù)的設(shè)計(jì)。(上述代碼為了省事,沒(méi)有定義數(shù)據(jù)結(jié)構(gòu)的銷(xiāo)毀函數(shù))

    數(shù)據(jù)再抽象

    上一節(jié)的代碼有些冗長(zhǎng),我們可以嘗試對(duì)其進(jìn)行精簡(jiǎn)。首先看下面這三個(gè)結(jié)構(gòu)體及其?create?函數(shù):

  • struct?point?{?
  • ????????double?x;?
  • ????????double?y;?
  • };?
  • ?
  • struct?rectangle?{?
  • ????????double?width;?
  • ????????double?height;?
  • };?
  • ?
  • struct?circle?{?
  • ????????struct?point?*center;?
  • ????????double?radius;?
  • };?
  • ?
  • struct?chain_node_shape?{?
  • ????????struct?rectangle?*body;?
  • ????????struct?circle?*holes[2]?;?
  • };?
  • ?
  • struct?point?*?
  • create_point(double?x,?double?y)?
  • {?
  • ????????struct?point?*ret?=?malloc(sizeof(struct?point));?
  • ????????ret->x?=?x;?
  • ????????ret->y?=?y;?
  • ????????return?ret;?
  • }?
  • ?
  • struct?circle?*?
  • create_circle(struct?point?*center,?double?radius)?
  • {?
  • ????????struct?circle?*ret?=?malloc(sizeof(struct?circle));?
  • ????????ret->center?=?center;?
  • ????????ret->radius?=?radius;?
  • ????????return?ret;?
  • }?
  • ?
  • struct?rectangle?*?
  • create_rectangle(double?w,?double?h)?
  • {?
  • ????????struct?rectangle?*ret?=?malloc(sizeof(struct?rectangle));?
  • ????????ret->width?=?w;?
  • ????????ret->height?=?h;?
  • ????????return?ret;?
  • }?
  • ?
  • struct?chain_node_shape?*?
  • create_chain_node_shape(struct?circle?*c1,?
  • ????????????????????????struct?circle?*c2,?
  • ????????????????????????struct?rectangle?*rect)?
  • {?
  • ????????struct?chain_node_shape?*ret?=?malloc(sizeof(struct?chain_node_shape));?
  • ????????ret->body?=?rect;?
  • ????????ret->holes[0]?=?c1;?
  • ????????ret->holes[1]?=?c2;?
  • ????????return?ret;?
  • }?
  • 顯然,這些代碼長(zhǎng)的太像了!那四個(gè)結(jié)構(gòu)體都是存儲(chǔ)兩個(gè)成員的結(jié)構(gòu)體,而相應(yīng)的?create?函數(shù)也無(wú)非是將函數(shù)所接受的參數(shù)保存到結(jié)構(gòu)體成員中。有沒(méi)有辦法用很少的代碼來(lái)表示它們?有!

    既然每個(gè)結(jié)構(gòu)體都保存 2 個(gè)成員,那么我們就先將上述代碼刪掉,然后定義一個(gè)?pair?類(lèi)型的結(jié)構(gòu)體:

  • struct?pair?{?
  • ????????void?*first;?
  • ????????void?*second;?
  • };?
  • 在?pair?結(jié)構(gòu)體中,我們用了兩個(gè)?void *?指針,只有如此我們方能很自信的說(shuō)?pair?可以存儲(chǔ)任意類(lèi)型的兩個(gè)數(shù)據(jù)。接下來(lái),只需修改?create_chain_node?函數(shù)的定義:

  • struct?chain_node?*?
  • create_chain_node(void)?
  • {?
  • ????????double?*left_x?=?malloc(sizeof(double));?
  • ????????double?*left_y?=?malloc(sizeof(double));?
  • ????????*left_x?=?1.0;?
  • ????????*left_y?=?1.0;?
  • ????????struct?pair?*left_center?=?malloc(sizeof(struct?pair));?
  • ????????left_center->first?=?left_x;?
  • ????????left_center->second?=?left_y;?
  • ????????double?*left_radius?=?malloc(sizeof(double));?
  • ????????*left_radius?=?0.5;?
  • ????????struct?pair?*left_hole?=?malloc(sizeof(struct?pair));?
  • ????????left_hole->first?=?left_center;?
  • ????????left_hole->second?=?left_radius;?
  • ?
  • ????????double?*right_x?=?malloc(sizeof(double));?
  • ????????double?*right_y?=?malloc(sizeof(double));?
  • ????????*right_x?=?9.0;?
  • ????????*right_y?=?1.0;?
  • ????????struct?pair?*right_center?=?malloc(sizeof(struct?pair));?
  • ????????right_center->first?=?right_x;?
  • ????????right_center->second?=?right_y;?
  • ????????double?*right_radius?=?malloc(sizeof(double));?
  • ????????*right_radius?=?0.5;?
  • ????????struct?pair?*right_hole?=?malloc(sizeof(struct?pair));?
  • ????????right_hole->first?=?right_center;?
  • ????????right_hole->second?=?right_radius;?
  • ?
  • ????????struct?pair?*holes?=?malloc(sizeof(struct?pair));?
  • ????????holes->first?=?left_hole;?
  • ????????holes->second?=?right_hole;?
  • ?
  • ????????struct?pair?*body?=?malloc(sizeof(struct?pair));?
  • ????????double?*width?=?malloc(sizeof(double));?
  • ????????*width?=?10.0;?
  • ????????double?*height?=?malloc(sizeof(double));?
  • ????????*height?=?2.0;?
  • ????????body->first?=?width;?
  • ????????body->second?=?height;?
  • ?
  • ????????struct?pair?*shape?=?malloc(sizeof(struct?pair));?
  • ????????shape->first?=?body;?
  • ????????shape->second?=?holes;?
  • ?
  • ????????struct?chain_node?*ret?=?malloc(sizeof(struct?chain_node));?
  • ????????ret->prev?=?NULL;?
  • ????????ret->next?=?NULL;?
  • ????????ret->shape?=?shape;?
  • ????????return?ret;?
  • }?
  • 我勇敢的承認(rèn)這個(gè)基于?struct pair?的?create_chain_node?函數(shù)太丑陋了,但是我們總算是消除了大量的結(jié)構(gòu)體及其構(gòu)造函數(shù)了,而且整體代碼量減少了大約 1/6。

    仔細(xì)觀察上述代碼,顯然下面的三段代碼存在著高度的重復(fù):

  • double?*left_x?=?malloc(sizeof(double));?
  • double?*left_y?=?malloc(sizeof(double));?
  • *left_x?=?1.0;?
  • *left_y?=?1.0;?
  • struct?pair?*left_center?=?malloc(sizeof(struct?pair));?
  • left_center->first?=?left_x;?
  • left_center->second?=?left_y;?
  • ?
  • double?*right_x?=?malloc(sizeof(double));?
  • double?*right_y?=?malloc(sizeof(double));?
  • *right_x?=?9.0;?
  • *right_y?=?1.0;?
  • struct?pair?*right_center?=?malloc(sizeof(struct?pair));?
  • right_center->first?=?right_x;?
  • right_center->second?=?right_y;?
  • ?
  • struct?pair?*body?=?malloc(sizeof(struct?pair));?
  • double?*width?=?malloc(sizeof(double));?
  • *width?=?10.0;?
  • double?*height?=?malloc(sizeof(double));?
  • *height?=?2.0;?
  • body->first?=?width;?
  • body->second?=?height;?
  • 這三段代碼都在向?pair?結(jié)構(gòu)體中存入兩個(gè)?double *?類(lèi)型的數(shù)據(jù)。既然如此,我們可以專(zhuān)門(mén)寫(xiě)一個(gè)函數(shù),讓它生成面向double *?的?pair?結(jié)構(gòu)體,即:

  • struct?pair?*?
  • pair_for_double_type(double?x,?double?y)?
  • {?
  • ????????struct?pair?*ret?=?malloc(sizeof(struct?pair));?
  • ????????double?*first?=?malloc(sizeof(double));?
  • ????????double?*second?=?malloc(sizeof(double));?
  • ????????*first?=?x;?
  • ????????*second?=?y;?
  • ????????ret->first?=?first;?
  • ????????ret->second?=?first;?
  • ????????return?ret;?
  • }?
  • 然后再次重構(gòu)?create_chain_node?函數(shù):

  • struct?chain_node?*?
  • create_chain_node(void)?
  • {?
  • ????????struct?pair?*left_center?=?pair_for_double_type(1.0,?1.0);?
  • ????????double?*left_radius?=?malloc(sizeof(double));?
  • ????????*left_radius?=?0.5;?
  • ????????struct?pair?*left_hole?=?malloc(sizeof(struct?pair));?
  • ????????left_hole->first?=?left_center;?
  • ????????left_hole->second?=?left_radius;?
  • ?
  • ????????struct?pair?*right_center?=?pair_for_double_type(9.0,?1.0);?
  • ????????double?*right_radius?=?malloc(sizeof(double));?
  • ????????*right_radius?=?0.5;?
  • ????????struct?pair?*right_hole?=?malloc(sizeof(struct?pair));?
  • ????????right_hole->first?=?right_center;?
  • ????????right_hole->second?=?right_radius;?
  • ?
  • ????????struct?pair?*holes?=?malloc(sizeof(struct?pair));?
  • ????????holes->first?=?left_hole;?
  • ????????holes->second?=?right_hole;?
  • ?
  • ????????struct?pair?*body?=?pair_for_double_type(10.0,?1.0);?
  • ?
  • ????????struct?pair?*shape?=?malloc(sizeof(struct?pair));?
  • ????????shape->first?=?body;?
  • ????????shape->second?=?holes;?
  • ?
  • ????????struct?chain_node?*ret?=?malloc(sizeof(struct?chain_node));?
  • ????????ret->prev?=?NULL;?
  • ????????ret->next?=?NULL;?
  • ????????ret->shape?=?shape;?
  • ????????return?ret;?
  • }?
  • 山重水復(fù)疑無(wú)路

    經(jīng)過(guò)再次重構(gòu)后的?create_chain_node?看上去要好了一些,但是依然有兩段代碼存在高度重復(fù):

  • struct?pair?*left_center?=?pair_for_double_type(1.0,?1.0);?
  • double?*left_radius?=?malloc(sizeof(double));?
  • *left_radius?=?0.5;?
  • struct?pair?*left_hole?=?malloc(sizeof(struct?pair));?
  • left_hole->first?=?left_center;?
  • left_hole->second?=?left_radius;?
  • ?
  • struct?pair?*right_center?=?pair_for_double_type(9.0,?1.0);?
  • double?*right_radius?=?malloc(sizeof(double));?
  • *right_radius?=?0.5;?
  • struct?pair?*right_hole?=?malloc(sizeof(struct?pair));?
  • right_hole->first?=?right_center;?
  • right_hole->second?=?right_radius;?
  • 但是僅從?pair?結(jié)果體層面已經(jīng)無(wú)法對(duì)這兩段代碼進(jìn)行簡(jiǎn)化了,而且我又非常不想寫(xiě)一個(gè)像下面這樣的輔助函數(shù):

  • struct?pair?*?
  • create_hole(struct?pair?*center,?double?radius)?
  • {?
  • ????????struct?pair?*ret?=?malloc(sizeof(struct?pair));?
  • ????????double?*r?=?malloc(sizeof(double));?
  • ????????*r?=?radius;?
  • ????????ret->first?=?center;?
  • ????????ret->second?=?r;?
  • ????????return?ret;?
  • }?
  • 雖然?create_hole?能夠?qū)⑸鲜鰞啥沃貜?fù)的代碼簡(jiǎn)化為:

  • struct?pair?*left_center?=?pair_for_double_type(1.0,?1.0);?
  • struct?pair?*left_hole?=?create_hole(left_center,?0.5);?
  • ?
  • struct?pair?*right_center?=?pair_for_double_type(9.0,?1.0);?
  • struct?pair?*right_hole?=?create_hole(right_center,?0.5);?
  • 但是與?pair_for_double_type?函數(shù)相比,create_hole?這個(gè)函數(shù)的應(yīng)用范圍非常狹小。由于?pair_for_double_type函數(shù)可以將兩個(gè)?double?類(lèi)型的數(shù)據(jù)存儲(chǔ)到?pair?結(jié)構(gòu)體中,在我們的例子中創(chuàng)建二維點(diǎn)與矩形可以用到它,在科學(xué)計(jì)算中創(chuàng)建極坐標(biāo)、復(fù)數(shù)以及所有的二次曲線方程式也都都能用到它,但是?create_hole?卻只能在創(chuàng)建車(chē)鏈這件事上有點(diǎn)用處。也就是說(shuō),正是因?yàn)?pair_for_double_type?函數(shù)所取得的成功,導(dǎo)致我們認(rèn)為?create_hole?的品味太低。我們應(yīng)該想一想還有沒(méi)有其他途徑可以消除上述代碼的重復(fù)。

    仔細(xì)分析?left_hole?與?right_hole?的構(gòu)造過(guò)程,不難發(fā)現(xiàn)?hole?的?center?與?radius?這兩種數(shù)據(jù)的類(lèi)型不一致是造成我們難以對(duì)上述重復(fù)的代碼進(jìn)行有效簡(jiǎn)化的主要原因,create_hole?之所以能夠?qū)ι鲜鲋貜?fù)的代碼進(jìn)行大幅簡(jiǎn)化,是因?yàn)樗鶕?jù)我們的問(wèn)題構(gòu)造了一個(gè)特殊的?pair?結(jié)構(gòu)體——姑且稱(chēng)之為 X。X 結(jié)構(gòu)體的特殊指出在于其?first?指針存儲(chǔ)的是一個(gè)面向?double *?的同構(gòu)類(lèi)型的?pair?結(jié)構(gòu)體,其?second?指針則存儲(chǔ)了一個(gè)?double?類(lèi)型數(shù)據(jù)的基地址。正是因?yàn)?X 的結(jié)構(gòu)太特殊了,所以導(dǎo)致?create_hole?這種抽象的應(yīng)用范圍過(guò)于狹隘,以至于現(xiàn)實(shí)中只有圓形比較符合這種結(jié)構(gòu)體。

    既然是異構(gòu)的?pair,而我們已經(jīng)實(shí)現(xiàn)了一個(gè)可以創(chuàng)建存儲(chǔ)?double?類(lèi)型數(shù)據(jù)的?pair?的函數(shù)?pair_for_double_type,這個(gè)函數(shù)的結(jié)果是可以直接存入異構(gòu)?pair?中的。現(xiàn)在我們?nèi)鄙僦皇且粋€(gè)可以將?double?值轉(zhuǎn)化為可直接存入異構(gòu)?pair的函數(shù),即:

  • double?*?
  • malloc_double(double?x)?
  • {?
  • ????????double?*ret?=?malloc(sizeof(double));?
  • ????????*ret?=?x;?
  • ????????return?ret;?
  • }?
  • 有了這個(gè)函數(shù),就可以對(duì)?create_chain_node?繼續(xù)進(jìn)行簡(jiǎn)化了:

  • struct?chain_node?*?
  • create_chain_node(void)?
  • {?
  • ????????struct?pair?*left_hole?=?malloc(sizeof(struct?pair));?
  • ????????left_hole->first?=?pair_for_double_type(1.0,?1.0);;?
  • ????????left_hole->second?=?malloc_double(0.5);?
  • ?
  • ????????struct?pair?*right_hole?=?malloc(sizeof(struct?pair));?
  • ????????right_hole->first?=?pair_for_double_type(9.0,?1.0);;?
  • ????????right_hole->second?=?malloc_double(0.5);?
  • ?
  • ????????struct?pair?*holes?=?malloc(sizeof(struct?pair));?
  • ????????holes->first?=?left_hole;?
  • ????????holes->second?=?right_hole;?
  • ?
  • ????????struct?pair?*body?=?pair_for_double_type(10.0,?1.0);?
  • ?
  • ????????struct?pair?*shape?=?malloc(sizeof(struct?pair));?
  • ????????shape->first?=?body;?
  • ????????shape->second?=?holes;?
  • ?
  • ????????struct?chain_node?*ret?=?malloc(sizeof(struct?chain_node));?
  • ????????ret->prev?=?NULL;?
  • ????????ret->next?=?NULL;?
  • ????????ret->shape?=?shape;?
  • ????????return?ret;?
  • }?
  • 而且,基于?malloc_double?函數(shù),還能對(duì)?pair_for_double_type?函數(shù)進(jìn)行簡(jiǎn)化:

  • struct?pair?*?
  • pair_for_double_type(double?x,?double?y)?
  • {?
  • ????????struct?pair?*ret?=?malloc(sizeof(struct?pair));?
  • ????????ret->first?=?malloc_double(x);?
  • ????????ret->second?=?malloc_double(y);?
  • ????????return?ret;?
  • }?
  • 事實(shí)上,如果我們?cè)儆幸粋€(gè)這樣的函數(shù):

  • struct?pair?*?
  • pair(void?*x,?void?*y)?
  • {?
  • ????????struct?pair?*ret?=?malloc(sizeof(struct?pair));?
  • ????????ret->first?=?x;?
  • ????????ret->second?=?y;?
  • ????????return?ret;?
  • }?
  • 還能對(duì)?reate_chain_node?再做一步簡(jiǎn)化:

  • struct?chain_node?*?
  • create_chain_node(void)?
  • {?
  • ????????struct?pair?*left_hole?=?pair(pair_for_double_type(1.0,?1.0),?malloc_double(0.5));?
  • ????????struct?pair?*right_hole?=?pair(pair_for_double_type(9.0,?1.0),?malloc_double(0.5));?
  • ????????struct?pair?*holes?=?pair(left_hole,?right_hole);?
  • ????????struct?pair?*body?=?pair_for_double_type(10.0,?1.0);?
  • ????????struct?pair?*shape?=?pair(body,?holes);?
  • ?
  • ????????struct?chain_node?*ret?=?malloc(sizeof(struct?chain_node));?
  • ????????ret->prev?=?NULL;?
  • ????????ret->next?=?NULL;?
  • ????????ret->shape?=?shape;?
  • ????????return?ret;?
  • }?
  • 看到了吧,只要略微換個(gè)角度,很多看似難以簡(jiǎn)化的代碼都能得以簡(jiǎn)化。這個(gè)簡(jiǎn)化的過(guò)程一直是在指針的幫助下進(jìn)行的,但事實(shí)上,當(dāng)你的注意力一直集中在 怎么對(duì)代碼進(jìn)行簡(jiǎn)化時(shí),指針的使用簡(jiǎn)直就是本能一樣的存在,以至于你覺(jué)得你并沒(méi)有借助指針的任何力量,完全是你自己的邏輯在指導(dǎo)著你的行為。在這個(gè)過(guò)程 中,無(wú)論是面向?qū)ο筮€是面向模板,都很難將你從冗長(zhǎng)的代碼中拯救出來(lái)……

    面向什么,可能就會(huì)失去未面向的那些

    在上文中模擬車(chē)鏈的程序中,我一開(kāi)始是用面向?qū)ο蟮姆绞絹?lái)寫(xiě)的,所以我造出了 5 個(gè)結(jié)構(gòu)體,分別描述了二維點(diǎn)、矩形、圓形、鏈節(jié)形狀以及鏈節(jié)等對(duì)象,結(jié)果卻出現(xiàn)了一大堆繁瑣的代碼。雖然面向?qū)ο缶幊?#xff0c;在思維上是非常簡(jiǎn)單的,那就是現(xiàn)實(shí) 中有什么,我們就模擬什么。但是你認(rèn)真思考一下,現(xiàn)實(shí)中其實(shí)很多東西都有共性,如果你傻乎乎的去逐個(gè)模擬,而忽略它們的共性,那么你的代碼絕對(duì)會(huì)非常臃 腫。

    當(dāng)然,面向?qū)ο缶幊桃蔡岢珡乃M的事物中提取共性,然后借助繼承的方式來(lái)簡(jiǎn)化代碼。但是一旦信仰了類(lèi)與繼承,你能做的最好的抽象就是對(duì)某一類(lèi)事物 進(jìn)行抽象,比如你能夠?qū)Α很?chē)』類(lèi)的事物進(jìn)行抽象,但是你卻無(wú)法將對(duì)『飛機(jī)』和『車(chē)』這兩類(lèi)中的事物進(jìn)行抽象。顯然,飛機(jī)與車(chē)是有共性的,例如它們都能載 客,都有儀表盤(pán),都有窗戶,都有座位,都有服務(wù)員……

    當(dāng)我發(fā)現(xiàn)基于面向?qū)ο髣?chuàng)造的那些結(jié)構(gòu)體存在著一個(gè)共性——它們都包含著兩個(gè)成員,很自然的就會(huì)想到我應(yīng)該制造一個(gè)包含著兩個(gè)任意類(lèi)型的結(jié)構(gòu)體?pair,然后用?pair?來(lái)容納我需要的數(shù)據(jù)。當(dāng)面向?qū)ο缶幊谭妒皆谀愕乃枷胫懈畹俟?#xff0c;這種簡(jiǎn)單的現(xiàn)象往往會(huì)被忽略的,特別是你已經(jīng)滿足于你寫(xiě)的程序已經(jīng)能夠成功的運(yùn)行之時(shí)。

    接下來(lái),當(dāng)我試圖用?pair?結(jié)構(gòu)體取代二維點(diǎn)、矩形、圓形、鏈節(jié)形狀等結(jié)構(gòu)體的時(shí)候,我就開(kāi)始走上了『泛型』的道路。C 語(yǔ)言里沒(méi)有 C++ 模板這種工具可以用,所以我只能依賴(lài)?void *,而且為了簡(jiǎn)化?double?類(lèi)型的數(shù)據(jù)向?void *?的轉(zhuǎn)化,所以定義了:

  • double?*?
  • malloc_double(double?x)?
  • {?
  • ????????double?*ret?=?malloc(sizeof(double));?
  • ????????*ret?=?x;?
  • ????????return?ret;?
  • }?
  • ?
  • struct?pair?*?
  • pair_for_double_type(double?x,?double?y)?
  • {?
  • ????????struct?pair?*ret?=?malloc(sizeof(struct?pair));?
  • ????????ret->first?=?malloc_double(x);?
  • ????????ret->second?=?malloc_double(y);?
  • ????????return?ret;?
  • }?
  • 如果你對(duì) C++ 的泛型編程有所了解,一定會(huì)覺(jué)得?pair_for_double_type?函數(shù)其實(shí)就是對(duì)?pair?進(jìn)行特化。因?yàn)楸緛?lái)我是希望?pair?能存儲(chǔ)任意類(lèi)型的數(shù)據(jù)的,但是現(xiàn)在我需要頻繁的用它來(lái)存儲(chǔ)一對(duì)?double?類(lèi)型的數(shù)據(jù),那么我就應(yīng)該去制造一個(gè)專(zhuān)用的?pair?結(jié)構(gòu)。

    當(dāng)我發(fā)現(xiàn)我需要頻繁的產(chǎn)生?pair?實(shí)例,并向它的?first?與?second?指針中存儲(chǔ)某些類(lèi)型的數(shù)據(jù)存儲(chǔ)空間的基地址,所以我就將這種共性抽象為:

  • struct?pair?*?
  • pair(void?*x,?void?*y)?
  • {?
  • ????????struct?pair?*ret?=?malloc(sizeof(struct?pair));?
  • ????????ret->first?=?x;?
  • ????????ret->second?=?y;?
  • ????????return?ret;?
  • }?
  • 最終使得?create_chain_node?函數(shù)的定義即簡(jiǎn)潔又清晰:

  • struct?chain_node?*?
  • create_chain_node(void)?
  • {?
  • ????????struct?pair?*left_hole?=?pair(pair_for_double_type(1.0,?1.0),?malloc_double(0.5));?
  • ????????struct?pair?*right_hole?=?pair(pair_for_double_type(9.0,?1.0),?malloc_double(0.5));?
  • ????????struct?pair?*holes?=?pair(left_hole,?right_hole);?
  • ????????struct?pair?*body?=?pair_for_double_type(10.0,?1.0);?
  • ????????struct?pair?*shape?=?pair(body,?holes);?
  • ?
  • ????????struct?chain_node?*ret?=?malloc(sizeof(struct?chain_node));?
  • ????????ret->prev?=?NULL;?
  • ????????ret->next?=?NULL;?
  • ????????ret->shape?=?shape;?
  • ????????return?ret;?
  • }?
  • 原來(lái)我用面向?qū)ο缶幊谭妒剿鶎?xiě)的代碼是 104 行,換成泛型編程范式所寫(xiě)的代碼是 75 行。那么我可以斷定,是泛型編程拯救了面向?qū)ο髥?#xff1f;當(dāng)然不能!因?yàn)槲覀兊某绦蜻€沒(méi)有寫(xiě)完,我們還需要面向?qū)ο蟆?/p>

    對(duì)象的回歸

    先擺出?create_chain_node?函數(shù):

  • struct?chain_node?*?
  • create_chain_node(void)?
  • {?
  • ????????struct?pair?*left_hole?=?pair(pair_for_double_type(1.0,?1.0),?malloc_double(0.5));?
  • ????????struct?pair?*right_hole?=?pair(pair_for_double_type(9.0,?1.0),?malloc_double(0.5));?
  • ????????struct?pair?*holes?=?pair(left_hole,?right_hole);?
  • ????????struct?pair?*body?=?pair_for_double_type(10.0,?1.0);?
  • ????????struct?pair?*shape?=?pair(body,?holes);?
  • ?
  • ????????struct?chain_node?*ret?=?malloc(sizeof(struct?chain_node));?
  • ????????ret->prev?=?NULL;?
  • ????????ret->next?=?NULL;?
  • ????????ret->shape?=?shape;?
  • ????????return?ret;?
  • }?
  • create_chain_node?函數(shù)可以創(chuàng)建鏈節(jié),它是借助很抽象的?pair?結(jié)構(gòu)體將很多種類(lèi)型的數(shù)據(jù)層層封裝到了?chain+node結(jié)構(gòu)體中,那么我們?nèi)绾螐?chain_node?結(jié)構(gòu)體中提取這些數(shù)據(jù),并使之重現(xiàn)它們所模擬的現(xiàn)實(shí)事物?

    例如,我們?cè)鯓訌?chain_node?結(jié)構(gòu)體中獲取一個(gè)?left_hole?的信息?顯然,下面的代碼

  • struct?*t?=?create_chain_node();?
  • struct?pair?*shape?=?t->shape;?
  • struct?pair?*holes?=?shape->second;?
  • struct?pair?*left_hole?=?holes->first;?
  • 并不能解決我們的問(wèn)題,因?yàn)?left_hole?中只是兩個(gè)?void *?指針,而我們需要知道的是?left_hole?的中心與半徑。那么我們繼續(xù):

  • struct?pair?*center?=?left_hole->first;?
  • double?radius?=?*((double?*)(left_hole->second));?
  • 依然沒(méi)有解決我們的問(wèn)題,因?yàn)槲覀兿胍氖?left_hole?的中心,而不是一個(gè)包含著兩個(gè)?void *?指針的?center,所以需要繼續(xù):

  • double?center_x?=?*((double?*)(center->first));?
  • double?center_y?=?*((double?*)(center->second));?
  • 最后我們得到了三個(gè)?double?類(lèi)型的數(shù)據(jù),即?center_x,?center_y,?radius,于是似乎我們的任務(wù)完成了,但是你如何將上述過(guò)程寫(xiě)成一個(gè)函數(shù)?get_left_hole? C 語(yǔ)言中的函數(shù)只能有一個(gè)返回值。如果通過(guò)函數(shù)的參數(shù)來(lái)返回一些值,那么get_left_hole?是能寫(xiě)出來(lái)的,例如:

  • void?get_left_hole(struct?chain_node?*t,?double?*x,?double?*y,?double?*r)?
  • {?
  • ????????struct?pair?*shape?=?t->shape;?
  • ????????struct?pair?*holes?=?shape->second;?
  • ????????struct?pair?*left_hole?=?holes->first;?
  • ????????struct?pair?*center?=?left_hole->first;?
  • ????????*x?=?*((double?*)(center->first));?
  • ????????*y?=?*((double?*)(center->second));?
  • ????????*r?=?*((double?*)(left_hole->second));?
  • }?
  • 但是,如果你真的這么寫(xiě)了,那只能說(shuō)明再好的編程語(yǔ)言也無(wú)法挽救你的品味。

    我們應(yīng)該繼續(xù)挖掘指針的功能,像下面這樣定義?get_left_hole會(huì)更好一些:

  • struct?point?{?
  • ????????double?*x;?
  • ????????double?*y;?
  • };?
  • struct?hole?{?
  • ????????struct?point?*center;?
  • ????????double?*radius;?
  • };?
  • ?
  • struct?hole?*?
  • get_left_hole(struct?chain_node?*t)?
  • {?
  • ????????struct?pair?*shape?=?t->shape;?
  • ????????struct?pair?*holes?=?shape->second;?
  • ????????return?holes->first;?
  • }?
  • 好在哪?我們充分利用了 C 編譯器對(duì)數(shù)據(jù)類(lèi)型的隱式轉(zhuǎn)換,這實(shí)際上就是 C 編譯器的一種編譯期計(jì)算。這樣做可以避免在代碼中出現(xiàn)?*((double *)(...))?這樣的代碼。void *?指針總是能通過(guò)賦值語(yǔ)句自動(dòng)轉(zhuǎn)換為左值的,前提是你需要保證左值的類(lèi)型就是?void *?的原有類(lèi)型。這是 C 語(yǔ)言的一條清規(guī)戒律,不能遵守這條戒律的程序猿,也許再好的編程語(yǔ)言也無(wú)法挽救他。

    C++ 這個(gè)叛徒,所以無(wú)論它有多么強(qiáng)大,也無(wú)法拯救那些無(wú)法保證左值的類(lèi)型就是?void *?原有類(lèi)型的程序猿。用 C++ 編譯器迫使程序猿必須將

  • struct?pair?*shape?=?t->shape;?
  • struct?pair?*holes?=?shape->second;?
  • 寫(xiě)成:

  • struct?pair?*shape?=?(struct?pair?*)(t->shape);?
  • struct?pair?*holes?=?(struct?pair?*)(shape->second);?
  • 否則代碼就無(wú)法通過(guò)編譯。這樣做,除了讓代碼更加混亂之外,依然無(wú)法挽救那些無(wú)法保證左值的類(lèi)型就是?void *?原有類(lèi)型的程序猿,只會(huì)讓他們對(duì)裸指針以及類(lèi)型轉(zhuǎn)換這些事非常畏懼,逐漸就走上了惟類(lèi)型安全的形而上學(xué)的道路。C++ 11 帶來(lái)了新的智能指針以及右值引用,希望他們能得到這些新 C++ 式的拯救吧。

    當(dāng)我們用面向?qū)ο蟮乃悸穼?shí)現(xiàn)了?get_left_hole?之后,就可以像下面這樣使用它:

  • struct?*t?=?create_chain_node();?
  • struct?hole?*left_hole?=?get_left_hole(t);?
  • printf("%lf,?%lf,?%lf\n",?*(left_hole->center->x),?*(left_hole->center->y),?*(left_hole->radius));?
  • 一切都建立在指針上了,只是在最后要輸出數(shù)據(jù)的需用?*?對(duì)指針進(jìn)行解引用。

    上述代碼中有個(gè)特點(diǎn),left_hole?并不占用內(nèi)存,它僅僅是對(duì)?t?所引用的內(nèi)存空間的再度引用??赡苡腥藭?huì)擔(dān)心left_hole?具有直接訪問(wèn)?t?所引用的內(nèi)存空間的能力是非常危險(xiǎn)的……有什么危險(xiǎn)呢?你只需要清楚?left_hole?只是對(duì)其他空間的引用,而這一點(diǎn)自從你用了指針之后就已經(jīng)建立了這樣的直覺(jué)了,你想修改?left_hole?所引用的內(nèi)存空間中的數(shù)據(jù),就可以 do it,不想修改就不去 do it,這有何難?如果自己并不打算去修改?left_hole?所引用的內(nèi)存空間中的數(shù)據(jù),但是又擔(dān)心自己或他人會(huì)因?yàn)槭д`而修改了這些數(shù)據(jù)……你應(yīng)該將這些擔(dān)心寫(xiě)到?get_left_hole?的注釋里!

    對(duì)于只需要稍加注意就可以很大程度上避免掉的事,非要從編程語(yǔ)言的語(yǔ)法層面來(lái)避免,這真的是小題大作了。如果我們?cè)诰幊讨袑?duì)于?void *?指針的隱式類(lèi)型正確轉(zhuǎn)換率高達(dá) 99%,為何要為 1% 的失誤而修改編程語(yǔ)言,使之充滿各種巧妙迂回的技巧并使得代碼愈加晦澀難懂呢?

    《C 陷阱與缺陷》的作者給出了一個(gè)很好的比喻,在烹飪時(shí),你用菜刀的時(shí)候是否失手切傷過(guò)自己的手?怎樣改進(jìn)菜刀讓它在使用中更安全?你是否愿意使用這樣一把經(jīng) 過(guò)改良的菜刀?作者給出的答案是:我們很容易想到辦法讓一個(gè)工具更安全,代價(jià)是原來(lái)簡(jiǎn)單的工具現(xiàn)在要變得復(fù)雜一些。食品加工機(jī)一般有連鎖裝置,可以保護(hù)使 用者的手指不會(huì)受傷。然而菜刀卻不同,如果給菜刀這種簡(jiǎn)單、靈活的工具安裝可以保護(hù)手指的裝置,只能讓它失去簡(jiǎn)單性與靈活性。實(shí)際上,這樣做得到的結(jié)果也 許是一臺(tái)食品加工機(jī),而不再是一把菜刀。

    我成功的將本節(jié)的題目歪到了指針上?,F(xiàn)在再歪回來(lái),我們來(lái)談?wù)剬?duì)象。其實(shí)已經(jīng)沒(méi)什么好談的了,get_left_hole?返回的是泛型指針的類(lèi)型具化,借助這種類(lèi)型具化的指針我們可以有效避免對(duì)?pair?中的?void *?指針進(jìn)行類(lèi)型轉(zhuǎn)換的繁瑣過(guò)程。

    將函數(shù)變成數(shù)據(jù)

    再來(lái)看一下經(jīng)過(guò)大幅簡(jiǎn)化的?create_chain_node?函數(shù):

  • struct?chain_node?*?
  • create_chain_node(void)?
  • {?
  • ????????struct?pair?*left_hole?=?pair(pair_for_double_type(1.0,?1.0),?malloc_double(0.5));?
  • ????????struct?pair?*right_hole?=?pair(pair_for_double_type(9.0,?1.0),?malloc_double(0.5));?
  • ????????struct?pair?*holes?=?pair(left_hole,?right_hole);?
  • ????????struct?pair?*body?=?pair_for_double_type(10.0,?1.0);?
  • ????????struct?pair?*shape?=?pair(body,?holes);?
  • ?
  • ????????struct?chain_node?*ret?=?malloc(sizeof(struct?chain_node));?
  • ????????ret->prev?=?NULL;?
  • ????????ret->next?=?NULL;?
  • ????????ret->shape?=?shape;?
  • ????????return?ret;?
  • }?
  • 這個(gè)函數(shù)對(duì)于我們的示例而言,沒(méi)有什么問(wèn)題,但是它只能產(chǎn)生特定形狀的鏈節(jié),這顯然不夠通用。如果我們想更換一下鏈節(jié)的形狀,例如將原來(lái)的帶兩個(gè)小孔的矩形鐵片換成帶兩個(gè)小孔的橢圓形鐵片,那么我們將不得不重寫(xiě)一個(gè)create_elliptic_chain_node?函數(shù)。當(dāng)我們這樣做的時(shí)候,很容易發(fā)現(xiàn)?create_elliptic_chain_node?函數(shù)中同樣需要下面這段代碼:

  • struct?chain_node?*ret?=?malloc(sizeof(struct?chain_node));?
  • ret->prev?=?NULL;?
  • ret->next?=?NULL;?
  • ret->shape?=?shape;?
  • return?ret;?
  • 如果我們要生產(chǎn) 100 種形狀的鏈節(jié),那么上述代碼在不同的鏈節(jié)構(gòu)造函數(shù)的實(shí)現(xiàn)中要重復(fù)出現(xiàn) 100 次,這樣肯定不夠好,因?yàn)闀?huì)出現(xiàn) 500 行重復(fù)的代碼。太多的重復(fù)的代碼,這是對(duì)程序猿的最大的羞辱。

    面向?qū)ο蟮某绦蛟晨赡軙?huì)想到,我們可以為?chain_node?做一個(gè)基類(lèi),然后將上述共同的代碼封裝到基類(lèi)的構(gòu)造函數(shù),然后在各個(gè)?chain_node?各個(gè)派生類(lèi)的構(gòu)造函數(shù)中制造不同形狀的鏈節(jié)……在你要將事情搞復(fù)雜之前,建議先看一下這樣的代碼:

  • void?*?
  • rectangle_shape(void)?
  • {?
  • ????????struct?pair?*left_hole?=?pair(pair_for_double_type(1.0,?1.0),?malloc_double(0.5));?
  • ????????struct?pair?*right_hole?=?pair(pair_for_double_type(9.0,?1.0),?malloc_double(0.5));?
  • ????????struct?pair?*holes?=?pair(left_hole,?right_hole);?
  • ????????struct?pair?*body?=?pair_for_double_type(10.0,?1.0);?
  • ????????return?pair(body,?holes);?
  • }?
  • ?
  • struct?chain_node?*?
  • create_chain_node(void?*(*fp)(void))?
  • {?
  • ????????struct?chain_node?*ret?=?malloc(sizeof(struct?chain_node));?
  • ????????ret->prev?=?NULL;?
  • ????????ret->next?=?NULL;?
  • ????????ret->shape?=?fp();?
  • ????????return?ret;?
  • }?
  • 看到了吧,我將?create_chain_node?函數(shù)原定義中負(fù)責(zé)創(chuàng)建鏈節(jié)形狀的代碼全部的抽離了出去,將它們封裝到rectangle_shape?函數(shù)中,然后再讓?create_chain_node?函數(shù)接受一個(gè)函數(shù)指針形式的參數(shù)。這樣,當(dāng)我們需要?jiǎng)?chuàng)建帶兩個(gè)小孔的矩形形狀的鏈節(jié)時(shí),只需:

    struct chain_node *rect_chain_node = create_chain_node(rectangle_shape);

    如果我們像創(chuàng)建帶兩個(gè)小孔的橢圓形狀的鏈節(jié),可以先定義一個(gè)?elliptic_shape?函數(shù),然后將其作為參數(shù)傳給create_chain_node,即:

    struct chain_node *elliptic_chain_node = create_chain_node(elliptic_shape);

    這樣做,豈不是要比弄出一大堆類(lèi)與繼承的代碼更簡(jiǎn)潔有效嗎?

    在 C 語(yǔ)言中,函數(shù)名也是一種指針,它引用了函數(shù)代碼所在內(nèi)存空間的基地址。所以,我們可以將?rectangle_shape?這樣函數(shù)作為參數(shù)傳遞給?create_chain_node?函數(shù),然后在后者中調(diào)用前者。

    由于我們已經(jīng)將?chain_node?結(jié)構(gòu)體中的?shape?指針定義為?void *?指針了,因此對(duì)于?create_chain_node?函數(shù)所接受的函數(shù),其返回值是?void *?沒(méi)什么問(wèn)題。不僅沒(méi)問(wèn)題,更重要的是?void *(*fp)(void)?對(duì)所有不接受參數(shù)且返回指針類(lèi)型數(shù)據(jù)的函數(shù)的一種抽象。這意味著對(duì)于鏈節(jié)的形狀,無(wú)論它的形狀有多么特殊,我們總是能夠定義一個(gè)不接受參數(shù)且返回指針的函數(shù)來(lái)產(chǎn)生這種形狀,于是?create_chain_node?函數(shù)就因此具備了無(wú)限的擴(kuò)展能力。

    如果阿基米的德還活著,也許他會(huì)豪放的說(shuō),給我一個(gè)函數(shù)指針與一個(gè)?void *,我就能描述宇宙!

    代碼簡(jiǎn)化的基本原則

    當(dāng)你采用一切都是對(duì)象的世界觀編寫(xiě)代碼時(shí),一旦發(fā)現(xiàn)一些類(lèi)之間存在著共性的數(shù)據(jù)抽象,這往往意味著你需要?jiǎng)?chuàng)造一種泛型的數(shù)據(jù)容器,然后用這種容器與具體類(lèi)型的數(shù)據(jù)的組合來(lái)消除那些類(lèi)。

    當(dāng)你打算從泛型的數(shù)據(jù)容器中取數(shù)據(jù),并希望所取的數(shù)據(jù)能夠直觀的模擬現(xiàn)實(shí)中的事物時(shí),這往往意味著你要?jiǎng)?chuàng)造一些數(shù)據(jù)結(jié)構(gòu),然后讓泛型的數(shù)據(jù)容器中存 儲(chǔ)的數(shù)據(jù)流入這些數(shù)據(jù)結(jié)構(gòu)中,從而轉(zhuǎn)化為有類(lèi)型且具名的數(shù)據(jù)。這些數(shù)據(jù)結(jié)構(gòu)就類(lèi)似于各種各樣的觀察器或 Parser,我們通過(guò)它們解讀或修改泛型容器中的數(shù)據(jù)。

    當(dāng)某個(gè)函數(shù)?f?中有一部分代碼是與具體的問(wèn)題息息相關(guān),而另一部分代碼則與具體的問(wèn)題無(wú)關(guān)。為了讓這個(gè)函數(shù)具備強(qiáng)大的擴(kuò)展性,你需要將那些與具體問(wèn)題息息相關(guān)的代碼抽離到專(zhuān)用的函數(shù)中,然后再將這些專(zhuān)用函數(shù)傳遞給?f。

    回避 C 指針是要付出代價(jià)的

    在 C 語(yǔ)言中,在執(zhí)行這些基本原則時(shí),指針是最簡(jiǎn)單明快的工具,像是著名廚師庖丁手里的刀。在靜態(tài)類(lèi)型語(yǔ)言中,任何企圖回避指針的行為,必然會(huì)導(dǎo)致編程語(yǔ)言的語(yǔ)法復(fù)雜化或者削弱語(yǔ)言的表達(dá)能力。

    在 C++ 中為了回避指針,發(fā)明了引用——本質(zhì)上一種被弱化了的指針,結(jié)果導(dǎo)致 C++ 初學(xué)者經(jīng)常要問(wèn)『什么時(shí)候用指針,什么時(shí)候用引用』這樣的問(wèn)題。在智能指針未問(wèn)世之前,STL 提供的泛型容器無(wú)法存儲(chǔ)引用,為了避免在容器中存儲(chǔ)對(duì)象時(shí)發(fā)生過(guò)多的內(nèi)存復(fù)制,往往需要將指針存到容器中。當(dāng)某個(gè)函數(shù)在內(nèi)部創(chuàng)建了一個(gè)比較大的對(duì)象時(shí),這 個(gè)函數(shù)想將這個(gè)對(duì)象傳遞給其他對(duì)象時(shí),這時(shí)如果不借助指針,那只能是將這個(gè)大對(duì)象作為返回值,然后引發(fā)了對(duì)象數(shù)據(jù)不止一次被復(fù)制的過(guò)程。如果在函數(shù)中?new?一 個(gè)大對(duì)象,然后以指針的形式將其返回,這又與 C++ 一直想向用戶掩蓋指針的理想發(fā)生了矛盾……為了解決這個(gè)問(wèn)題,終于在 C++ 11 里搞出來(lái)一個(gè)挺復(fù)雜挺扭曲的右值引用的辦法,解決了在類(lèi)的復(fù)制構(gòu)造函數(shù)中偷偷的使用指針,但是類(lèi)的用戶卻看不到指針這樣的問(wèn)題……

    Java 回避指針的策略比 C++ 要高明一些。在 Java 中,即沒(méi)有指針也沒(méi)有引用。只要是類(lèi)的實(shí)例(對(duì)象),無(wú)論是將其作為參數(shù)傳遞給函數(shù),還是作為函數(shù)的返回值,還是將其復(fù)制給同類(lèi)的其他對(duì)象,都是在傳地 址,而不是在傳值。也就是說(shuō),Java 將所有的類(lèi)實(shí)例都潛在的作為指針來(lái)用的,只有那些基本類(lèi)型才是作為值來(lái)傳遞的。這種對(duì)數(shù)據(jù)類(lèi)型進(jìn)行了明確的區(qū)分的態(tài)度是值得點(diǎn)贊的,但是當(dāng) Java 想將一個(gè)函數(shù)(方法)傳遞給另一個(gè)函數(shù)(方法)時(shí),代碼就出現(xiàn)了扭曲,完全不能做到像 C 語(yǔ)言以指針的形式傳遞函數(shù)那樣簡(jiǎn)潔直觀。

    C# 在指針的處理上似乎要比 C++ 與 Java 好得多,但是將那些使用指針的代碼標(biāo)定為?unsafe,這是一種歧視。類(lèi)似于『嗟,來(lái)食!』。廉者不受嗟來(lái)之食。

    在動(dòng)態(tài)類(lèi)型語(yǔ)言中,例如 Python,據(jù)說(shuō)是一切皆引用,這樣很好。也可以直接將一個(gè)函數(shù)作為參數(shù)傳遞給另一個(gè)函數(shù),甚至還能在一個(gè)函數(shù)中返回一個(gè)函數(shù),這樣更好。動(dòng)態(tài)類(lèi)型語(yǔ)言 在語(yǔ)法、抽象能力、類(lèi)型安全以及資源管理方面很大程度上超越了 C、C++、Java 這些靜態(tài)類(lèi)型語(yǔ)言,但是用前者編寫(xiě)的程序的計(jì)算速度卻往往比后者慢上一倍。

    沒(méi)有完美的指針,也不會(huì)有完美的編程語(yǔ)言,這一切皆因我們是在機(jī)器上編程,而不是在我們的大腦里編程。

    C 程序猿的指針信條

    篡改《步槍兵信條》,自?shī)首詷?lè)。

    這是我的指針。雖有很多相似的,但這個(gè)是我的。我的指針是我的摯友,如同我的生命。我將運(yùn)用它如同運(yùn)用我的生命。指針沒(méi)了我便是廢物,我沒(méi)了指針便成為廢人。我將準(zhǔn)確無(wú)誤的使用我的指針,我將比敵人用的更好,我將在他的程序速度超過(guò)我之前超過(guò)他,我會(huì)超過(guò)他的。

    我與我的指針知道,編程中不論動(dòng)用多么優(yōu)雅的語(yǔ)言,動(dòng)用多么強(qiáng)大的標(biāo)準(zhǔn)庫(kù),面向多么強(qiáng)大的編程范式,都是沒(méi)意義的。只有解決問(wèn)題才有意義。我們會(huì)解決的。

    我的指針是人性的,就像我一樣,因?yàn)樗缤业纳R虼宋覍⑾駥?duì)兄弟一樣地了解它。我將了解它的弱點(diǎn),它的強(qiáng)項(xiàng),它的構(gòu)成,它所指的和指向它的。我將對(duì)指針持續(xù)建立完善的知識(shí)與技藝,使它們就如同我一般整裝待發(fā)。我們會(huì)成為彼此的一部分。

    在上帝面前我對(duì)這信條宣誓。我與我的指針是計(jì)算機(jī)的守衛(wèi)者,我們是問(wèn)題的克星,我們將拯救我的程序。但愿如此,直到不需要編程,沒(méi)有問(wèn)題,只有休息。


    來(lái)源:51CTO

    總結(jié)

    以上是生活随笔為你收集整理的我们来谈谈面向指针编程的那些事的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。