版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
1、面向指針編程的那些事時(shí)間:2015-12-31 19:23 點(diǎn)擊:22次面向?qū)ο缶幊?,面向設(shè)計(jì)模式編程(亦即設(shè)計(jì)模式),面向接口編程,面向模板編程(亦即泛型編程),面向函數(shù)編程(亦即函數(shù)式編程),面向多核時(shí)代的并行編程,面向大數(shù)據(jù)的機(jī)器學(xué)習(xí)編程這么多年,大家要面向的東西已經(jīng)夠多了,然而我看到的現(xiàn)象是,很多編程語(yǔ)言讓大家面向 xxx 的同時(shí)在竭力回避指針。我可不想面向這么多東西,所以我只好加入指針的黑暗勢(shì)力。我要不自量力的來(lái)寫一篇面向指針編程作為投名狀,借以表示我與軟件世界的光明勢(shì)力的徹底決裂。這個(gè)世界上,提供指針的編程語(yǔ)言很少,這樣的語(yǔ)言有匯編語(yǔ)言
2、、C/C+ 以及 Pascal 等。Pascal 我沒(méi)學(xué)過(guò)。匯編語(yǔ)言過(guò)于黑暗,我現(xiàn)在功力還不足以駕馭它。C+,我覺得它簡(jiǎn)直是黑暗勢(shì)力中的敗類它試圖掙脫指針,走向光明,結(jié)果卻出了一堆幺蛾子。所以我還是俗套的選 C 語(yǔ)言來(lái)闡述指針的黑暗力量。閱讀本文之前,請(qǐng)讀三遍 Unix 無(wú)名師說(shuō)的話:當(dāng)尊者 Ritchie 發(fā)明 C 時(shí),他將程序員放到緩沖溢出、堆損壞和爛指針 bug 的地獄中懲罰。然后自我安慰一下,如果地獄未能使我屈服,那么我會(huì)比地獄更黑暗更強(qiáng)大。指針是什么??jī)?nèi)存是以字節(jié)為單位的一個(gè)很大但是又經(jīng)常不夠用的空間。指針是內(nèi)存中 x 個(gè)連續(xù)的字節(jié)中存儲(chǔ)的數(shù)據(jù)在 32 位的機(jī)器上,x 的值為 4;在
3、 64 位機(jī)器上,x 值為 8。為了敘述的簡(jiǎn)便,本文只在 64 位的機(jī)器上談?wù)撝羔?。指針是一種數(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ù)??jī)?nèi)存地址。內(nèi)存是以字節(jié)為單
4、位的空間,其中每個(gè)字節(jié)都伴隨著一個(gè)地址,這個(gè)地址機(jī)器賦予的,并不是我們的程序編制的。你可以將整個(gè)內(nèi)存空間想象成一棟大樓,將字節(jié)想象為大樓中每個(gè)房間,將每個(gè)字節(jié)的地址想象為房間的門牌號(hào),于是指針中存儲(chǔ)的數(shù)據(jù)就類似于門牌號(hào)。如果你從未學(xué)過(guò) C 語(yǔ)言,讀到此處可能會(huì)問(wèn),我們?yōu)槭裁匆趦?nèi)存中存儲(chǔ)內(nèi)存地址?不知你是否住過(guò)賓館。在正規(guī)的賓館里,每個(gè)房間的門后都會(huì)貼著逃生路線圖,圖中存儲(chǔ)了該賓館與你的房間同一樓層內(nèi)的全部房間的門牌號(hào)以及它們的布局。如果你住酒店時(shí)從來(lái)也不看逃生路線圖,那么從現(xiàn)在開始,入住酒店后第一件事就是認(rèn)真的看一下它,關(guān)鍵時(shí)刻它能救你一命。在內(nèi)存中存儲(chǔ)內(nèi)存地址,雖然不是救你性命的,但是可以
5、藉此構(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ù)類型修飾,它表示 foo 是內(nèi)存中 4 個(gè)連續(xù)字節(jié)的首字節(jié)地址( 64 位機(jī)器上,int 類型的數(shù)據(jù)長(zhǎng)度為 4 個(gè)字節(jié))。C 編譯器總是會(huì)根據(jù)某個(gè)內(nèi)存地址相應(yīng)的類型來(lái)確定以該內(nèi)存地址起始的一段連續(xù)字節(jié)中所存儲(chǔ)的數(shù)據(jù)的邏輯意義。因此,當(dāng)我們用 int
6、 類型來(lái)修飾 foo,編譯器就會(huì)認(rèn)為以 foo 開始的連續(xù) 4 個(gè)字節(jié)中存儲(chǔ)的數(shù)據(jù)是一個(gè)整型數(shù)據(jù)。在上述代碼中,這個(gè)整型數(shù)據(jù)是 10,我們通過(guò)賦值運(yùn)算符 = 將這個(gè)整型數(shù)保存到內(nèi)存中以 foo 地址開始的連續(xù) 4 個(gè)字節(jié)中。從此刻開始,要記住一個(gè)事實(shí),那就是 C 語(yǔ)言中所有的變量名,本質(zhì)上都是內(nèi)存地址。之所以不直接使用內(nèi)存地址,而是使用一些有意義的名字,這就類似于沒(méi)人愿意用你的身份證號(hào)來(lái)稱呼你,大家更愿意用你的姓名來(lái)稱呼你。由于 C 語(yǔ)言認(rèn)為數(shù)據(jù)的長(zhǎng)度是由其類型確定的。例如,int 類
7、型的數(shù)據(jù)長(zhǎng)度是 4 個(gè)字節(jié),char 類型的數(shù)據(jù)長(zhǎng)度是是 1 個(gè)字節(jié),用戶自定義的 struct 類型的數(shù)據(jù)長(zhǎng)度則是根據(jù)實(shí)際情況而待定。在這種情況下,所有表示內(nèi)存地址的名字,它們實(shí)質(zhì)上表示的是內(nèi)存中各種類型數(shù)據(jù)存儲(chǔ)空間的起始地址專業(yè)一點(diǎn),就是基地址。凡是用名字來(lái)表示基地址的內(nèi)存空間,我們就將其稱為有名的內(nèi)存空間。再來(lái)看 bar 是什么?bar 是內(nèi)存地址的名字,由于 bar 前面有個(gè) * 號(hào),這表示我們打算在以 bar 為基地址的連續(xù) 8 個(gè)字節(jié)中存儲(chǔ)一個(gè)內(nèi)存地址(別忘了,我們是在 64 位機(jī)器
8、上,指針數(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ì)某塊以
9、160;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)存空
10、間呢?看下面的代碼: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)描述。比如我在寫文章,這種行為無(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)存所開辟 4 個(gè)字節(jié)的空間的基地址,這個(gè)基地址是沒(méi)有名
11、字的,所以它就是個(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ǔ)空間稱為對(duì)象(Object)并非面向?qū)ο缶幊讨械膶?duì)象,然后將指代這個(gè)對(duì)象的表達(dá)式稱為左值(lvalue)。也就是說(shuō),在 C 語(yǔ)言中,上例中的 foo 與 bar 都是左值,因?yàn)樗鼈兛偸悄軌虺霈F(xiàn)在賦值符號(hào)的左側(cè)。看下面的代碼:int foo = 10;in
12、t *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 的類型是 int,
13、所以程序自身就可以知道要訪問(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)存中(??臻g)隨便逛,隨便破壞,然后你的程序可能就崩潰了。你的程序如果隱含緩沖區(qū)溢出漏洞,它甚至可被其他程序控制著去執(zhí)行一些對(duì)你的系統(tǒng)非常不利的代碼,這就是所謂的緩沖區(qū)溢出攻擊。C 語(yǔ)言不提供任何緩沖區(qū)保護(hù)機(jī)制,能否有效保護(hù)緩沖區(qū),主要
14、取決于你的 C 編程技藝?,F(xiàn)在我們寫 C 程序時(shí),基本上不需要擔(dān)心自己的程序會(huì)遭遇緩沖區(qū)溢出攻擊。因?yàn)橹挥心切┍粡V泛使用的 C 程序才有這種風(fēng)險(xiǎn);如果很不幸,你寫的 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í)行代碼所在
15、的內(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ǔ)空間是棧空間。也就是說(shuō),在一個(gè)函數(shù)中,所有的局部變量所占據(jù)的存儲(chǔ)空間屬于??臻g。可能再說(shuō)的學(xué)術(shù)一點(diǎn),就是所有的左值都在??臻g(我不確定這樣說(shuō)到底是不是正確)。當(dāng)一個(gè)函數(shù)運(yùn)
16、行結(jié)束,它所占據(jù)的棧空間就不再屬于它了,而是將會(huì)被一個(gè)新的待運(yùn)行的函數(shù)占據(jù)。所以,從本質(zhì)上說(shuō),C 程序?qū)?臻g的回收都不屑一顧,因?yàn)樗静换厥?,而是舊的數(shù)據(jù)會(huì)被新的數(shù)據(jù)覆蓋。堆空間,我們?cè)诔绦蚶餆o(wú)法直接訪問(wèn),只能借助指針。因?yàn)槎芽臻g的內(nèi)存地址可被指針引用。例如,當(dāng)使用 malloc 分配空間時(shí),所分配空間的基地址總是保存在一個(gè)位于??臻g的指針中的。??臻g通常遠(yuǎn)遠(yuǎn)小于堆空間,即便如此也幾乎不會(huì)出現(xiàn)某個(gè)函數(shù)會(huì)耗盡??臻g的現(xiàn)象。如果這種現(xiàn)象出現(xiàn)了,那只能證明造出這種現(xiàn)象的程序猿應(yīng)該繼續(xù)學(xué)習(xí) C 語(yǔ)言了。??臻g被耗盡,往往是因?yàn)橛行┏绦虮緛?lái)是寫成遞歸,但可能是代碼寫錯(cuò)了,導(dǎo)致遞而
17、不歸;還有一種可能是遞歸層次太深,這時(shí)可以想辦法在堆空間中模擬一個(gè)棧來(lái)解決。還有一種情況就是在函數(shù)中定義了很大的數(shù)組,導(dǎo)致??臻g放不下這種情況總是可以靠分配堆空間來(lái)解決。數(shù)據(jù)的抽象當(dāng)你具備了一些 C 編程基礎(chǔ),并且能夠理解上文中的內(nèi)容,那么你就可以對(duì)各種類型的數(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ì)自行車鏈的任意一個(gè)鏈節(jié)進(jìn)行模擬:struct chai
18、n_node struct chain_node *prev; struct chain_node *next; void *shape;然后我們可以造出 3 個(gè)鏈節(jié),然后可以造出世界上最短的車鏈:struct chain_node a, b, c; a.next
19、= &b;b.prev = &a; b.next = &c;c.prev = &b; c.next = &a;a.prev = &c;如果再多造一些鏈節(jié),就可以得到周長(zhǎng)大一些的車鏈,也能夠制造出各種形狀的多邊形,但是最好是借助無(wú)名的內(nèi)存空間。下面的代碼可以創(chuàng)建一條具有 1000 個(gè)鏈節(jié)的鏈條:struct chain_node *head = malloc(sizeof(struct chain_node);struct chain_node *tail = head;for
20、(int i = 0; i < 1000; i+) struct chain_node *new_tail = malloc(sizeof(struct chain_node); tail->next = new_tail; new_tail->prev =
21、 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)
22、,而我們所仰仗的東西僅僅是將三個(gè)指針?lè)庋b到一個(gè)結(jié)構(gòu)體中。事實(shí)上,struct chain_node 中的第三個(gè)指針 void *shape 還沒(méi)被用到。這是一個(gè) void * 類型的指針,是喜歡用 C 代碼玩各種抽象的程序猿的最愛,因?yàn)樗芤萌魏晤愋蛿?shù)據(jù)所在內(nèi)存空間的基地址。這就意味著 struct chain_node 可以借助 shape 指針獲得強(qiáng)大的擴(kuò)展能力?,F(xiàn)在,我要制造一種很簡(jiǎn)陋的鏈節(jié),它的形狀僅僅是一個(gè)矩形的小鐵片,上面打了兩個(gè)小圓孔。我將它的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)為:struct poi
23、nt double x; double y; struct rectangle double width; double height; struct
24、 circle struct point *center; double radius; struct chain_node_shape struct rectangle *body;
25、; struct circle *holes2 ;基于這些數(shù)據(jù)結(jié)構(gòu),我就可以寫出一個(gè)專門用來(lái)制造矩形小鐵片的函數(shù):struct chain_node_shape *create_chain_node_shape(struct circle *c1, struct
26、 circle *c2, struct rectangle *rect) struct chain_node_shape *ret = malloc(sizeof(struct
27、160;chain_node_shape); ret->body = rect; ret->holes0 = c1; ret->holes1 = c2; return ret;然后再為
28、create_chain_node_shape 所接受的兩種參數(shù)寫出相應(yīng)的構(gòu)造函數(shù):struct circle *create_circle(struct point *center, double radius) struct circle *ret = malloc(sizeof(struct circle); ret->
29、;center = center; ret->radius = radius; return ret; struct rectangle *create_rectangle(double w, double h) struct rectang
30、le *ret = malloc(sizeof(struct rectangle); ret->width = w; ret->height = h; return ret;為了讓 create_circle 更方便使用,最好再創(chuàng)建一個(gè) stru
31、ct point 的構(gòu)造函數(shù):struct point *create_point(double x, double y) struct point *ret = malloc(sizeof(struct point); ret->x = x;
32、; ret->y = y; return ret;一切所需要的構(gòu)件都已準(zhǔn)備完畢,現(xiàn)在可以開始生產(chǎn)某種特定型號(hào)的鏈節(jié)了,即:struct chain_node *create_chain_node(void) double radius = 0.5; double
33、;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_cent
34、er, radius); double right_x = 9.0; double right_y = 1.0; struct point *right_center = create_point(right_x, right_y);
35、60; struct circle *right_hole = create_circle(right_center, radius); struct rectangle *body = create_rectangle(10.0, 2.0); struct chain_node *ret = malloc(si
36、zeof(struct chain_node); ret->prev = NULL; ret->next = NULL; ret->shape = create_chain_node_shape(left_hole, right_hole, body);
37、 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();
38、; tail->next = new_tail; new_tail->prev = tail; tail = new_tail;tail->next = head;head->prev = tail;現(xiàn)在我們所模擬的車鏈與現(xiàn)實(shí)中的車鏈已經(jīng)有些形似了。上述代碼雖然有些冗長(zhǎng),下文會(huì)對(duì)其進(jìn)行重構(gòu),現(xiàn)在先來(lái)總結(jié)一
39、下上述代碼中指針的用法。仔細(xì)觀察上述代碼中我們所定義的結(jié)構(gòu)體,它們的共同特征是:所有非 C 內(nèi)建的數(shù)據(jù)類型都是結(jié)構(gòu)體類型,當(dāng)它們作為某個(gè)結(jié)構(gòu)體成員類型時(shí)均被聲明為指針類型。為什么要這樣?如果你真的打算問(wèn)這個(gè)問(wèn)題,那么就請(qǐng)你觀察一下上述的 5 個(gè)create_xxx 函數(shù),你會(huì)發(fā)現(xiàn)這些 create 函數(shù)的參數(shù)與返回值也都是結(jié)構(gòu)體類型的指針。將這些現(xiàn)象綜合起來(lái),可以得出以下結(jié)論:1. 將結(jié)構(gòu)體指針作為函數(shù)的參數(shù)與返回值,可以避免函數(shù)調(diào)用時(shí)發(fā)生過(guò)多的內(nèi)存復(fù)制。2. 當(dāng)一個(gè)結(jié)構(gòu)體類型作為其他結(jié)構(gòu)體的成員類型時(shí),將前者聲明為指針類型,可以在后者的 create&
40、#160;函數(shù)中避免繁瑣的解引用。3. void * 指針可以引用任意類型的數(shù)據(jù)存儲(chǔ)空間的基地址。例如在 create_chain_node 函數(shù)的定義中,我們將一個(gè)struct chain_node_shape 類型的指針賦給了 void * 類型的指針 shape。這三條結(jié)論是指針在數(shù)據(jù)抽象中的慣用手法,它不僅關(guān)系到數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì),也關(guān)系到數(shù)據(jù)結(jié)構(gòu)的構(gòu)造與銷毀函數(shù)的設(shè)計(jì)。(上述代碼為了省事,沒(méi)有定義數(shù)據(jù)結(jié)構(gòu)的銷毀函數(shù))數(shù)據(jù)再抽象上一節(jié)的代碼有些冗長(zhǎng),我們可以嘗試對(duì)其進(jìn)行精簡(jiǎn)。首先看下面這三個(gè)結(jié)構(gòu)體及其 crea
41、te 函數(shù):struct point double x; double y; struct rectangle double width; doubl
42、e height; struct circle struct point *center; double radius; struct chain_node_shape struct rectangle *body;
43、60; struct circle *holes2 ; struct point *create_point(double x, double y) struct point *ret = malloc(sizeof(struct point); re
44、t->x = x; ret->y = y; return ret; struct circle *create_circle(struct point *center, double radius) struct circle *ret
45、 = malloc(sizeof(struct circle); ret->center = center; ret->radius = radius; return ret; struct rectangle *create_rectangle(doub
46、le w, double h) struct rectangle *ret = malloc(sizeof(struct rectangle); ret->width = w; ret->height = h;
47、 return ret; struct chain_node_shape *create_chain_node_shape(struct circle *c1, struct circle *c2,
48、0; struct rectangle *rect) struct chain_node_shape *ret = malloc(sizeof(struct chain_node_shape)
49、; ret->body = rect; ret->holes0 = c1; ret->holes1 = c2; return ret;顯然,這些代碼長(zhǎng)的太像了!那四個(gè)結(jié)構(gòu)體都是存儲(chǔ)兩個(gè)成員的結(jié)構(gòu)體
50、,而相應(yīng)的 create 函數(shù)也無(wú)非是將函數(shù)所接受的參數(shù)保存到結(jié)構(gòu)體成員中。有沒(méi)有辦法用很少的代碼來(lái)表示它們?有!既然每個(gè)結(jié)構(gòu)體都保存 2 個(gè)成員,那么我們就先將上述代碼刪掉,然后定義一個(gè) pair 類型的結(jié)構(gòu)體:struct pair void *first; void *second;在 pair 結(jié)構(gòu)體中,我們用
51、了兩個(gè) void * 指針,只有如此我們方能很自信的說(shuō) pair 可以存儲(chǔ)任意類型的兩個(gè)數(shù)據(jù)。接下來(lái),只需修改 create_chain_node 函數(shù)的定義:struct chain_node *create_chain_node(void) double *left_x = malloc(sizeof(double);
52、0;double *left_y = malloc(sizeof(double); *left_x = 1.0; *left_y = 1.0; struct pair *left_center = malloc(sizeof(struct pair);
53、160; left_center->first = left_x; left_center->second = left_y; double *left_radius = malloc(sizeof(double);
54、;*left_radius = 0.5; struct pair *left_hole = malloc(sizeof(struct pair); left_hole->first = left_center; left_hole->second = left_radius;
55、 double *right_x = malloc(sizeof(double); double *right_y = malloc(sizeof(double); *right_x = 9.0;
56、; *right_y = 1.0; struct pair *right_center = malloc(sizeof(struct pair); right_center->first = right_x; right_center->second =
57、 right_y; double *right_radius = malloc(sizeof(double); *right_radius = 0.5; struct pair *right_hole = malloc(sizeof(struct pair);
58、0; right_hole->first = right_center; right_hole->second = right_radius; struct pair *holes = malloc(sizeof(struct pair);
59、 holes->first = left_hole; holes->second = right_hole; struct pair *body = malloc(sizeof(struct pair);
60、double *width = malloc(sizeof(double); *width = 10.0; double *height = malloc(sizeof(double); *height = 2.0; &
61、#160; body->first = width; body->second = height; struct pair *shape = malloc(sizeof(struct pair); shape->first = body
62、; shape->second = holes; struct chain_node *ret = malloc(sizeof(struct chain_node); ret->prev = NULL; &
63、#160; 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ì)觀察上述代碼,顯然下面的三段代碼
64、存在著高度的重復(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 =
65、;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
66、0;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 * 類型的數(shù)據(jù)。既然如此,我們可以專門寫一個(gè)函數(shù),讓它生成面向double * 的 pair 結(jié)構(gòu)體,即:s
67、truct pair *pair_for_double_type(double x, double y) struct pair *ret = malloc(sizeof(struct pair); double *first = malloc(sizeof(double);
68、; double *second = malloc(sizeof(double); *first = x; *second = y; ret->first = first; &
69、#160; 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);
70、 double *left_radius = malloc(sizeof(double); *left_radius = 0.5; struct pair *left_hole = malloc(sizeof(struct pair);
71、0; left_hole->first = left_center; left_hole->second = left_radius; struct pair *right_center = pair_for_double_type(9.0, 1.0);
72、60; double *right_radius = malloc(sizeof(double); *right_radius = 0.5; struct pair *right_hole = malloc(sizeof(struct pair); righ
73、t_hole->first = right_center; right_hole->second = right_radius; struct pair *holes = malloc(sizeof(struct pair); holes->first =
74、left_hole; holes->second = right_hole; struct pair *body = pair_for_double_type(10.0, 1.0); struct pair *shape = malloc(sizeof(st
75、ruct pair); shape->first = body; shape->second = holes; struct chain_node *ret = malloc(sizeof(struct chain_node); &
76、#160; ret->prev = NULL; ret->next = NULL; ret->shape = shape; return ret;山重水復(fù)疑無(wú)路經(jīng)過(guò)再次重構(gòu)后的 create_chain_node
77、;看上去要好了一些,但是依然有兩段代碼存在高度重復(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;
78、160;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
79、160;結(jié)果體層面已經(jīng)無(wú)法對(duì)這兩段代碼進(jìn)行簡(jiǎn)化了,而且我又非常不想寫一個(gè)像下面這樣的輔助函數(shù):struct pair *create_hole(struct pair *center, double radius) struct pair *ret = malloc(sizeof(struct pair); double *r =
80、 malloc(sizeof(double); *r = radius; ret->first = center; ret->second = r; return ret;雖然 cr
81、eate_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_
82、for_double_type 函數(shù)相比,create_hole 這個(gè)函數(shù)的應(yīng)用范圍非常狹小。由于 pair_for_double_type函數(shù)可以將兩個(gè) double 類型的數(shù)據(jù)存儲(chǔ)到 pair 結(jié)構(gòu)體中,在我們的例子中創(chuàng)建二維點(diǎn)與矩形可以用到它,在科學(xué)計(jì)算中創(chuàng)建極坐標(biāo)、復(fù)數(shù)以及所有的二次曲線方程式也都都能用到它,但是 create_hole 卻只能在創(chuàng)建車鏈這件事上有點(diǎn)用處。也就是說(shuō),正是因?yàn)?#160;pair_for_double_type 函數(shù)所取得的成功,導(dǎo)致我們認(rèn)為 cr
83、eate_hole 的品味太低。我們應(yīng)該想一想還有沒(méi)有其他途徑可以消除上述代碼的重復(fù)。仔細(xì)分析 left_hole 與 right_hole 的構(gòu)造過(guò)程,不難發(fā)現(xiàn) hole 的 center 與 radius 這兩種數(shù)據(jù)的類型不一致是造成我們難以對(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)體姑且稱之為 X。X 結(jié)構(gòu)體的特殊指出在于其
84、first 指針存儲(chǔ)的是一個(gè)面向 double * 的同構(gòu)類型的 pair 結(jié)構(gòu)體,其 second 指針則存儲(chǔ)了一個(gè) double 類型數(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 類型數(shù)據(jù)的 pair 的函數(shù) pair_for_double_type,這
85、個(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;
86、0; 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);
87、 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);
88、; 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);
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 手工泥巴制作課程設(shè)計(jì)
- 室外景觀設(shè)計(jì)師的植物配置與環(huán)境打造
- 保健品行業(yè)話務(wù)員工作總結(jié)
- 2025年中考物理一輪復(fù)習(xí)之物態(tài)變化
- 超市行業(yè)客服工作總結(jié)周到服務(wù)增添購(gòu)物樂(lè)趣
- 化妝護(hù)膚行業(yè)銷售工作總結(jié)
- 餐飲服務(wù)員工作總結(jié)熱情招待細(xì)心服務(wù)顧客
- 【八年級(jí)下冊(cè)地理湘教版】專項(xiàng)02 港、澳、臺(tái)的經(jīng)濟(jì)發(fā)展
- 2024年熱鬧的元宵節(jié)教案
- 2024年石家莊理工職業(yè)學(xué)院?jiǎn)握新殬I(yè)技能測(cè)試題庫(kù)標(biāo)準(zhǔn)卷
- 美容院2024年度規(guī)劃
- 裝飾裝修巡查記錄表
- 2024高考物理一輪復(fù)習(xí):觀察電容器的充、放電現(xiàn)象(練習(xí))(學(xué)生版+解析)
- 公司安全生產(chǎn)事故隱患內(nèi)部報(bào)告獎(jiǎng)勵(lì)工作制度
- 2024年度內(nèi)蒙古自治區(qū)國(guó)家電網(wǎng)招聘之電工類綜合練習(xí)試卷A卷附答案
- 艾滋病預(yù)防知識(shí)講座
- 零售服務(wù)質(zhì)量提升
- 《4 平平安安回家來(lái)》 說(shuō)課稿-2024-2025學(xué)年道德與法治一年級(jí)上冊(cè)統(tǒng)編版
- 2024中考英語(yǔ)真題分類匯編-代詞
- 第九版內(nèi)科學(xué)配套課件-8-骨髓增生異常綜合征(MDS)
- 新聞宣傳報(bào)道先進(jìn)單位(集體)申報(bào)材料
評(píng)論
0/150
提交評(píng)論