技(jì)術交流
周立功教授(shòu)數年之心血之(zhī)作《程序設計與(yǔ)數據結🌈構》,電子(zi)版已無償性分(fèn)享到電子工程(chéng)師與高校群體(tǐ)。書本内🔞容公開(kai)後🆚,在電✂️子行業(ye)掀起一片學習(xí)熱✂️潮。經周立功(gōng)教授授權,特對(duì)本👉書内容進行(háng)連載,願共勉之(zhī)。
第一章爲程序(xu)設計基礎,本文(wen)爲1.5.2/1.5.3共性與可變(bian)性分析:建😘立抽(chou)🤩象和建立接口(kǒu)。
>>>> 1.5.2 建立抽象
抽象(xiang)化的目的是使(shǐ)調用者無需知(zhi)道模塊的内部(bu)細節,隻需要知(zhī)道模塊或函數(shù)的名字,因此将(jiāng)其稱🙇🏻爲黑盒化(huà)。調用者隻需要(yao)知道黑盒子的(de)輸入和輸出,而(er)過程的細節是(shì)隐🔱藏的。由于建(jian)立了一個由黑(hēi)盒子組成的系(xì)統,因此複雜的(de)結構就被🔞黑盒(hé)子隐👣藏起來了(le),則理解系統的(de)整體結構就變(biàn)得更容易✍️了。
從(cóng)概念的視角來(lai)看,建立抽象關(guān)注的不是如何(hé)實現,而是函數(shu)要做什麽,過早(zǎo)地關注實現細(xì)節,将實💋現細節(jie)隐藏起來,進而(er)幫助我們構建(jian)更易于修改的(de)軟件。因此,我們(men)首先應該選擇(ze)一個具有描述(shu)性的符合需求(qiú)的名字,雖然可(kě)以選擇的名字(zì)有swapByte、swapWord和swap,但swap更簡潔(jié)更貼切。其次,可(ke)以用一句話概(gai)✔️念性地描述😘swap的(de)數據抽象——swap是實(shi)現兩個數據交(jiāo)換的函數。
顯然(ran),調用者僅需一(yī)般性地在概念(nian)層次上與實現(xian)者交流🙇🏻,因爲調(diào)用者的意圖是(shi)如何使用swap()實現(xiàn)兩個數據的交(jiao)🐉換,所以無🔴需準(zhǔn)确地知道實現(xiàn)的細節。而具體(tǐ)如何完成數據(jù)的👄交換,這是在(zài)實現層次進行(háng)的。由此可見,将(jiāng)模塊的目的與(yǔ)實現分離的抽(chou)象揭♍示了問題(ti)的本質,并沒有(yǒu)提供解決方👌案(an)。隻說明需🥵要做(zuò)什麽,并不🚶♀️會指(zhi)出如何實現某(mǒu)個模塊。隻要概(gai)念不變,調用者(zhe)與實現細節的(de)變化🚶就徹底隔(gé)離了🐕。當某個模(mó)塊完成編碼後(hòu),隻要說明該模(mó)☀️塊的目的和參(can)數就可以使用(yong)它,無需知道具(jù)體的實現。
函數(shù)抽象對團隊項(xiàng)目非常重要,因(yīn)爲在團隊中必(bi)須使用其⭐他成(chéng)員編寫的模塊(kuai)。比如,編程語言(yán)本身👅自帶㊙️的庫(kù)函數,由于已經(jing)🤩被預編譯,因此(cǐ)無法訪問它的(de)源代碼。同時庫(kù)函數不一定是(shi)用C編寫的,因此(ci)隻要知道其調(diào)用規範,就可以(yǐ)在程序中毫無(wú)顧忌地使用這(zhè)個函數。實際上(shàng),在使用scanf()函數的(de)過程中,我們考(kao)慮過scanf()是☎️如何實(shi)現的嗎?無關緊(jǐn)要。盡管不同系(xi)統實現scanf()的方法(fǎ)可能不一樣,但(dan)其中的不同對(dui)于程序☁️員來說(shuo)是透明的。
>>>> 1.5.3 建立(lì)接口
接口是由(you)公開訪問的方(fang)法和數據組成(cheng)的,接口描述了(le)與模塊交互的(de)唯一途徑。最小(xiǎo)化的接口隻包(bāo)含對于接💚口的(de)任務🚩非常重要(yao)的參數,最小化(huà)的接口便于學(xue)習如何與之交(jiāo)互,且隻需要理(li)解少量的參數(shu),同時易于擴展(zhan)和維護,因✔️此設(she)計良好的接口(kou)是一項重要的(de)技能。
>>> 1. 函數調用(yong)
(1)傳值調用
如何(he)調用swap()函數呢?實(shí)參将值從主調(diao)函數傳遞給被(bei)調函數,也許其(qí)調用形式是下(xia)面這樣的:
swap(a, b);
從黑(hei)盒視角來看,形(xíng)參和其它局部(bu)變量都是函數(shu)私有的,聲💃明🧑🏾🤝🧑🏼在(zai)不同函數中的(de)同名變量是完(wán)全不同的變量(liàng),而且函數無法(fǎ)直接訪問其它(ta)函數中的變量(liang),這種限制訪問(wen)保護了數據的(de)完整性,黑盒發(fa)💃🏻生了什麽💜對主(zhu)調函數是不可(kě)💞見的。
一個變量(liang)的有效範圍稱(chēng)作它的作用域(yu),變量的作用域(yu)指可以通過變(bian)量名稱引用變(bian)量的區域,在函(hán)數内部聲🔴明的(de)變量隻在該函(hán)數内部有效。當(dang)主調函數調用(yòng)子函數時,主函(han)數内聲明的變(bian)量在子函數内(nei)無效,子函數内(nei)聲明的變量也(ye)隻在該子函數(shu)♍内部有效。
由于(yú)傳遞給函數的(de)是變量的替身(shēn),因此改變函數(shù)參數對原始變(bian)量沒有影響。當(dāng)變量傳遞給函(hán)數時,變量的值(zhí)被複制給函數(shù)參數。由此可見(jian),通過“傳值調用(yòng)”方㊙️式交換a、b的值(zhi),無法改變主調(diao)函🌏數相應變量(liang)的值。
(2)傳址調用(yòng)
如果希望通過(guo)被調函數将更(gèng)多的值傳回主(zhǔ)調函數而改變(biàn)主調函數中的(de)變量,則使用“傳(chuan)址調用”——将&a、&b作爲(wei)實參傳遞給形(xing)參。其調用形式(shì)如下:
swap(&a, &b);
利用指針(zhen)作爲函數參數(shù)傳遞數據的本(běn)質,就是在主調(diao)函數和被調函(hán)數中,通過不同(tóng)的指針指向同(tong)一内存地址訪(fang)問相同的内存(cún)區域,即它們背(bei)後共享相同♊的(de)内存,從而實現(xian)數據的傳遞和(hé)交換。
>>> 2. 函數原型(xíng)
函數原型是C語(yu)言的一個強有(you)力的工具,它讓(rang)編譯器🈲捕☂️獲在(zai)使用函數時可(ke)能出現的許多(duō)錯誤或疏漏。如(rú)果編譯器沒有(yǒu)發現這些問題(ti),就很難察覺出(chu)來。函數原型包(bao)括函數返回值(zhi)的類型、函數名(ming)和形參列表(參(can)🔞數的數量和每(měi)個參數的類型(xíng)),有了這些⁉️信息(xī),編譯器就可以(yǐ)檢查函數調用(yong)與函數原型是(shi)否匹配?比如,參(can)數的數量是否(fǒu)正确?參數的類(lèi)型是否匹配?如(ru)🐅果類型不匹配(pèi),編譯器會将實(shí)參的類型轉換(huan)成形參的類型(xing)。
(1)函數形參
通過(guo)程序清單 1.15可以(yi)看出,其相同的(de)處理部分是2個(gè)int類值的交換代(dai)碼,因此可以将(jiang)數據交換代碼(mǎ)移到swap()函數♊的實(shí)現中,其可變的(de)數據由外部傳(chuán)進來的參數應(ying)對。由于&a是✊指向(xiàng)int類📐型變量a的指(zhi)針,&b是指向int類型(xing)變量b的指💃針,因(yīn)此必須将p1、p2形參(can)🔞聲明爲指向int *類(lei)型的指針變量(liang),即必須将存儲(chu)int類型值變量的(de)地址作爲實參(can)賦給指針形參(can),實參與形參才(cái)能匹配。其函數(shù)原型進化如下(xia):
swap(int *p1, int *p2);
(2)返回值的類型(xing)
聲明函數時必(bì)須聲明函數的(de)類型,帶返回值(zhi)的函數類型應(ying)該與其返回值(zhi)類型相同,而沒(mei)有返回值的函(han)數應該聲明爲(wei)void。類型聲明是函(han)數定義的一部(bu)分,函數🔱類型指(zhǐ)的是返回值的(de)類型,不是函數(shù)參數的類型。
雖(sui)然可以使用return返(fan)回值,但return隻能返(fǎn)回一個值給主(zhu)調函數。比如,如(ru)果返回值爲整(zheng)數,則函數返回(huí)值的類型爲int。當(dang)返回值爲int類型(xing)時😄,如果返回值(zhí)爲負數,則表示(shi)失敗☔;如果返回(huí)值爲☀️非負數,則(ze)表示成功。當返(fan)回值爲bool類型時(shi),如果返回值爲(wèi)false,則表示失敗,如(ru)果返回值爲true,則(zé)表示成功。當返(fan)回值爲指針類(lèi)型時,如果返📱回(hui)值爲NULL,則表示失(shi)敗,否則返回一(yi)個有效的指⛱️針(zhen)。
如果利用指針(zhen)作爲參數傳遞(dì)給函數,不僅可(ke)以向函數傳👈入(rù)數據,而且還可(ke)以從函數返回(hui)多個值。因爲函(hán)數的調用者和(he)函數都可以使(shǐ)用指向同一内(nèi)存地址♻️的指針(zhēn),即使用同一塊(kuài)内存,所以使用(yong)指針作爲函數(shu)參數時就是對(duì)同一數據進行(hang)⛱️讀寫操作。這樣(yàng)不僅可♌以傳入(rù)數據,還可以通(tong)過在函數内部(bu)🏃♀️修改這些數據(ju)🌈,将函數的結果(guo)傳出給調用者(zhě)。
當函數的實參(can)是指針變量時(shí),有時希望函數(shu)能通過指針指(zhi)向别處的方式(shi)改變此變量,則(zé)需要使用指向(xiang)指針的指針作(zuò)爲形參。
由于swap()無(wu)返回值,因此swap()返(fan)回值的類型爲(wèi)void,其函數原型如(ru)下:
void swap(int *p1, int *p2);
其被解釋爲(wei)swap是返回void的函數(shu)(參數是int *p1,int *p2)。
這是一(yī)個不斷叠代優(you)化的過程,用戶(hù)隻需要知道“函(han)數㊙️名、傳入🏒函數(shu)的參數和函數(shù)返回值的類型(xing)”,就知道如何有(you)效地調🙇♀️用相應(ying)的函數。
>>> 3. 依賴倒(dao)置原則
在面向(xiang)過程編程中,通(tong)常的做法是高(gao)層模塊調用低(di)層模塊,其目的(de)之一就是要定(dìng)義子程序層次(ci)結構。當高層模(mo)塊依賴于低層(céng)模塊時,對低層(ceng)模塊的改動會(huì)👣直接影響高層(céng)模塊,從而迫使(shǐ)它們依次做💚出(chū)修改。如果高層(ceng)模塊獨立于低(dī)層模塊,則高層(céng)模塊更容易重(zhòng)用,這就是分層(céng)架構設計❤️的核(hé)心原則,即依賴(lài)倒置原則(Dependence Inversion Principle,DIP):
● 高層(céng)模塊不應該依(yi)賴低層模塊,兩(liang)者都應該依賴(lài)于抽象接☀️口;
● 抽(chōu)象接口不應該(gai)依賴于細節,細(xì)節應該依賴抽(chōu)象接口。
當在分(fen)層架構中使用(yòng)依賴倒置原則(ze)時,将會發現“不(bu)再存❓在分🐉層”的(de)概念了。無論是(shi)高層還是低層(ceng),它們都🐅依賴于(yu)抽象接口,好像(xiàng)将整個分層架(jia)構推平一樣。
其(qi)實從“Hello World”程序開始(shǐ),我們就已經在(zài)使用stdio.h包含的“抽(chōu)象接口”了,即以(yi)後凡是用#include文件(jian)的擴展名叫.h(頭(tou)文件)。如果源代(dai)碼中要用到stdio标(biao)準輸入輸出函(hán)數時,那麽就要(yao)包含這個頭文(wén)件,比✂️如,“scanf("%d",&i);”函數,其(qí)目的是告訴編(bian)譯器要使用stdio庫(kù)。庫是一❗種工具(ju)的集合,這些工(gōng)具是由其它程(chéng)序員編寫的,用(yòng)于實現特定的(de)功能。盡管實現(xian)者無需關心用(yong)戶将如何使用(yong)庫,且不會直接(jie)開放源代碼給(gěi)用戶使用,但必(bi)須給用戶提供(gòng)調用函數所需(xu)要的信息。顯😄然(ran)隻要将頭文件(jiàn)開放給用戶,即(jí)可讓用戶了解(jie)接口㊙️的所有細(xì)節,詳見程序清(qing)單 1.16。
程序清單 1.16 swap數(shù)據交換接口(swap.h)
1 #ifndef _SWAP_H
2 #define _SWAP_H
3 // 前(qian)置條件:實參必(bi)須是int類型變量(liàng)的地址
4 // 後置條(tiao)件:p1、p2作爲輸出參(cān)數,改變主調函(han)數中相應的變(biàn)量
5 void swap(int *p1, int *p2);
6 // 調用形式:swap(&a, &b)
7 #endif
其(qí)中,每個頭文件(jiàn)都指出了一個(gè)用戶可見的外(wai)部函數接口,主(zhu)💚要包括函數名(míng)、所需的參數、參(can)數的類型和返(fǎn)回結果的類型(xíng)。其中,swap是庫的名(míng)字,程序清單 1.16(1~2)與(yu)(8)是幫助編譯❄️器(qi)記錄它所讀取(qǔ)的接口,當寫一(yi)個接口時,必須(xu)包含#ifndef、#define和#ednif。#include行部分(fèn)僅當接口本身(shen)需要其它庫時(shí)才使用,它由标(biāo)準的#include行組成。程(cheng)🔅序清單 1.16(6)接口🐅項(xiàng)表示庫輸出的(de)函數的原型🆚、常(chang)量和類型等。不(bu)管你是否理解(jiě),這些行是接口(kou)的模闆文件,這(zhe)就是信息隐藏(cang)。
>>> 4. 前/後置條件
處(chù)理信息隐藏還(hai)涉及到另一個(ge)技術,那就是使(shǐ)用前置條件和(he)後置條件描述(shu)函數的行爲。在(zài)編寫一個完整(zhěng)的🏃♀️函數定義時(shí),需要描述該函(hán)數是如何執行(hang)計算的。但在👣使(shǐ)用函數時,隻需(xū)考慮該函數能(néng)做💞什麽,無需知(zhi)道是如何完成(chéng)的。當不知道函(han)數是如🌈何實現(xian)時,就是在使用(yòng)一✂️種名爲過程(chéng)抽象的信息隐(yǐn)藏形式,它抽象(xiàng)掉的是函數如(ru)何工作的細節(jie)。計算機科學家(jiā)使用“過程”表示(shi)任意指令集,因(yīn)此使用術語過(guo)程抽象。過程抽(chōu)象是一種強大(da)的工具,使得我(wo)們一次隻考慮(lǜ)一個而不是所(suo)有的函數,從而(er)使問題求解簡(jian)單化。
爲了使描(miáo)述更準确,則需(xū)要遵循固定的(de)格式,它包含兩(liang)🔞部分信息:函數(shù)的前置條件和(he)後置條件。前置(zhì)條件👉就是📱調用(yong)該函🌂數必須成(cheng)立的條件,當函(han)數被調用時,該(gāi)語句給出要求(qiú)爲真的條件。除(chu)非前置條件爲(wèi)真,否則無法保(bǎo)證函數能☎️正确(que)執行。在調用swap()函(hán)數時,實參必須(xū)是int類型變量的(de)地址,這是調用(yòng)者的職責。通常(chang)在函㊙️數開始處(chù)檢查是否滿足(zú)?如果不滿足,說(shuo)明調用代碼有(you)問題㊙️,抛出一個(ge)異常。
後置條件(jian)就是該操作完(wán)成後必須成立(li)的條件,當函數(shu)調用時,如果函(han)數是正确的,而(er)且前置條件爲(wei)真,那👄麽該函數(shù)調用♈将可以執(zhí)行完成。當函數(shù)調用㊙️完成後,後(hòu)置條件爲真。如(ru)果不滿足後🏃🏻♂️置(zhi)條件,則說💁明業(ye)務邏輯有問題(tí)。
當滿足調用swap()函(han)數的前置條件(jiàn)時,必須同時确(què)保其結☔束時滿(mǎn)足它的後置條(tiáo)件,其後置條件(jiàn)是被調函數将(jiāng)返回🛀🏻值傳回主(zhu)調函🛀數,改變主(zhǔ)調函數中變量(liang)🏃♂️的值。
前後置條(tiao)件不隻是概括(kuò)地描述函數的(de)行爲,聲明這⚽些(xie)條件應該是設(she)計任何函數的(de)第一步。在開始(shi)考慮🥵某個函數(shu)的算🔴法和代碼(ma)之前,應該寫出(chu)該函數的原型(xíng),其中🌈包括函數(shù)的返😍回類型、名(ming)稱和參數👈列表(biao),最後緊跟一個(ge)🔆分号。直接來自(zi)于用戶的輸入(rù)✂️不能作爲前置(zhì)條件,通常🐪前/後(hòu)置條件都可以(yi)轉化爲assert語句。編(biān)寫函數原型時(shi),應該以注釋的(de)形式描述該函(hán)數的前置條件(jiàn)和後置條件。
事(shì)實上,前置條件(jian)和後置條件在(zài)使用函數的程(cheng)序員和編寫函(han)數的程序員之(zhī)間形成了一個(gè)契約,也就是爲(wèi)什麽需要這個(gè)函數?接口通過(guo)前置條件和後(hou)置條件以契約(yue)的形式表達需(xu)求,承諾在滿足(zú)前置條件時開(kāi)始,按照程序的(de)流🌈程運行,系統(tong)就能到達後置(zhì)條件。
雖然注釋(shì)是一種很好的(de)溝通形式,但在(zài)代碼可以傳🏃♀️遞(dì)意圖的地方不(bu)要寫注釋。因爲(wei)代碼解釋做了(le)什麽,再注⁉️釋也(ye)沒有什麽👄用處(chù),相反注釋要說(shuō)明爲什麽會這(zhe)樣寫代碼?
>>> 5. 開閉(bi)原則
接口僅需(xu)指明用戶調用(yòng)程序可能調用(yòng)的标識符,應盡(jin)可能地将算法(fa)以及一些與具(jù)體的實現細節(jiē)🚩無關的信息隐(yin)藏起來,這樣用(yòng)戶在調用程序(xu)時也就不必依(yī)賴特定的實現(xian)細節了。當接口(kǒu)一旦發布🐆後,也(ye)就不能改變了(le),因爲改變接口(kou)勢必引起用戶(hù)程序的改變。如(ru)果此前定義的(de)接口滿足不了(le)需求,怎麽辦?隻(zhi)能擴展新的接(jie)口,但不能修改(gǎi)或廢除原有的(de)接口,這就是“對(duì)修改關閉,對擴(kuo)展開放”的開閉(bi)原則(Open-Closed Princple,OCP)。顯然,依賴(lài)倒置原則更加(jia)精确的定義就(jiu)是面向接口的(de)編程,它是實現(xiàn)開閉原則的重(zhong)要途徑。如果DIP依(yī)賴倒置原則沒(mei)有實現,就别想(xiǎng)實現對擴展開(kai)🐆放,對修改關閉(bì)。
技術支持
- 售後(hòu)服務
- 服務流程(cheng)
The URL is:
http://qigi.cc
MySQL server error:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'and id!=211 and id!=210 order by orders desc,id desc' at line 1 ( 1064 )