當了12年大學教師,跟大家聊聊嵌入式工程師,硬核單片機編程思想

2022-04-24     大方老師單片機

原標題:當了12年大學教師,跟大家聊聊嵌入式工程師,硬核單片機編程思想

12年大學教師,跟大家聊聊嵌入式工程師,硬核單片機編程思想

摘要:沒有思想的裸程序就如一副人體骨架,有個人形,但沒有人樣,骨骼之間的關節都是靠膠水或拉線連接起來的,生硬而呆板。假如給骨架包上皮肉,加上靈魂,我們就會驚嘆:啊!這是帥哥,這是美女!由於骨架活了。

///插播一條:我自己在今年年初錄製了一套還比較系統的入門單片機教程,想要的同學找我拿就行了免費的,私信我就可以~點我頭像黑色字體加我地球呺也能領取哦。最近比較閒,帶做畢設,帶學生參加省級或以上比///正文開始:

一、裸編程是什麼?

先聲明一個概念,裸編程,指的是在裸機上編寫程序,裸機,在單片機領域就是指帶著硬體的單片機控制系統,不要想歪咯。

在裸機上編程,就猶如在一片荒地上開墾,任何一鋤頭下去,都會碰到硬生生的石頭,要說做這有什麼味?拓荒者追求的是來年的綠洲。而我們這些開墾裸機的所謂的工程師們追求的是什麼?我們當然追求的是完成一個任務。

我們一般都自稱是高級知識分子,那麼我們在拓荒的過程中應該想些什麼?當然不是想著如何把任務完成,而應該首先想著我們在想些什麼。繞了是不?繞了就對了,這一繞就繞出了思想。思想是一個簡略的人在一個複雜的環境里做任何事情的統帥,它影響著一個拓荒者人生的每一個細節,當然也包括裸編程自身。

當一個人拿著鋤頭,一鋤又一鋤,汗滴腳下土的時候,我們能知道他們在想什麼嗎?當然這不好說,假如自己去鋤就知道了。但是大抵也差不多,隨便舉幾個吧:這太陽他娘的怎麼這麼毒?這石頭他娘的咋這麼多?這地種什麼最好?這還有多少天能搞完?這樣干太慢了,要是有台機械搞多好。當然這只是一局部,任何人能夠想出很多想法來。

那麼當我們在裸機上拓荒的時候,我們該想些什麼?興許我們一般的想法是:先把一個簡略的功能做了,先把一個重要的功能做了,今天終於把這個功能調試好了明天能夠做下一個功能了,這個為什麼不是我想像的那樣的結果?真是莫名其妙!也等等一下吧。

假如拿來一個任務,搭好測試平台就初始做程序,想著一個功能一個功能的湊完,然後就自我陶醉著成功的喜悅,那這樣做程序,根本就叫做沒思想。有思想的做程序,是不能一下去就堆積源碼的,由於那樣只會讓一堆生硬的數字怯生生的擠在一起,不管他們有沒有多餘,有沒有矛盾。所以寫源碼之前,是要想想如何寫的。興許很多人在寫之前都想過類似的問題,假如把任務模塊化後再組織程序。但是這樣的想法只是任務上的事情,而並不是裸編程時的思想,裸編程的思想,應該是在組織任務模塊過程中及編寫裸程序時影響源碼組織的指教思想,它直接決定著源碼的質量。

一個數據構造,一個模塊構成,一個單片機的指令,一個硬指令的運行機制,一個口線的驅動方式,一個中斷的順序,一個跳變的延遲,一個代碼的位置,一個邏輯的組織,一個模塊與模塊之間的(運行時的狀)(不運行時的狀)關係等等,都是裸程序思想的組成局部。

這似乎很瑣碎,但裸程序原本就如此,它不同於上位機程序,有一個強大完善的作業系統支持。單片機里不可能植入作業系統,那樣做就變味了,可不要有人跳出來說,某某某單片機就有作業系統了。裸程序就應該是建設在赤裸裸的硬體根底上的程序,獨有有用的功能才有代碼,裸程序的質量興許經常在應用中感覺不出來,興許你做和他做都能實現功能,但是好的裸程序有良好的可擴充性、可維護性,系統具有高穩定性和高性能。

而追求這種高品位的技術境界,就必需要有好的思想來指教。是不是看著有些迷糊?別說看得迷糊,我說都說迷糊了,總的來說,就是把一個優秀的靈魂,植入你的源碼中,讓你的源碼具有一個良好的思想。

二、裸編程詳細做法

前文說到裸編程要有思想,興許還不夠詳細,接下來就是要詳細說裸編程的思想的詳細做法。

沒有思想的裸程序就如一副人體骨架,有個人形,但沒有人樣,骨骼之間的關節都是靠膠水或拉線連接起來的,生硬而呆板。假如給骨架包上皮肉,加上靈魂,我們就會驚嘆:啊!這是帥哥,這是美女!由於骨架活了。

裸程序也一樣,假如按傳統的思維方式說這樣就足夠了,那麼裸程序就形如骨架,通常只是一些功能的粗糙堆砌,也只會叫後人看了說這程序垃圾,而後人再做也未必能跳出這個圈子,那麼後後人看了又叫這程序垃圾,如此下去,代代相傳,傳了什麼?傳了一個總被叫垃圾的東西:沒思想的裸程序。

我做了程序好多年,也思考了編程好多年,不斷的經歷積攢告訴我:寫好的程序不是如何去完成代碼,而是如何去組織代碼。上位機中面向對象的編程思想,就是一個非常可取的思想。

面向對象的編程思想在上位機中是有一個非常豐盛的開發包和功能強大的作業系統支持的,裸編程如何引入這樣的思想呢?興許很多人會覺得不可能。

其實,沒有什麼是不可能的。再複雜的思想,最終都會歸結到彙編,歸結到裸程序,我們的單片機程序,正是一種裸程序。只是在單片機編程時和微機編程時我們站在開發平台上的高度不一樣,罷了!

對這個高度的了解,興許很多人很困惑,由於我們平時很少注意它們,那麼這裡我就舉個其他的例子來說明,只管和裸編程好象不很相關,但是這個例子裡的高度概念十分清晰。

我們知道網絡傳輸規範層次有七層:應用層、表示層、會話層、傳輸層、網絡層、鏈路層、物理層,這麼多層做什麼用?興許了解這樣分層的概念也十分辛苦,但是了解這樣分層的思想,就容易多了,而且這也是我們硬體工程師們最應該借鑑的思想,讓我們的硬體設計更具有規範性和前瞻性。

這個七層的思想從根本上講就是將一個網絡傳輸產品細化,讓不同的製造商選擇一個合適自己的層次開發自己的產品,層次不一樣,他們所選擇的開發根底和開發內容就不一樣,高一層開發者繼承低層開發者的成果,從而節省社會資源,提高社會出產力。對這個指教思想我就不贅述了,各位自己去了解,這裡要說的是,微機上的面向對象編程思想就是如同在應用層上實現的思想,而裸程序的面向對象思想則如同在鏈路層上實現的思想,他下面沒有軟體開發包,獨有物理構架。但是在應用層上實現的思想,最終都要翻譯到物理構架上。

看懂了上面的例子,就一定明白,裸程序的面向對象思想,是能夠實現的,只是難度要大得多,了解要難得多。但是這不要緊,這正是軟體水平的表現,你喜愛技術,又何懼之?其實也不會難到哪裡去,只是把做事情的方式稍微變更一下罷了。

傳統上我們都喜愛用功能來劃分模塊,細分任務,面向對象思想不這樣。面向對象思想則是先從一個任務中找出對象,在對象中攙雜些模塊等來實現功能的。這就是兩種格調截然不同的地方。假如我們要讓我們的單片機把顯示信息輸出到顯示器,那麼傳統的分析方法是信息格式化、格式化數據送顯示器顯示,似乎這樣也就足夠了,不同的顯示器用不同的送顯示程序或者程序段,配置不同的變量,能共的共起來,不能共的分開。

但是面向對象的思想不是這樣做的,而是首先把顯示器當作一個對象,該對象具有一些功能和一些變量屬性,不同的顯示器在對象中使用相同的代碼標識,如函數指針C語言中),這樣對於任何一個不同的顯示器,在調用時都使用同樣的代碼。興許有人說,傳統的做法這樣也能夠做呀,為什麼要弄得羅里吧唆的呢?其實不然,使用了正確的思想的益處在前頭已經說了好多了,假如還含糊就上去再看一次。

說了那麼多理論,此時就說些詳細的做法吧。KeilC為編譯環境來說說一個對象詳細組織的一些做法。首先是找出對象,如顯示器,這就是一個典型的對象。其次是分析一個活對象所應具有的根本特徵,即屬性與動作。顯示器的屬性如:類型代號、亮度、比照度、顯存等,動作如:初始化、內容刷新和顯示、開啟和關閉、內容閃爍等花樣顯示等。

這樣分也比較容易了解,下面是對於代碼的組織上,要注意對象的獨立性與完整性,首先把顯示器對象單獨放在一個文檔上,屬於對象特有的變量與對象的定義放在一起,要區分公有變量與私有變量的定義方式,對於私有變量要考慮臨時變量與永久變量的安排,這些安排都是對變量生命期的嚴格確定,這樣能夠節省內存,避免混亂。

如某一個函數要使用一個變量,函數在調用完了就退出了,而有一個變量獨有它使用,卻要保存每一次調用函數所產生的結果,這樣的變量怎麼定義呢?

很多人會直接定義一個全局變量,但是一個好的做法是把這個變量定義成該函數的局部變量,但是定義成靜態的,那麼這樣這個變量對其他代碼就是透明的,完全不可能會被誤修改,而且代碼分類性好,便於將來的維護。用函數指針來統一不同類型的顯示器不同的處理方式,也是一個很好的處理辦法,那樣能夠讓詳細處理方式千差萬別的顯示器都能用一個統一的對象,但是函數指針要謹慎使用。

好了,說長了我就頭暈,不說了,思想的精髓,不必有一樣的形態,不同的人會有不同的了解,我只希望能給大家的程序生涯拋磚引玉,我就覺得很有成就感了。

三、準備工作

本文在此引用一個例子。在引入例子之前,我們要做一些準備工作,然後一步一步地走向例子裡去。就以前面帖子提到過的顯示器控制為例。

顯示器就是一個對象。沒論它是功能多麼複雜的顯示器,或者功能多麼簡略的顯示器,對於須要顯示信息的調用者來說,都並不重要,也就是說對於須要使用顯示器的主體來講,他只管顯示信息,不管顯示器的千差萬別,只有顯示器提供了某功能,它能夠調用就行,調用前當然要遵守顯示器的數據傳遞規則,但是不必考慮不同的顯示器所產生的傳遞規則的差異。也就是說,對於調用者來說,永遠不會希望有多條規則來讓自己的調用代碼變複雜。

因此,我們首先須要構造一個相對獨立的代碼段,也就是顯示器對象,以下都KeilC作為裸程序的編譯環境。正如很多人說的KeilC並不OOP語言,那怎麼做?正是由於我們認KeilC不能做,所以我才把這種思想拿出來與大家探討,讓我們的程序變得更精彩,更有技術含量。

構成一個獨立代碼段,最好的辦法就是在主工程目錄下建設一個子目錄,DISPLAY,然後再DISPLAY目錄下建設一個文檔,DISPLAY.H,然後DISPLAYDISPLAY.H#include到一個恰當的位置上,這樣,一個獨立的面向對象的代碼段就初步構成了,以後維護代碼的時候,你永遠不要考慮調用者是什麼感受,只有維護這個文檔,就能夠保證你對顯示器的不斷更新了。

很多人興許會說,這算什OOP?大家先鄙兗著急,這才是個初始,下面才是組織代碼的詳細過程。

對於一個顯示器,我們必需要有顯示要求,我們才會去定製它,假如連使用要求都提不出來,就不要去讓人為你做顯示器。所以我們首先要明確我們要的顯示器必需要做什麼。由於是單片機控制的顯示器,我們不能想像成微機顯示器那樣,一個大的顯存,能夠顯示多少頁,顯示多少色,滿屏滿屏的傳遞數據,假如這樣想了,就是犯了盲目例比的錯誤,說明對問題沒鑽研透。對於單片機控制的顯示器,我們考慮能顯示單個字符、單行顯示,就根本足夠了。所以我們能夠定義下列兩個對象功能:

dispShowAChar;//顯示一個字符

dispShowALine;//顯示一行字符

由於是單片機的裸系統,所以我們作為一個軟體設計者,我們一定要清楚,我們所面對的顯示器,經常是沒CPU的,所以我們一定要明白,我們這兩個函數,實質上都做些什麼。很顯然,這兩個函數是不能長期占CPU的,否則我們的程序將什麼都不能做,專去顯示了,成了顯示器的一個處理晶片,所以這兩個函數運行完後是肯定要退出來的,而顯示不能中斷呀,所以必需要有一個代碼段一直存在於活動代碼中而且不能影響其他的功能。做過上位機程序的人應該能看出來,這段代碼就是線程。裸編程中我們也用這個概念。

我們的顯示器對象正須要一個一直活動的線程,來完成單片機系統對顯示功能的解釋和執行,因dispShowAChar()dispShowALine()實質上是不能直接去做顯示工作的,它倆最適宜的工作,就是去按指定的格式去設置顯示內容,這樣我們在使用的時候就不必在這兩個函數裡設置複雜的代碼和嵌套調用關係,由於那樣一定會浪費很多的代碼,調用多了也會讓單片機運行效率降低,硬體資源耗費增加,嚴重的可能會造成堆棧溢出最後還不曉得為什麼。讓我們也為這個活動線程也先命個名吧:

dispMainThread;//按指定的要求執行顯示功能

//指定的要求包括顏色信息、閃爍、遊動等等

程序分析下去,引出的概念也就會越來越多,這裡所說的多線程概念以後有時機再說,單片機里的多線程也是一個複雜羅嗦的處理問題,此時介紹還為時過早。只是我感覺一不小心又說長了,詳細下文繼續展開。

四、展開思想

對於對象才能的定義,我們一般能夠從重要的入手,然後慢慢地展開,把所須要的其他才能漸漸歸納為函數,從而把面向對象的思想開展下去。上文我們提到了三個函數是怎麼來的,還沒有波及到函數的任何實質,那麼本帖就探討一下這三個函數的實質性布局與設計。

有了功能要求,我們就要實現它,在裸程序中,實現它的一個首要任務,就是要進行數據傳遞方式的設計。很顯然我們必需要有一個顯示區域,來寄存我們所要顯示的內容,以及顯示內容的顯示屬性,我們還要布局這個顯示區域到底要顯示多少多少字符或者是點陣。但是由於我們事先並不知道我們的顯示設備一次會提供多少顯示量,所以我們能夠把顯示區域的內存,也就是顯存,定義得大一點,以至任何一款合乎設計要求的顯示器都能得到滿足,這樣的做法在裸編程中其實還是比較實用的,由於裸編程中我們很少去申請動態的空間,程序設計完,所有的變量位置皆已確定,行就行,不行編譯就過不去,所以我們能夠通常選擇一些內存資源比較豐盛的新款單片機。

但是這樣的做法也有一個弊端,假如當我們預先大約不足而導致數據空間不夠的時候,我們就得從頭來改這個顯存的大小,從而導致整個顯示程序都要相應的產生一些變動,這還不是最糟糕的,最糟糕的是當一款新的顯示器由於新的功能需求而導致數據構造須要發生變化的時候,我們就崩潰了,前期的工作可能改動就非常大,甚至於都要重新過一遍,也就是重寫重調,這麼痛苦的事情,我是最討厭的了。

所以我們要儘量避免這類事情發生,這裡對面向對象的思想,就頗為需求了。這個時候,我們就要引入一個新的概念,那就是對象的兒子,子對象。前面探討的,其實都只是一個抽象的對象,沒有任何詳細的樣子,而只是籠統的布局了所有的顯示器必需具有什麼才能,而對於每一個詳細的顯示器來說,還沒有任何詳細的設計,在這裡,每一個詳細的顯示器,就是顯示器對象的子對象,他們形態各異,但是都必需能完成規定的功能。以傳統OOP語言理論來說,這裡就產生了一個繼承的關係,但是在裸程序思想里,我並不贊成引入這個概念,由於傳統OOP語言里的繼承,純粹是一個語法上的邏輯關係,繼承關係明確,而裸程序中的這個思想,並沒有任何語法支持,繼承關係就非常微弱了,還不如說是歸類與概括。但沒論是什麼關係,我還是不想就這種不言而喻的關係弄個新名詞來,讓看的人費解。

既然引入了子對象,我們能看出這種做法有什麼實際意義嗎?興許有經歷的資深程式設計師能看出來。我們在做父對象數據設計的時候,我們並不規定詳細的數據格式和顯存大小,而是一股腦兒地全推給子對象自己去搞,父對象什麼都不管。哈哈!這樣做事情真是很簡略吧?不是我的事情我不管,不要說我偷懶,由於站在父對象的角度講,這是最明智的做法,由於管不了,所以不管。

到這裡興許就會產生更多的疑問了,一個對象什麼都不管,那作為調用者怎麼使用這個對象呢?你想用它,它什麼都不管,這怎麼行呀?別著急,父對象不管的,是那些詳細的事情,抽象的事情,還是管的,要不然它就沒有理由存在了。你埋怨了,說明你在思考,既然思考了,就把思考的問題提出來,提出來的,就是我們設計父對象的依據。提問題,我想這比搞程序要簡略得多,假如:顯示器能顯示多少乘多少的字符?顏色是多色還是單色?顯示模式是否支持預定的方式(如挪動、閃爍等)?工作模式是圖像還是字符?等等,這裡附加說明一下,對於顯示模式,我們這裡都以字符顯示為例,既然是面向對象的思想,相信擴充出圖像顯示模式,還是很容易的事情。

有問題出來了,我們就繼續為它添加代碼好了。

dispGetMaxCol();//取一行最多有多少列

dispGetMaxRow();//取顯示器一共有多少行

dispGetMaxColors();//取顯示器最多有多少色

dispSetShowMode();//設置顯示的方式,對於不支持的顯示方式就自動轉為正常顯示

dispSetWorkMode();//設置工作模式,假如沒有的模式就返0,支持的就返1

對於這些函數的定義,各人能夠依據自己的習慣來設置,我只是臨時弄了這個例子,未必就是最好的,我的目標是重在說明思想。我也怕把程序弄得龐大了,出本書都嫌厚。

似乎加了這些函數之後,我們根本就沒看到顯示數據的詳細形式,和前面的函數一起,都並沒有什麼明確的說法。這種感覺很正確,我們的確沒有對顯存做任何定義,但是似乎功能卻都已經定義了,其實也的確是定義了,而且將來我們就這樣用,而且也不用怕,程序一定會寫完的。

五、數據傳遞與程序邏輯同等重要

繼續上面探討的問題。前面我們提到,為了使dispShowAChar()dispShowALine()dispMainThread()這三個函數,我們又引出五個新的函數來,這些新的函數最主要的宗旨,就是要實現調用者與被調用者之間數據的傳遞。

對於程序設計來講,數據傳遞與程序邏輯有著同樣重要的地位,前者經常在最後會構成一種協議,後者則經常表現為各種算法。

在裸程序中,我們的思想應該主要是表現為一種靈魂,而不能C++那樣,去追求語法的完美,所以對於參數的傳遞,我們不能去追求語法上的完美,而是不拘一格用傳遞。除了函數能夠傳遞數據外,直接調用值也是一種很快速的方式,但是調用不能隨便說調就調,而是也要進C++上語法的習慣,儘量不能讓一些專用的變量名稱,出此時與專用變量沒關的程序體中。例如,我們的設計中規定,我們這套裸系統對顯示器最多支65536色,那麼我們就會用一16位的沒符號整數來保存這個指標。為了簡化以後的說明,我們先定義兩個數據類型:

typedef unsigned int UINT;

typedef unsigned char UCH;

假如我們用函數來傳遞這項數據,我們能夠用如下的方式:

#define Monitor01_MaxColors 0xFFFF

對於顏色調用函數則定義如下:

UINTdispGetMaxColors()

{

return Monitor01_MaxColors;

}

很顯然,假如另一個顯示器是個單色顯示器,則顏色調用函數只須要改為下列形式就能夠了:

#define Monitor02_MaxColors 0x0001

UINTdispGetMaxColors()

{

return Monitor02_MaxColors;

}

之前有人提到過,用數組,這能夠攻克很多問題。說得一點沒錯!上面的例子我們忽略了一個問題,那就是同一個函數名要去做很多不同函數所做的事情,而我們卻沒有在函數體內使switch(),這顯然是不對的。要真正實現不同顯示器的共有屬MaxColors的傳遞,我們必需要添switch()以區分不同的顯示器類型。那麼這裡我們就須要引入一個新的父對象屬性以指代它的第幾號兒子:

UCHMonitorType = 0;//顯示器類型,最多支256種顯示器

並在初始化的時候,為該屬性初始化0,以作為缺少省類型顯示器的代號。以下命名我們就說一個約定,以讓代碼更具有規範的模樣:父對象的接口函數用小寫disp打頭,變量Monitor打頭,宏數據Monitor開頭並且內部至少有一個下劃線,宏函數則用全大寫字母組成。那麼不用數組的情況下,上面的代碼將會變成如下形式:

#define Monitor_00 0

#define Monitor_01 1

#define Monitor_02 2

UINTdispGetMaxColors()

{

//以下用多出口,但這並不會破壞什麼,為節約代碼,完全能夠使用

switchMonitorType

{

caseMonitor_01:returnMonitor01_MaxColors;

caseMonitor_02:returnMonitor02_MaxColors;

}

return Monitor00_MaxColors;//缺少省則返回默認顯示器

}

這樣的形式很顯然是太冗長了,只管非常構造化,但是一般在優化程序的時候我們還是可能會廢棄它的,所以這裡就提到了數組的使用。既然是數組,那麼它自然不能屬於某一個子對象,而是應該在父對象中定義的,只管這樣做我們每次在添加新顯示器的時候我們假如在父對象中添加難以了解的新的數據,但是為了節省代碼,我們還是能忍受這樣的痛苦。假如改用數組,則上面的代碼將變更為如下形式:

#define Max_Monitor_Types 3***

#define Monitor00_MaxColors 1

UINTcodeMonitorMaxColorsArrayMax_Monitor_Types=

{

Monitor00_MaxColors//缺少省為單色

Monitor01_MaxColors

Monitor02_MaxColors

};***

***的語句將是前景擴充時不斷須要修改的句子。那麼上面的函數就簡略了

UINTdispGetMaxColors()

{

return MonitorMaxColorsArrayMonitorType;

}

甚至有人還能夠用宏函數來節省運行時長,只有修改一下調用規則就能夠了:

#define DISPGETMAXCOLORS (c) c = MonitorMaxColorsArrayMonitorType;

興許當我們寫成如上代碼的時候,我們的每一次改進,都會讓我們欣喜,我們的代碼又優化了。但是可惜的是,這種沒有思想的優化會在不遠的將來,給我們帶來麻煩。我覺得我的記憶力很不好,興許一分鐘前的事情我都會想不起來,這種在將來擴充中的上竄下跳地修改會讓我覺得暈眩!

所以,在工程化的工作中,我們須要把父對象與子對象儘量隔離開來,減少關聯性的修改量,這也是面向對象思想的重要意義之所在,對於這一改動,我將在下帖中闡述。

六、父子對象接口函數功能剝離

上文我們說 dispGetMaxColors()的一些設計思維,我們有很多很好的辦法來實現它,但是我們有沒有更好的管理辦法來實現它,這才是我們要站在更高層次看問題的焦點,是更重要的。這也就是一個從傳統思維到面向對象思維的一個重要的轉折。

要想把這個函數轉變為面向對象的邏輯構造,我們也要先做些準備工作。

第一說參數傳遞的思想。儘量減少參數傳遞,這是尊C518位單片機硬體現狀的一項重要措施,記著,不要埋C51檔次低,資源少,而是要在尊重和熱C51的前提下,我們才有熱情來開展我們的裸程序面向對象思想的。也就是說,沒論我們面臨的系統有多簡陋,我們都有策略,來實現複雜的功能,而且從開展的眼光來看,產品的升級,並不是盲目標升級我CPU,由於那樣只會讓產品設計者智商下降,所以我覺C51的特色就是應該在簡潔,越來越簡潔,而不是越來越複雜。所以,希望我們把思想升級作為升級產品的一個開展方向。傳遞參數要減少指針、浮點等類型的數據傳遞,儘量UCHUINT為主,而且參數的數量不要太多,最理想的上限2個,假如實在多了,就使用共享緩衝區,或者全局變量。最好是不要傳遞參數。

本函數就利用MonitorType省略了一個參數傳遞。

第二是我們要讓父對象的接口函數與詳細的子對象的多種可能性的功能實現剝離,這裡我們就須要使用函數指針。函數指針興許我們一般用得少,但是其實並不是很複雜。先看我們函數的形式:

UINTdispGetMaxColors(void);

為該函數定義一個指針類型,只需做如下定義,就能夠了:

typedefUINT( *dGMC)(void);

那麼對於父對象中dispGetMaxColors()函數,我們就只須要轉換定義一個函數指針,在創建父對象的時候為它提供一個子對象對應功能調用的入口地址,就足夠了。所以對於這個函數的實體將只在子對象中出現,而在父對象中只會出現一個變量的定義:

dGMCdispGetMaxColors;

為了給它賦初值,我們也能夠定義一個空指針,作為一個未使用的判斷標誌:

# define NIL 0

那麼初始dispGetMaxColors的時候只須要寫條如下語句就能夠了:

dispGetMaxColors=NIL;

而且功能調用也很簡略,與實質的函數是一樣的:

if(dispGetMaxColors=NIL) vMaxColors=dispGetMaxColors();

假如再加上約定,連前面的判斷語句完全能夠省略。由於我們的裸程序的程序空間實際上也是運行空間,不存在代碼調入內存和移出內存的事情發生,所以我們不須要考慮程序內存的優化問題,只有我們規定對象一定是先創建後使用,判斷語句就會變得沒有意義,而且我們創建後即便不再使用,函數體我們也不會釋放,由於它被放在程序空間內是固定死的,你想移出去,還不能實現呢。

第三,儘量讓程序所使用的語法簡略化,以減少編譯器可能帶來的差別而產生的了解上的誤區。有人C51C的子集,這說法我認為不科學,只能說二者繼承C的根本精神,而在實質上它們針對的是不同的對象,所以也出現了一些不同的結果,所以我看到有些高手或者一些面試題弄出一些題目來讓我望而生畏,興許我做一輩子的裸程序也做不出他們的題目,但我並不覺得我不如他們。他們只不過是在編譯器上花了很多時長鑽研他們的一些約定,而我並不想花時長去鑽研那些將來可能發生變化的東西,我希望我能用一個更簡略的途徑把我的事情做優秀,我只關懷我的項目標速度、代碼的大小、運行的效率、維護的複雜度,所以我也建議和我交流的人能用一種通俗的方法來實現我們的功能,也不必過多的考慮我的程序8位單片機上能夠16位單片機上也能夠用,我們的系統要經常升級,但不要輕易去增加硬體老本。當然假如你有精力去鑽研那些約定,也沒什麼,只有你樂意。

好了,上面三條,獨有第二條是我要說的關鍵,其他兩條,個人能夠依據自己的詳細情況來尋找一些快速實用的方法。其實說來我們把父對象用函數指針來代替實體函數,這完全是一種思想,我們並沒有使用複雜的語法,而是從管理上把對象別離開來,讓不同的人做不同的事情,比較容易。但是,同時我們又不能讓這種中間環節影響我們程序的效率,所以我們完全能夠自己尋求一些方法,而不必去循規蹈矩。我這樣說興許會遭到一些非議,但是我能夠告訴大家,計算機語言這門學科,自身就是一門人造科學,沒有最好獨有更好,我們沒必要完全依照別人的思維去開展設計思想,所以也不要拿著人家的一些約定來引以為榮,那些東西,獨有先學後學的問題,沒有水平的差異;我們應該更注意的是,人家創造了工具,我們就用工具再來創造世界!假如你停留在觀賞工具的層次上,那就是沒所鳥事了!

本帖實質上只說了一個轉換,兩條建議,這都不是詳細的程序,而是思想。我想強調的,也不是格式,而是思想。下帖將再回到對象上,和大家探討一下對象自身的組織問題,假如對象的層次關係、對象的創建、對象的書寫等等,我也希望有人能有一些更好的方法回到帖子裡,我們互相進修,共有提高。

七、面向對象思想的層次關係

前面的思想衍變過程已經說了很久了,面向對象的思想也就到了瓜熟蒂落呼之欲出的境界了。

相已經足夠說清楚我們KeilC中如何用語言來組織我們的顯示器對dispdisp是一個抽象的對象,它只是一種聯絡,完成對所有子對d000d001d002到最d255的歸納概括並提供一組被調用者所使用的功能接口。這些功能接口正是上貼所提到的函數指針。而詳細的功能實現及不同顯示對象對數據構造的要求,我們都能夠交給子對象設計工程師自己去決定。

很顯然,大家在這套方案詳細的程序設計過程中,最主要的精力還是要放在自己做自己的問題上,多思考如何把事情做得更漂亮,而不必在代碼編寫時黏糊不清。父對象設計者必需要完成總體方案的設計,抽象思維多而詳細工作量少,子對象的設計者則恰恰相反,只須要多考慮考慮詳細的設計,而不必去擔憂別人是怎麼用自己的東西。

很顯然,作為總體設計者,必需要嚴格考慮這中間的數據替換關係,由於我們沒有作業系統,所以對於可用的內存資源的使用法則,直接關係到我們整個系統的成敗,混亂使用常常會導致系統崩潰,相對獨立的代碼則在編譯過程中KeilC直接安排好了,我們不須要去考慮它們對程序的影響。

例子中的顯存大小及顯存的位置都是我們方案成敗的關鍵。我們都知KeilC對單片機內存劃分了四種,dataidatapdataxdata四種,各種存儲器的大小與特點都決定著我們代碼的運行效果,我們既要考慮信息所須要的空間,又要考慮單片機處理時足夠到達我們的視覺要求。在這個例子中,我覺得我們能夠選xdata來作為顯存。為什麼呢?

由於我覺得只有我們處理得當,我們的單片機完全能夠克服處理速度上的缺少陷,所以我們能夠優先滿足信息量的要求,提供足夠多的空間來實現我們想要的功能。

提速的方式有很多,假如:選擇一些性能優越的新型單片機,傳統的12T的,此時6T的,也1T的,這讓很多指令都會有更高的效率;適當的提高晶振頻次;選擇更科學的算法;等等。

到目前為止,根本上能夠去構造我們的對象了,假如你有興趣,你能夠使#define進行一些偽碼定義,把我們的對象定義得更美觀一點,更接C++一些,不過我要說的是:我們這裡沒有嚴格的類定義,所以定義時類與對象經常是沒有界限的。

文章來源: https://twgreatdaily.com/zh-sg/b77e69c15ceb6467f4519cdd7270e502.html