精品看🚩看影院🌈福州江閩儀器技術有限公司
産品搜索:

技(ji)術交流

周立功(gong):設計良好的程(chéng)序接精品🚩看看影院🌈口需注意(yi)的5個事項
來源(yuán):   發布時間:2025-12-07   浏覽(lan)量:66

周立功教授(shòu)數年之心血之(zhi)作《程序設計與(yǔ)數據結構》,電子(zi)版已無償性分(fèn)享到電子工程(cheng)師與高校群體(ti)。書本内🛀🏻容公開(kai)後,在電子行業(ye)掀起一片學習(xi)熱潮。經周立功(gōng)教授授權,特對(duì)本書内容進行(háng)連載,願共勉之(zhī)。

第一章爲程序(xù)設計基礎,本文(wen)爲1.5.2/1.5.3共性與可變(biàn)性分🚶析:建立抽(chou)象和建立接口(kou)。

>>>> 1.5.2 建立抽象

從(cóng)概念的視角來(lai)看,建立抽象關(guān)注的不是如何(he)實現,而是❌函💛數(shù)要做什麽,過早(zao)地關注實現細(xì)節,将實現細節(jie)隐藏起來,進🚶‍♀️而(ér)幫✍️助我們構建(jian)更易于修改的(de)軟件。因此,我們(men)首先📐應該選擇(ze)一個具有描述(shu)性的符合需👉求(qiu)的名字,雖然可(ke)以選擇的名字(zì)有swapByte、swapWord和swap,但swap更簡潔(jie)更貼切。其次,可(ke)以用一句話概(gài)⭕念性地描述🐉swap的(de)數據抽象✍️——swap是實(shí)現兩個數據交(jiāo)換的函數。

顯然(rán),調用者僅需一(yī)般性地在概念(nian)層次上與實現(xian)者交流,因爲調(diao)用者的意圖是(shi)如何使用swap()實現(xian)兩個數據的交(jiao)換,所以無需準(zhun)确地知道實現(xian)的細節。而具體(ti)如何完成數據(jù)的交換,這是在(zài)實現層次進行(hang)⭐的。由此可見,将(jiang)模塊🚶‍♀️的目的與(yu)實現分離的抽(chou)象揭示了問題(ti)的本質,并沒有(yǒu)提供解決方案(an)。隻說明需要做(zuò)什麽,并不會指(zhi)出如何實現某(mǒu)個模塊。隻要概(gài)念不變,調用者(zhě)與🤟實現細節的(de)變化就徹底隔(ge)離了。當🎯某個模(mó)塊完成編碼後(hòu),隻要說明該模(mo)💋塊的目的和參(can)數就可以使用(yong)它,無需知道具(ju)體的實現。

函數(shù)抽象對團隊項(xiang)目非常重要,因(yīn)爲在團隊中必(bì)須使用其他成(cheng)員編寫的模塊(kuài)。比如,編程語言(yan)本身自帶‼️的庫(kù)函數,由于已經(jīng)被預編譯,因此(cǐ)無法訪問它的(de)源代碼。同時庫(kù)函🌐數不一定是(shi)用💛C編寫的,因此(ci)隻要知道其調(diao)用規🔴範,就可以(yi)🔞在程序中毫無(wu)顧忌地使用這(zhè)個函數。實際上(shang),在使用scanf()函數的(de)過程中,我們考(kao)慮過scanf()是如❄️何實(shi)現的嗎?無✊關緊(jǐn)要。盡管不同系(xì)統實現scanf()的👄方法(fǎ)可🔅能不一樣,但(dan)其中的不同對(duì)于程序員來說(shuo)是透明的。

>>>> 1.5.3 建立(li)接口

接口是由(you)公開訪問的方(fāng)法和數據組成(chéng)的,接口描述了(le)與模塊交互的(de)唯一途徑。最小(xiǎo)化的接口隻包(bāo)含對💰于接🈚口的(de)任務☀️非常重要(yào)的參數,最小化(huà)的接口便于♉學(xué)習如何與之交(jiao)互,且隻需要理(li)解少量的參數(shu),同時易于擴展(zhǎn)和維護,因此設(shè)計良好的接口(kǒu)是一項重要的(de)技能。

>>> 1. 函數調用(yòng)

(1)傳值調用

如何(hé)調用swap()函數呢?實(shí)參将值從主調(diao)函數傳遞給被(bei)調函數,也許其(qi)調用形式是下(xia)面這樣的:
swap(a, b);

從黑(hēi)盒視角來看,形(xíng)參和其它局部(bù)變量都是函數(shù)私有的,聲🔞明在(zài)不同函數中的(de)同名變量是完(wan)全不同的變量(liàng),而且函數無法(fǎ)🔴直接訪問其它(ta)函數中的變量(liang),這🏃‍♀️種限制訪問(wen)保護了數據的(de)完整性,黑盒發(fa)生了什麽對主(zhu)調函數是不可(ke)見的。

一個變量(liang)的有效範圍稱(cheng)作它的作用域(yu),變量的作用域(yù)☀️指可以通過變(biàn)量名稱引用變(biàn)量的區域,在函(han)數内部聲明的(de)變量隻在該函(hán)數内部有效。當(dāng)主調函數調用(yòng)子函數時,主函(hán)數内聲明的變(biàn)量在子函數内(nei)無效⛹🏻‍♀️,子函數内(nei)聲明的變量也(ye)隻在該子函數(shù)内部有效。

由于(yú)傳遞給函數的(de)是變量的替身(shen),因此改變函數(shù)參數✊對原始變(biàn)量沒有影響。當(dang)變量傳遞給函(han)數時🛀,變量的值(zhi)被複制給函數(shu)參數。由此可見(jiàn),通過“傳值調用(yong)”方式交換a、b的值(zhí),無法改變主調(diào)函數相應變量(liàng)的值。

如果希望通過(guo)被調函數将更(gèng)多的值傳回主(zhu)調函數而改變(biàn)🎯主調函數中的(de)變量,則使用“傳(chuan)址調用”——将&a、&b作爲(wei)💜實參傳遞給形(xíng)參。其調用形式(shi)如下:
swap(&a, &b);

利用指針(zhēn)作爲函數參數(shu)傳遞數據的本(ben)質,就是在主調(diào)✊函數和被調函(han)數中,通過不同(tóng)的指針指向同(tóng)一内存地㊙️址訪(fang)問相同的🍓内存(cun)區域,即它們背(bèi)後共享相同的(de)内存,從而實現(xiàn)數據🔅的傳遞和(he)交換。

>>> 2. 函數原型(xing)

函數原型是C語(yǔ)言的一個強有(yǒu)力的工具,它讓(ràng)編譯器捕獲在(zài)🏃‍♀️使✏️用函數時可(kě)能出現的許多(duō)錯誤或疏漏。如(ru)果編☂️譯器沒有(yǒu)發現這些問題(ti),就很難察覺出(chū)來。函數原型包(bao)括函數🈲返回值(zhi)的類型、函數名(ming)和形參列表(參(cān)數的數量⛹🏻‍♀️和每(mei)個參數的類型(xíng)),有了這些信息(xī),編譯器就可以(yi)檢查函數調用(yòng)與函數原型是(shì)否匹配?比如,參(can)數的數量是否(fǒu)正确?參數的類(lei)型是否匹配?如(rú)果類型不匹配(pèi)✂️,編譯器會将實(shí)參的類型🈚轉換(huan)成形參的類型(xíng)。

(1)函數形參

通過(guò)程序清單 1.15可以(yi)看出,其相同的(de)處理部分是2個(gè)👉int類值的交換代(dài)碼,因此可以将(jiāng)數據交換代碼(mǎ)移到swap()函數的實(shi)現中,其可變的(de)數據由外部傳(chuán)進來的參數應(ying)對🔴。由于&a是指向(xiang)int類型變量a的指(zhǐ)針,&b是指向int類型(xing)變量b的指針,因(yin)此必須将p1、p2形參(cān)聲明爲指向int *類(lèi)型的指針變量(liang),即必須将存儲(chǔ)int類型值變量🍉的(de)地址作爲🌈實參(can)賦給指⚽針形參(can),實參與形參才(cái)能匹配。其函數(shù)原型進化📱如下(xia):
swap(int *p1, int *p2);

(2)返回值的類型(xíng)

聲明函數時必(bì)須聲明函數的(de)類型,帶返回值(zhí)的函數類✔️型應(ying)該🥰與其返回值(zhi)類型相同,而沒(mei)有返回值的函(han)數應該聲明爲(wei)void。類型聲明是函(han)數定義的一部(bù)分,函數類型指(zhi)的是返回值的(de)類型,不是函數(shu)參數📧的類型。

雖(sui)然可以使用return返(fan)回值,但return隻能返(fan)回一個值給主(zhu)調函數。比如,如(ru)果返回值爲整(zhěng)數,則函數返回(huí)值的類型爲int。當(dāng)返回值爲int類型(xing)時👨‍❤️‍👨,如果返回值(zhi)爲負數,則表🌈示(shì)失敗;如果返回(hui)值爲非負數,則(zé)表示📧成功。當返(fǎn)回值爲bool類型🐕時(shi),如果返回值爲(wèi)false,則㊙️表示失敗♊,如(ru)果返回🌐值爲true,則(ze)表示成功。當返(fǎn)回值爲指針類(lèi)型時,如果返回(huí)值爲NULL,則表示☂️失(shi)敗,否則返回一(yī)個有效的指針(zhēn)。

如果利用指針(zhēn)作爲參數傳遞(di)給函數,不僅可(ke)以向👉函數傳入(rù)數據,而且還可(kě)以從函數返回(hui)多個值。因爲函(hán)🌈數的❄️調用者和(hé)函數🆚都可以使(shǐ)用指向同一内(nèi)♉存地址⁉️的指針(zhēn),即使用同一塊(kuai)内存,所⭐以使用(yong)指針作爲函數(shu)參數時就是對(dui)同一數據進行(háng)讀寫操作。這樣(yàng)不僅可以傳入(ru)數據,還可以通(tong)過在函數内部(bu)修改這些數據(jù),将函數的結果(guǒ)傳出給調用者(zhe)。

當函數的實參(can)是指針變量時(shí),有時希望函數(shu)能通過指🔞針指(zhi)✂️向别處的方式(shì)改變此變量,則(ze)需要使用指向(xiàng)指針的指針作(zuò)爲形💯參。

由于swap()無(wú)返回值,因此swap()返(fǎn)回值的類型爲(wei)void,其函數原型如(ru)下:
void swap(int *p1, int *p2);

其被解釋爲(wèi)swap是返回void的函數(shù)(參數是int *p1,int *p2)。

這是一(yī)個不斷叠代優(you)化的過程,用戶(hù)隻需要知道“函(han)數名、傳🌈入函數(shù)的參數和函數(shu)返回值的類型(xíng)”,就知道⁉️如何有(you)效地調用相應(ying)的函數。

在面向(xiàng)過程編程中,通(tōng)常的做法是高(gao)層模塊調用低(di)層模塊,其🏃‍♂️目的(de)之一就是要定(ding)義子程序層次(cì)結構。當🎯高層🌈模(mó)塊依賴🌈于低層(ceng)模塊時,對低層(ceng)模塊的改動會(huì)直接影🈚響高層(céng)模塊,從而迫使(shǐ)它們依次做👉出(chū)修改。如果高層(céng)模塊獨立于低(dī)層模塊,則高層(ceng)模塊更容易重(zhòng)用,這就是分🔴層(ceng)架構設計的核(hé)心原則,即依賴(lài)倒置原則(Dependence Inversion Principle,DIP):

● 高層(ceng)模塊不應該依(yi)賴低層模塊,兩(liang)者都應該依賴(lài)🤩于抽象接口;

● 抽(chōu)象接口不應該(gai)依賴于細節,細(xì)節應該依賴抽(chōu)象接口。

當在分(fèn)層架構中使用(yòng)依賴倒置原則(ze)時,将會發現⭕“不(bú)再存在🔴分☀️層”的(de)概念了。無論是(shì)高層還是低層(céng),它們都依賴于(yú)抽象接口,好像(xiàng)将整個分層架(jià)構推平一樣。

其(qí)實從“Hello World”程序開始(shi),我們就已經在(zài)使用stdio.h包含的“抽(chou)象接口”了,即以(yi)後凡是用#include文件(jian)的擴展名叫.h(頭(tóu)文件)。如果源代(dài)碼中要用到stdio标(biao)準輸入輸出函(han)數時,那麽就要(yao)包含這個頭文(wen)件,比如,“scanf("%d",&i);”函數,其(qí)目的是告訴編(biān)譯㊙️器要使用stdio庫(ku)。庫是一種工具(jù)的集合,這些工(gōng)具是由其它程(chéng)序員🌏編寫的,用(yòng)于實現特定的(de)功能。盡管實🥵現(xiàn)者無需關🥵心用(yòng)戶将如何使用(yòng)庫,且不會直接(jie)開放源代碼給(gěi)用戶使用,但必(bi)須給用戶提供(gong)調用函數🤟所需(xu)要的信息。顯然(rán)隻要将頭文件(jiàn)開放給用戶,即(jí)可讓用⛱️戶了解(jie)接口的所有細(xi)節,詳見程序清(qīng)單✊ 1.16。

程序清單 1.16 swap數(shu)據交換接口(swap.h)
1     #ifndef  _SWAP_H
2     #define  _SWAP_H
3     // 前(qián)置條件:實參必(bì)須是int類型變量(liang)的地址
4     // 後置條(tiao)件:p1、p2作爲輸出參(cān)數,改變主調函(hán)數中相應的變(bian)量
5     void swap(int *p1, int *p2);
6     // 調用形式:swap(&a, &b)
7     #endif

其(qí)中,每個頭文件(jiàn)都指出了一個(gè)用戶可見的外(wai)部函數接口,主(zhǔ)㊙️要包括函數名(míng)、所需的參數、參(cān)數的類✏️型和返(fǎn)回💘結果的類☎️型(xing)。其中,swap是庫的名(ming)字,程序清單㊙️ 1.16(1~2)與(yǔ)(8)是幫助編譯器(qì)記錄👈它所讀♋取(qǔ)的接口,當寫一(yi)個🈲接口時,必須(xū)包含#ifndef、#define和#ednif。#include行部分(fèn)僅當接口本身(shen)需要其它庫時(shí)才使用,它由标(biāo)準的#include行組成。程(chéng)序清單 1.16(6)接口項(xiàng)表示庫輸出🧡的(de)函數的原型♈、常(cháng)量和類型等㊙️。不(bú)管你是否理解(jie),這些🐪行是接口(kǒu)的模闆文件,這(zhe)就是信息隐藏(cang)。

>>> 4. 前/後置條件

處(chù)理信息隐藏還(hai)涉及到另一個(gè)技術,那就是使(shǐ)用🔞前置條件㊙️和(he)後置條件描述(shu)函數的行爲。在(zài)編寫一個完整(zheng)的函數定義時(shi),需要描述該函(hán)數是如何執行(hang)計算的。但在使(shǐ)用函數時,隻♻️需(xū)考慮該函數能(néng)做👨‍❤️‍👨什麽,無需知(zhī)道是如何完成(cheng)的。當不知道🥵函(han)數是如🙇‍♀️何實現(xian)時,就是在使用(yong)一種名爲過程(chéng)抽象的信息隐(yǐn)藏形式,它抽象(xiàng)掉的是函數如(ru)何工作的細節(jie)。計算機科學家(jiā)使用“過程”表示(shi)任意指令集,因(yin)此使用術語過(guò)程抽象。過程抽(chōu)象是一種強大(da)的工具,使得我(wǒ)們一次隻考慮(lǜ)一個而不是🆚所(suo)有的函數,從📐而(ér)使問題求解簡(jiǎn)單化。

爲了使描(miao)述更準确,則需(xū)要遵循固定的(de)格式,它包含兩(liang)㊙️部分信🍉息:函數(shù)的前置條件和(hé)後置條件。前置(zhì)條件就是調用(yong)該函數必🐕須成(chéng)立的條件,當函(hán)數被調用時,該(gai)語句給出要求(qiú)爲真的條件。除(chú)非前置條件爲(wei)真,否則無法保(bǎo)🈲證函數能正确(que)執行。在調用swap()函(hán)數時,實參必須(xu)是int類型變量的(de)地址,這是調用(yong)者的🥰職責。通常(cháng)在函🌈數開始處(chu)檢查是否滿足(zú)?如果不滿足,說(shuō)明調♋用代碼有(you)問題,抛出一個(ge)異常。

後置條件(jiàn)就是該操作完(wán)成後必須成立(li)的條件,當函數(shu)調用時,如果函(han)數是正确的,而(er)且前置條件爲(wei)真,那麽該函數(shu)調用将可以執(zhi)行完成。當函數(shu)調用完成後,後(hòu)置條件爲真。如(rú)果不滿足後置(zhì)條件,則說♋明業(yè)務邏輯有問題(ti)。

當滿足調用swap()函(han)數的前置條件(jian)時,必須同時确(què)保其結✔️束時滿(mǎn)❄️足它的後置條(tiao)件,其後置條件(jian)是被調函💛數将(jiang)🔴返回值傳👈回主(zhǔ)調函數,改變主(zhu)調函數中變量(liang)的值。

前後置條(tiao)件不隻是概括(kuo)地描述函數的(de)行爲,聲明這些(xiē)條件應該是設(shè)計任何函數的(de)第一步。在開始(shǐ)考慮某個函數(shù)的算法和🤟代碼(ma)之前,應該寫出(chu)該函數的原型(xíng),其中包括函數(shu)的返回類型、名(míng)稱和參數🈲列表(biao),最後緊跟一個(gè)分号。直接來自(zì)于用戶的輸入(rù)不能作爲前置(zhì)條件,通常前/後(hou)置條件都可以(yi)轉化爲assert語句。編(biān)寫函數原型時(shí),應該以注釋的(de)形式🆚描述該函(han)數的前置條件(jian)和後㊙️置條件。

事(shi)實上,前置條件(jiàn)和後置條件在(zài)使用函數的程(cheng)序員和編寫函(hán)數的程序員之(zhi)間形成了一個(ge)契約,也就是㊙️爲(wèi)什麽需要這個(ge)函‼️數?接口通過(guo)前置條件和後(hou)置條🐆件以契🎯約(yue)的形式表達需(xu)求,承諾在滿足(zú)前置條件時開(kai)始,按照🤩程序的(de)流程運行,系統(tǒng)就能到達後置(zhì)條件。

雖然注釋(shì)是一種很好的(de)溝通形式,但在(zài)代碼可以傳遞(di)意📱圖的地方不(bú)要寫注釋。因爲(wèi)代碼解釋做了(le)☁️什麽,再注👄釋也(yě)沒有什麽用處(chù),相反注釋要說(shuo)明爲什麽會🌂這(zhe)樣寫📐代碼?

>>> 5. 開閉(bi)原則

接口僅需(xu)指明用戶調用(yong)程序可能調用(yong)的标識符,應盡(jin)可能地将算法(fa)以及一些與具(ju)體的實現細節(jie)無關的信息隐(yǐn)藏起來,這樣用(yong)戶在調用程序(xù)時也就不必🌍依(yi)賴特定的實🏒現(xiàn)細節了。當接口(kou)一旦發布💛後,也(ye)就不能改變了(le)✨,因爲改變接口(kou)勢必引起用戶(hu)程序的改變。如(rú)果此前定♻️義的(de)接口滿足不了(le)需求,怎麽辦?隻(zhi)能擴展新的接(jie)口,但不能修改(gai)或廢🤟除原有的(de)接口,這就是“對(dui)修改關閉,對擴(kuo)展開放”的開閉(bì)原則(Open-Closed Princple,OCP)。顯然,依賴(lài)倒置原則更加(jia)精确的定義就(jiù)是面向接口的(de)編程,它是實現(xian)開閉原則的重(zhong)要途徑。如果DIP依(yi)賴倒置原則沒(mei)有實現,就别想(xiǎng)實現對擴展開(kāi)🐅放,對修改關閉(bi)。

总 公 司急 速 版WAP 站H5 版无线端AI 智能3G 站4G 站5G 站6G 站
··
  
 
 
·
·
·