這兩天在用AVR單片機做項目,這次是邊做邊深入學(xué),盡量將以前忽略的知識用上,比如指針、文件包含、條件編譯、變量作用域等一一調(diào)研清楚。收獲不少,再次體會到學(xué)習(xí)的過程中沒有無用的地方。用的越深入,才發(fā)現(xiàn)學(xué)時以為沒用的東西都是最有用的,因為都被忽略了。幸虧學(xué)習(xí)時候的最重要的《c程序設(shè)計》還在,全能解決,所以遇到問題就問“譚浩強”。如果不能解決,就找編譯器的問題。這是這段時間總結(jié)的經(jīng)驗。
一、函數(shù)指針
1.int (*func)(void)--基本函數(shù)指針變量定義方式,變量func是一個指向返回值為int,沒有參數(shù)的函數(shù)指針,這與其他變量定義有點區(qū)別,一般是把變量名放最后,如int a,而函數(shù)指針比較不直觀,
2.typedef int(*ftype)(void); ftype func;--類型重定義方式,先定義一種新的類型ftype,它是一種指針類型,這種類型專門指向返回值為int,沒有參數(shù)的函數(shù),再通過該類型定義一個具體的變量func
3.func=functionName--函數(shù)指針賦值,functionName為已經(jīng)定義的函數(shù)的函數(shù)名
4.(*func)()--通過函數(shù)指針調(diào)用函數(shù),也可以直接調(diào)用func(),
5.函數(shù)指針的一般作用:剛學(xué)函數(shù)指針的時候感覺沒啥用,在接觸操作系統(tǒng)以及一些消息、事件驅(qū)動的機制以后意識到它的意義。簡單的講如果希望在發(fā)生某種事件、中斷等情況下不希望主程序再去查看、掃描做判斷,而是自動執(zhí)行某個功能函數(shù)時候,可以使用回調(diào)函數(shù)實現(xiàn)。如每次按下鍵盤都讓某個燈亮,可以將亮燈的函數(shù)指針傳遞給按鍵中斷函數(shù),由中斷內(nèi)部自動調(diào)用。這是比較簡單的應(yīng)用,肯定可以直接用一個函數(shù)代替,但是一旦希望在執(zhí)行過程中修改該消息、中斷響應(yīng)的操作的話,用函數(shù)指針就方便多了。在正常的程序執(zhí)行過程中,調(diào)用方一般都是遇到函數(shù)就立即執(zhí)行。而回調(diào)函數(shù)則是調(diào)用方通過函數(shù)指針的形式把函數(shù)儲存起來。這樣在合適的實際調(diào)用方就可以通過這個函數(shù)指針執(zhí)行某個功能?;卣{(diào)函數(shù)可以說是一種訂閱、分發(fā)的機制。被調(diào)用方可以通過訂閱的形式將自己的處理函數(shù)以函數(shù)指針的形式交給調(diào)用方。當(dāng)調(diào)用方需要執(zhí)行這個回調(diào)函數(shù)的時候,就會通過分發(fā)的形式回調(diào)被調(diào)用方。回調(diào)函數(shù)的機制可以說無處不在,比如Channie Liu 所說的MFC消息機制,再比如HOOK,等等都是通過回調(diào)函數(shù)機制來執(zhí)行的。但是回調(diào)函數(shù)并不是系統(tǒng)獨有的機制。你完全可以在自己的程序中通過函數(shù)指針來實現(xiàn)一套回調(diào)函數(shù)。還有回調(diào)函數(shù)并不是面向?qū)ο缶幊?,在某些情況下可以使用觀察者模式來代替它。在.net中已經(jīng)使用是事件的方式代替了回調(diào)函數(shù)來實現(xiàn)消息相應(yīng)。
6.avr編程中要注意不是所有的編譯器都能很好的支持函數(shù)指針,使用以及查資料發(fā)現(xiàn)ICCAVR7.1某個版本在編譯后的代碼進行仿真發(fā)現(xiàn)程序總是跑飛,查看匯編發(fā)現(xiàn)生成一個EIJMP指令(擴展間接跳轉(zhuǎn)指令),后來嘗試換用最新版7.21A版本,發(fā)現(xiàn)這個編譯后就沒這個指令,可以正常仿真了。同時CodeVision也有這個版本的問題。
二、頭文件包含
以前一直以為對這個很理解,沒太在意,雖然也犯了幾次錯誤,但都沒有深究,這次又復(fù)習(xí)了一下“譚浩強”發(fā)現(xiàn)還是有根本的誤解的。在對頭文件理解之前需要理解編譯過程。編譯是以源文件為單位,也就是*.c或*.c++等,生成的目標(biāo)文件也是與源文件對應(yīng)的。而頭文件的作用是把可能公用的聲明放在一起,被源文件包含后,在編譯的過程中可以理解為直接加在源文件的內(nèi)部,而且添加的順序與源文件的include語句順序?qū)?yīng)。所以如果A.c包含B.h,而B.h又用到C.h內(nèi)容,但B.h本身沒包含則在A.c中要注意先包含C.h再包含B.h。另外就是變量的定義,即需要申請內(nèi)存占用內(nèi)存這樣的語句不能放在可能被多處包含的頭文件中,這樣會引發(fā)多次定義的錯誤。這個問題我一直以為在頭文件的開始和結(jié)尾有個#ifndef語句就萬事大吉了,后來才知道不是那么回事。#ifndef語句只是為了在A包含B,A包含C,B包含C這樣情況下阻止C被兩次包含,而如果還有一個D也包含C的話那么A與D兩個源文件編譯后的代碼中都會對C中的變量進行定義的,從而造成變量多次定義。
三、條件編譯
這個沒太多用過,直到最近想把單片機的程序?qū)懙母ㄓ靡恍M量能抽象出來一些公用的函數(shù),可以在各個硬件平臺使用,免去不少的重復(fù)工作。如串口通訊在51下,在avr下都寫過,但是每次都是現(xiàn)用現(xiàn)寫,現(xiàn)在想盡量把每種功能硬件相關(guān)部分提煉出來,并壓縮到最小。這里面就需要用到條件編譯對各種平臺進行判斷,對每種功能進行控制。看過嵌入式linux內(nèi)核的應(yīng)該都發(fā)現(xiàn)這個特點了,那就是成篇的條件編譯。
四、變量作用域
我最常犯的就是全局變量與靜態(tài)變量的錯誤使用。具體可以看“譚浩強”,這里只說明一下常用的全局變量的使用。有時候希望一個公共變量能在各個源文件訪問,或者作為某種信號、開關(guān)使用。就需要在某個源文件中定義,然后在其他需要用到的地方使用extern關(guān)鍵字。如果需要用的地方太多,就在頭文件中使用extern聲明該變量,在其他源文件中包含該頭文件即可。