




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
第一章硬件基礎(chǔ)
硬件是軟件的運(yùn)行平臺(tái),沒(méi)有硬件的支撐軟件也將不復(fù)存在。您能想象
沒(méi)有顯示器軟件將如何顯示圖形,沒(méi)有CPU軟件將如何運(yùn)行嗎?反正我想象不
到!但是如果把問(wèn)題反過(guò)來(lái)問(wèn)就問(wèn)到本質(zhì)了,軟件運(yùn)行需要哪些硬件支持呢?看
圖1.1:
CPU
RAM
ROM
輸出設(shè)備
輸入設(shè)備
存儲(chǔ)設(shè)備
圖L1系統(tǒng)結(jié)構(gòu)框圖
我們拋開(kāi)硬件的什么電器特性等等,去蕪存菁,就是上面的這個(gè)圖了。
如果程序要運(yùn)行沒(méi)有CPU是不行的,CPU要快速的交換數(shù)據(jù),沒(méi)有RAM也是不
行的。因此無(wú)論任何系統(tǒng),CPU和RAM都是必不可少的。您一定會(huì)提醒我ROM不
也是不變的嗎?這種說(shuō)法不完全對(duì),因?yàn)樵赑C系統(tǒng)和嵌入式系統(tǒng)之間ROM的作
用是不一樣的。在PC系統(tǒng)中ROM就是那個(gè)BIOS芯片,是用來(lái)提供系統(tǒng)的啟動(dòng)代
碼和基本的輸入輸出功能的;而在嵌入式系統(tǒng)中,ROM存儲(chǔ)了全部的代碼,它
已經(jīng)將PC中白勺BIOS和硬盤(pán)的與代碼相關(guān)的功能混合在一起了。
設(shè)備PC系統(tǒng)典型硬件設(shè)備嵌入式系統(tǒng)典型硬件設(shè)備
CPU任何CPU任何CPU
RAM任何RAM任何RAM
ROMBIOS芯片F(xiàn)lash芯片
存儲(chǔ)設(shè)備硬盤(pán)Flash芯片
輸入設(shè)備鍵盤(pán)鍵盤(pán)
輸出設(shè)備顯示卡+顯示器LCD顯示屏
PC的ROM——BIOS芯片可以采用Flash芯片,在這里之所以不寫(xiě)成Flash
芯片是因?yàn)锽IOS的作用和嵌入式系統(tǒng)的Flash作用不大一樣,使用BIOS以示區(qū)
分。
1.1CPU和RAM
從軟件觀點(diǎn)來(lái)講,任何CPU和RAM都可以應(yīng)用于各種系統(tǒng)中,不存在明顯的
區(qū)別,只要CPU可以執(zhí)行指令控制設(shè)備就可以了。但是考慮到耗電以及體積(嵌
入式設(shè)備通常要求耗電低、體積小)等問(wèn)題,嵌入式系統(tǒng)就發(fā)展出了專(zhuān)用的CPU
芯片。當(dāng)前應(yīng)用最廣泛的是ARMCPU。ARMCPU是由英國(guó)的ARM公司設(shè)計(jì)的,由
于其執(zhí)行效率高,體積小,耗電少等特點(diǎn)被廣泛應(yīng)用于嵌入式系統(tǒng)。由于嵌入式
系統(tǒng)要求高集成度,通常不會(huì)存在單獨(dú)的CPU芯片,而是將CPU和很多的外圍
電路集成到一起,做成一塊芯片,因此ARM采用授權(quán)的方式提供內(nèi)核芯片設(shè)計(jì),
以便于使用者進(jìn)行芯片的集成。
CPU按照次執(zhí)行指令的數(shù)據(jù)帶寬可以分為16位、32位、64位等。32位CPU
一次只能處理32位,也就是4個(gè)字節(jié)的數(shù)據(jù);而64位CPU一次就能處理64
位即8個(gè)字節(jié)的數(shù)據(jù)。如果我們將總長(zhǎng)128位的指令分別按照16位、32位、64
位為單位進(jìn)行編輯的話:舊的16位CPU(如Intel80286CPU)需要8個(gè)指令,
32位的CPU需要4個(gè)指令,而64位CPU則只要兩個(gè)指令。顯然,在工作頻率相
同的情況下,64位CPU的處理速度比16位、32位的更快。
除了運(yùn)算能力之外,與32位CPU相比,64位CPU的優(yōu)勢(shì)還體現(xiàn)在系統(tǒng)對(duì)內(nèi)
存的控制上。由于地址使用的是特殊的整數(shù),而64位CPU的一個(gè)ALU(算術(shù)邏
輯運(yùn)算器)和寄存器可以處理更大的整數(shù),也就是更大的地址。傳統(tǒng)32位CPU
的尋址空間最大為4GB,使得很多需要大容量?jī)?nèi)存的大規(guī)模的數(shù)據(jù)處理程序在
這時(shí)都會(huì)顯得捉襟見(jiàn)肘,形成了運(yùn)行效率的瓶頸。而64位的處理器在理論上則
可以達(dá)到1800萬(wàn)個(gè)TB(1TB=1O24GB),將能夠徹底解決32位計(jì)算系統(tǒng)所遇到
的瓶頸現(xiàn)象。當(dāng)然64位尋址空間也有一定的缺點(diǎn):內(nèi)存地址值隨著位數(shù)的增加
而變?yōu)樵瓉?lái)的兩倍,這樣內(nèi)存地址將在緩存中占用更多的空間,其他有用的數(shù)據(jù)
就無(wú)法載入緩存,從而引起了整體性能一定程度的下降。
在進(jìn)行系統(tǒng)設(shè)計(jì)時(shí),會(huì)根據(jù)不同尋址能力的CPU來(lái)進(jìn)行尋址空間的分配。由
于CPU都是通過(guò)設(shè)備的寄存器(這個(gè)寄存器可以理解為設(shè)備本身帶的RAM)來(lái)
控制設(shè)備的,因此地址空間的劃分就顯得十分重要。例如,一個(gè)具有32位尋址
能力的CPU不可能講全部的地址空間都分配給RAM,好比PC系統(tǒng)需要為BIOS
分配存儲(chǔ)空間等。也就是說(shuō)只要是需要CPU直接控制的外部設(shè)備都需要為其分配
CPU地址空間。
RAM就是可以隨機(jī)訪問(wèn),快速讀寫(xiě)的存儲(chǔ)器。CPU可以直接從RAM中取得數(shù)
據(jù)(CPU可以從所有分配了地址空間的設(shè)備寄存器中取得數(shù)據(jù))或代碼指令,因
此RAM的訪問(wèn)速度將直接影響系統(tǒng)的性能。
1.2ROM存儲(chǔ)芯片
ROM是每個(gè)計(jì)算機(jī)系統(tǒng)必不可少的,但是其實(shí)現(xiàn)的方式卻不盡相同。在
我們熟悉的PC系統(tǒng)中,ROM是一個(gè)稱(chēng)作BIOS(BaseInput&OutputSystem)
的芯片。CPU上電時(shí)會(huì)從ROM中讀取指令,因此沒(méi)有ROM的系統(tǒng)是不能夠運(yùn)行的,
因?yàn)槿绻麤](méi)有ROM,CPU將無(wú)法獲得起始的執(zhí)行指令。在PC系統(tǒng)中BIOS的作用
除了提供起始指令以外,還會(huì)掃描硬件設(shè)備并初始化主板(MainBoard)上的硬
件接口。由于PC上的接口都遵循著一組通用的協(xié)議,因此BIOS就可以實(shí)現(xiàn)所有
硬件接口的驅(qū)動(dòng)(如USB接口、顯示卡、鍵盤(pán)和鼠標(biāo)等)和硬件的數(shù)據(jù)輸入輸
出功能,這也是BIOS(基本輸入輸出系統(tǒng))名稱(chēng)的由來(lái)了。在BIOS控制的硬件
接口中也包含了硬盤(pán)的控制接口,在BIOS初始化完成后就會(huì)到硬盤(pán)主分區(qū)上查
找啟動(dòng)文件(注3),后面的事情就交給PC的操作系統(tǒng)了。這個(gè)過(guò)程請(qǐng)看圖1.2。
通常,在硬盤(pán)內(nèi)有一個(gè)主引導(dǎo)記錄區(qū),在安裝操作系統(tǒng)的時(shí)候由操作系
統(tǒng)寫(xiě)入Boot程序,BIOS就是取出這段程序然后執(zhí)行。由于Boot程序是由操作
系統(tǒng)寫(xiě)入的,因此從這個(gè)Boot程序開(kāi)始,系統(tǒng)的運(yùn)行權(quán)限就交由操作系統(tǒng)來(lái)控
制了。以Windows2000操作系統(tǒng)為例,這段代碼區(qū)域執(zhí)行時(shí)會(huì)搜索名叫
“NTLDR”的系統(tǒng)文件。之所以分成Boot程序和NTLDR文件的原因是硬盤(pán)的Boot
Sector很小(只有466Bytes),不可能容納全部的啟動(dòng)程序。
圖L2只是一個(gè)啟動(dòng)的示意圖,在這里我并沒(méi)有詳細(xì)的列出每一個(gè)必須
的步驟,因?yàn)槲业哪康闹皇亲屛覀兡軌蛄私釨IOS硬件芯片在系統(tǒng)中的作用。
對(duì)比PC的BIOS,嵌入式系統(tǒng)由于軟件規(guī)模小,因此將引導(dǎo)代碼和操作
系統(tǒng)代碼全部放到了系統(tǒng)的Flash芯片中了。正如我們所知道的,PC機(jī)上大部
分的操作系統(tǒng)代碼全部放在硬盤(pán)上,然后從硬盤(pán)上將程序載入內(nèi)存執(zhí)行。而嵌入
式系統(tǒng)中目前大多數(shù)采用直接尋址的方式從NorFlash芯片中讀取代碼并執(zhí)行。
因此,實(shí)際上嵌入式系統(tǒng)簡(jiǎn)化了PC系統(tǒng)的設(shè)計(jì),將PC系統(tǒng)中的BIOS和硬盤(pán)代
碼全部集中到了一個(gè)Flash芯片上。因此BIOS雖然也可以使用Flash芯片,但
是相對(duì)于嵌入式系統(tǒng)來(lái)說(shuō),他們的含義和作用卻不同。
BIOS自檢
顯卡BIOS檢測(cè)
CPU型號(hào)檢測(cè)、內(nèi)存測(cè)試
讀取硬盤(pán)啟動(dòng)扇區(qū)
獲取操作系統(tǒng)啟動(dòng)文件
圖1.2PC開(kāi)機(jī)流程
Flash芯片對(duì)于我們來(lái)說(shuō)并不陌生,那些可以更新BIOS程序的BIOS芯片也
是使用Flash芯片實(shí)現(xiàn)的,還有MP3用的SD卡等等也是Flash芯片。Flash芯
片是一種可以多次擦寫(xiě)的存儲(chǔ)芯片,廣泛的應(yīng)用于嵌入式系統(tǒng)。Flash的特點(diǎn)
是耗電低,容量大(相對(duì)于嵌入式系統(tǒng)而言),寫(xiě)入之前需要先擦除(因?yàn)镕lash
芯片的存儲(chǔ)單元只允許從1變到0)o當(dāng)前流行的分為NORFlash和NANDFlash。
NAND與NORFlash的區(qū)別主要有:
1、NANDFlash的空間比NorFlash大
2、NANDFlash的訪問(wèn)速度比NorFlash快
3、NANDFlash只有Page訪問(wèn)模式,NorFlash可以進(jìn)行Page和直接地址
訪問(wèn)(直接地址訪問(wèn)也就是CPU可以直接尋址,或者叫做隨機(jī)訪問(wèn))
4、NANDFlash允許有壞塊,但是NorFlash不能有壞塊
5、NANDFlash比NORFlash更加便宜
在嵌入式系統(tǒng)中,NOR和NAND都可以做為代碼區(qū)和文件系統(tǒng)區(qū)來(lái)使用。通
常情況下NOR和NAND做為嵌入式文件系統(tǒng)區(qū)的時(shí)候都使用Page模式。Page模
式允許一次讀取多個(gè)字節(jié),就像硬盤(pán)的最小讀寫(xiě)單位是扇區(qū)一樣,只不過(guò)Flash
的最小讀寫(xiě)單位叫做Page。Page模式下可以加快Flash的讀寫(xiě)速度。由于NAND
Flash只支持Page讀寫(xiě)模式,因此使用NANDFlash做為代碼區(qū)的時(shí)候需要外加
控制電路。當(dāng)前使用NAND做為代碼區(qū)正在成為一種流行的趨勢(shì)(因?yàn)镹ANDFlash
成本更低),主要的實(shí)現(xiàn)方式有兩種:一是添加仿真電路使得NANDFlash可以
支持隨機(jī)訪問(wèn);二是增加一個(gè)類(lèi)似硬盤(pán)的引導(dǎo)區(qū)(通常是第一個(gè)Page),系統(tǒng)
啟動(dòng)的時(shí)候使用引導(dǎo)區(qū)的代碼將全部NAND中的代碼復(fù)制到RAM中執(zhí)行。
當(dāng)然,可以設(shè)想隨著將來(lái)嵌入式操作系統(tǒng)的發(fā)展,動(dòng)態(tài)載入內(nèi)存的形式也許
就會(huì)出現(xiàn)了,但是目前嵌入式系統(tǒng)仍然沒(méi)有發(fā)展到這個(gè)地步。更進(jìn)一步,當(dāng)前應(yīng)
用于嵌入式系統(tǒng)的微型硬盤(pán)也已經(jīng)出現(xiàn)了,或許更為復(fù)雜的操作系統(tǒng)也可以應(yīng)用
在嵌入式系統(tǒng)上了。
在這里我們主要介紹的ROM存儲(chǔ)芯片是Flash,嚴(yán)格意義上說(shuō)Flash并不能
稱(chēng)作ROM,因?yàn)镽OM是只讀存儲(chǔ)器(ReadonlyMemory),而Flash是一種可讀
可寫(xiě)的芯片。但是由于ROM“一次成型、終生不變”的特點(diǎn),不便于升級(jí)換代,
現(xiàn)在正逐漸的被Flash芯片所取代,但是其功能性的稱(chēng)謂“ROM”還在為大家所
用。
在計(jì)算機(jī)系統(tǒng)中主要存在用戶數(shù)據(jù)、程序數(shù)據(jù)和代碼三種二進(jìn)制內(nèi)容。用戶
數(shù)據(jù)指用戶文件,程序數(shù)據(jù)是指程序運(yùn)行時(shí)需要修改或使用的非代碼內(nèi)容。下面
將就PC系統(tǒng)和嵌入式系統(tǒng)中的這三種二進(jìn)制內(nèi)容做一個(gè)比較,請(qǐng)看下表:
二進(jìn)制形PC系統(tǒng)嵌入式系統(tǒng)
態(tài)
用戶數(shù)據(jù)存儲(chǔ)在文件系統(tǒng)中,典型的設(shè)備存儲(chǔ)在文件系統(tǒng)中,典型的設(shè)備
是硬盤(pán)是Flash存儲(chǔ)芯片
程序數(shù)據(jù)可讀可寫(xiě)的數(shù)據(jù)存放在RAM中;可讀可寫(xiě)的數(shù)據(jù)存放在RAM中;
只讀數(shù)據(jù)存放在硬盤(pán)中,運(yùn)行時(shí)只讀數(shù)據(jù)存放在Flash中,與代
與代碼一起讀入RAM碼存儲(chǔ)在同一個(gè)區(qū)域
代碼存儲(chǔ)在文件系統(tǒng)中的文件里,運(yùn)如果存儲(chǔ)在NORFlash等可隨機(jī)
行時(shí)讀入RAM由CPU執(zhí)行訪問(wèn)的空間中則CPU直接在芯片
中取指令運(yùn)行:如果存儲(chǔ)在NAND
Flash等不能隨機(jī)訪問(wèn)的空間中
則需要讀入RAM中運(yùn)行
關(guān)于程序數(shù)據(jù)的詳細(xì)情況將在編譯器基礎(chǔ)?節(jié)詳細(xì)介紹。
1.3輸出設(shè)備
輸出設(shè)備有很多種,例如顯示器、打印機(jī),在這里我們主要講一下顯示
設(shè)備。
任何顯示設(shè)備都是點(diǎn)陣式的,至少目前是這樣。還記得初次看見(jiàn)電視里顯示
的人物時(shí)內(nèi)心的驚異,這個(gè)世界竟是如此神奇!后來(lái)知道了,如果我愿意,我
可以買(mǎi)640*480個(gè)燈泡,組成一個(gè)640*480的方陣,然后控制每個(gè)燈泡的亮滅,
我也可以顯示一個(gè)人物。終于懂了,原來(lái)任何的顯示設(shè)備都是基于這個(gè)原理實(shí)
現(xiàn)的。我想關(guān)于這一點(diǎn)我沒(méi)有必要再多說(shuō)什么了,畢竟萬(wàn)變不離其宗嘛。
當(dāng)前流行的顯示設(shè)備有CRT顯示器(也就是電腦上很大個(gè)頭的那種顯示
器),LCD(液晶)等(雖然我在大學(xué)實(shí)驗(yàn)室里用的可以顯示數(shù)字的LED燈也是
顯示設(shè)備,但是他太簡(jiǎn)單了)。CRT顯示器經(jīng)歷了從球形到純平的物理演變,LCD
則經(jīng)歷了從黑白到彩色的變化。按照顯示器每個(gè)點(diǎn)能夠顯示的顏色數(shù)目可以分
為黑白兩色、灰度、8位色、16位色、24位真彩色等顯示設(shè)備。這個(gè)顏色數(shù)目
就是所謂的色深(ColorDepth),色深越大,每個(gè)點(diǎn)能夠表達(dá)的顏色數(shù)就越多,
這個(gè)點(diǎn)就是“象素”。
在嵌入式系統(tǒng)中主要使用LCD的顯示設(shè)備,LCD會(huì)集成一個(gè)顯示的存儲(chǔ)
空間,在這個(gè)空間中存儲(chǔ)了對(duì)應(yīng)的每個(gè)象素的值。例如16位色的LCD每個(gè)象素
需要由2個(gè)字節(jié)來(lái)表示,如果顯示屏幕的大小是100*100,那么就需要2*100*100
=20000個(gè)字節(jié)的存儲(chǔ)空間。程序正是通過(guò)更新這個(gè)存儲(chǔ)空間中的內(nèi)容來(lái)控制LCD
的顯示。
衡量LCD顯示效果的還有象素間距,象素間距越小,畫(huà)面就越細(xì)膩,顯示效
果越好。舉個(gè)極端的例子,如果一個(gè)象素是整個(gè)LCD那么大,那就只能看見(jiàn)一
個(gè)象素點(diǎn)的“燈泡”了,也就沒(méi)法顯示圖像了。通過(guò)像素個(gè)數(shù)和每個(gè)點(diǎn)的大小就
可以換算出顯示屏的大小了,例如常用的xx英寸大小的屏幕等等。
1.4輸入設(shè)備
輸入設(shè)備也有很多種,最典型的是鍵盤(pán)和觸摸屏。在這一節(jié)里,我將簡(jiǎn)
單的介紹一下它們的實(shí)現(xiàn)原理。
鍵盤(pán)通常是一個(gè)矩陣式的電路,當(dāng)按鍵按下的時(shí)候,接通電路產(chǎn)生信號(hào),
如圖L3:
1
2
3
A
B
C
圖1.3鍵盤(pán)原理
圖1.3是一個(gè)簡(jiǎn)版的鍵盤(pán)原理圖,圖中任何交叉點(diǎn)的橫向和縱向都未連
通,并假設(shè)只要交叉點(diǎn)連通,則相應(yīng)的行和列就可以連通并發(fā)生狀態(tài)改變。其
中1、2、3和A、B、C連接在控制芯片上,通過(guò)掃描行和列,確定行的A、B、C
是否連通,再掃描列1、2、3是否連通,這樣就可以唯一?確定一個(gè)點(diǎn)是否按下。
千萬(wàn)注意,這只是一個(gè)示意圖,并不是真正的鍵盤(pán)原理圖。
觸摸屏是近來(lái)應(yīng)用越來(lái)越多的輸入器件。典型觸摸屏的工作部分-?般由
三部分組成:兩層透明的阻性導(dǎo)體層、兩層導(dǎo)體之間的隔離層、電極。阻性導(dǎo)
體層選用阻性材料,如錮錫氧化物(ITO)涂在襯底上構(gòu)成,上層襯底用塑料,
下層襯底用玻璃。隔離層為粘性絕緣液體材料?,如聚脂薄膜。電極選用導(dǎo)電性
能極好的材料(如銀粉墨)構(gòu)成。
觸摸屏在工作時(shí),上下導(dǎo)體層相當(dāng)于電阻網(wǎng)絡(luò)。當(dāng)某一層電極加上電壓
時(shí),會(huì)在該網(wǎng)絡(luò)上形成電壓梯度。如有外力使得上下兩層在某一點(diǎn)接觸,則在
電極未加電壓的另一層可以測(cè)得接觸點(diǎn)處的電壓,從而知道接觸點(diǎn)處的坐標(biāo)。比
如,在頂層的電極(X+,X—)上加上電壓,則在頂層導(dǎo)體層上形成電壓梯度,當(dāng)
有外力使得上下兩層在某一點(diǎn)接觸,在底層就可以測(cè)得接觸點(diǎn)處的電壓,再根據(jù)
該電壓與電極(X+)之間的距離關(guān)系,知道該處的X坐標(biāo)。然后,將電壓切換到
底層電極(Y+,Y-)上,并在頂層測(cè)量接觸點(diǎn)處的電壓,從而知道Y坐標(biāo)。通常
有專(zhuān)門(mén)的控制芯片。很顯然,觸摸屏的控制芯片要完成兩件事情:一是完成電
極電壓的切換;二是采集接觸點(diǎn)處的電壓值(即A/D)。雖然還有其他的實(shí)現(xiàn)方
法,我就不贅述了,因?yàn)楸緯?shū)的目的不是講解硬件的原理,而僅僅是讓我們能
夠基本了解它們。
1.5小結(jié)
在這一章里介紹了各種硬件的特性和原理,其中包括了最小系統(tǒng)各種組
件的介紹,為的是防止我們見(jiàn)到這些東西的時(shí)候會(huì)一頭霧水,不知來(lái)由。只要我
們見(jiàn)到這些硬件不再感到神秘,那么這個(gè)基礎(chǔ)就算打好了。
思考題
在PC計(jì)算機(jī)系統(tǒng)中硬盤(pán)、BIOS、RAM和嵌入式系統(tǒng)中的文件Flash、程
序Flash、RAM之間有什么區(qū)別?它們?cè)谙到y(tǒng)中的作用分別是什么?
第二章軟件基礎(chǔ)
我們正在向我們的軟件王國(guó)進(jìn)發(fā),千萬(wàn)別急,在這條路上“枯燥”是我
們最大的敵人,不知有多少人在它的面前臣服,但愿您不是其中之一。或許您
覺(jué)得應(yīng)該獲得一些鼓勵(lì),寫(xiě)一些代碼,能夠看見(jiàn)一些諸如“Hello,World!”之
類(lèi)的信息。非常幸運(yùn),從這里開(kāi)始您將能夠看見(jiàn)它們了,我會(huì)將部分內(nèi)容使用源
程序的方式向您講解。在這本書(shū)里,我將使用VisualStudio.Net2003的開(kāi)發(fā)
環(huán)境來(lái)執(zhí)行這些測(cè)試程序。建立測(cè)試工程的步驟如下:
1、首先打開(kāi)VisualStudio.Net2003開(kāi)發(fā)環(huán)境,選擇新建項(xiàng)目。如圖
2.1,選擇項(xiàng)目類(lèi)型VisualC++項(xiàng)目/Win32控制臺(tái)項(xiàng)目,選擇路徑,填寫(xiě)測(cè)試程
序名稱(chēng)是Testi,點(diǎn)擊確定按鈕。
2、在Win32應(yīng)用程序向?qū)е薪邮苣J(rèn)設(shè)置,點(diǎn)擊完成按鈕。如圖2.2。
圖2.1建立測(cè)試程序
這樣就完成了一個(gè)測(cè)試用的應(yīng)用程序,在以后的測(cè)試程序中我將不再重
復(fù)這個(gè)步驟。由于我們主要是在C語(yǔ)言的基礎(chǔ)上講解,因此在這里創(chuàng)建的是
Win32控制臺(tái)應(yīng)用程序,它使用Windows的命令窗口顯示輸出結(jié)果。不過(guò)請(qǐng)注意,
雖然它使用Windows的命令窗口,但是它是一個(gè)Win32的應(yīng)用程序,而不是DOS
應(yīng)用程序。
在Windows環(huán)境下,應(yīng)用程序分為控制臺(tái)(Console)應(yīng)用程序和窗口應(yīng)
用程序。控制臺(tái)應(yīng)用程序不顯示窗口,其表現(xiàn)形式類(lèi)似于DOS環(huán)境下的應(yīng)用程
序,但是由于它可以調(diào)用Windows的API來(lái)實(shí)現(xiàn),所以它是一個(gè)Windows的應(yīng)用
程序而不是DOS應(yīng)用程序。同時(shí)控制臺(tái)應(yīng)用程序可以使用標(biāo)準(zhǔn)C/C++的庫(kù),這
樣Windows下的控制臺(tái)應(yīng)用程序就和DOS環(huán)境下的C/C++十分的相似了。
在這一部分我們將通過(guò)實(shí)例來(lái)演示一些C語(yǔ)言中較比令人“眩暈”的話
題,期望能夠通過(guò)這些實(shí)例讓您弄明白這些問(wèn)題的本質(zhì)。由于本書(shū)是建立在您
已經(jīng)有一定的C語(yǔ)言基礎(chǔ)之上的,因此主要是針對(duì)C語(yǔ)言中一些較難理解的概念
進(jìn)行講解,畢竟我們不是一本專(zhuān)門(mén)講解C語(yǔ)言的書(shū)籍。在本部分主要講解的內(nèi)
容是指針、結(jié)構(gòu)體、預(yù)處理和函數(shù),因?yàn)樵谖覀兘酉聛?lái)的行程中必須要充分的理
解它們才能繼續(xù)前進(jìn)。
圖2.2Win32應(yīng)用程序向?qū)?/p>
2.1重溫C語(yǔ)言的指針
指針是一個(gè)精靈,以至于在我們剛剛接觸它的時(shí)候有點(diǎn)不知所措,甚至
有些人懷著敬畏的心情而決心遠(yuǎn)離它!不過(guò),當(dāng)我們掌握了它的時(shí)候,就會(huì)發(fā)
現(xiàn)它能讓我們隨心所欲。盡管我們?nèi)匀幻媾R著使用不當(dāng)所帶來(lái)的巨大風(fēng)險(xiǎn),但是
我還是會(huì)義無(wú)反顧的告訴您——一定要使用它。之所以我會(huì)在這里向您發(fā)出這
樣的號(hào)召,絕對(duì)不是因?yàn)槲覍?duì)指針的個(gè)人情感,我和指針也是非親非故,只不過(guò)
是因?yàn)橥高^(guò)它我們不但可以編寫(xiě)出靈活的程序,而且可以窺探到程序的真正世
界——二進(jìn)制世界的秘密。這就讓我們開(kāi)始擁抱它吧!
2.1.1指針的本質(zhì)
指針的本質(zhì)是存儲(chǔ)它所指向存儲(chǔ)空間地址的變量,下面將通過(guò)一個(gè)測(cè)試
程序來(lái)開(kāi)始征服它的旅程。打開(kāi)測(cè)試工程Testi,在Testi.cpp中添加如下代碼:
#include“stdafx.h"
int_tmain(intargc,_TCHAR*argv[1)
(
int?pointer;
intnNumber=100;
pointer=&nNumber;
printf(,z&pointer=0x%xpointer=0x%x*pointer=%d
\n〃,&pointer,pointer,*pointer);
return0;
在這段代碼中,我們定義了一個(gè)int型指針pointer和一個(gè)int變量
nNumber,然后讓pointer指向nNumber。編譯、運(yùn)行生成可執(zhí)行文件,可以看
至I」輸出的&pointer、pointer和*pointer的值如下:
&pointer=0xl2fed4
pointer=0xl2fec8
*pointer=100
"pointer二100是我們知道的,也就是指針指向的內(nèi)存地址的內(nèi)容,我們?cè)?/p>
程序中將pointer指針指向nNumber的地址,那么與nNumber的值相等就不足為
奇了。pointer=1244872這個(gè)值就是指針指向的內(nèi)存地址,是當(dāng)前函數(shù)運(yùn)行中
存儲(chǔ)nNumber的地址。&pointer就是這個(gè)指針變量的地址,由于我們定義了一
個(gè)指針變量,因此,在函數(shù)運(yùn)行時(shí)將會(huì)為這個(gè)變量分配一個(gè)存儲(chǔ)空間,因此這個(gè)
值是有意義的,而且它與pointer的值十分相近。我們可以這樣理解指針,它
是一個(gè)變量(因?yàn)橹羔樢残枰臻g存儲(chǔ)它所指向的地址),這個(gè)變量的值就是一
個(gè)內(nèi)存空間的地址。示意圖如下:
100
?
?
0xl2fec8
0xl2fec8
?
?
0xl2fed4
pointer
nNumber
圖2.3指針示意圖
在上面的代碼中,&pointer是一個(gè)指向指針的指針,這樣的稱(chēng)呼實(shí)在是過(guò)于
繁瑣了,我們就稱(chēng)它為“二重指針”吧。順延的,如果是指向(指針的指針)的
指針我們就叫它“三重指針”。在C語(yǔ)言中,二重指針是一個(gè)非常有用的東西,
很多高階的C語(yǔ)言應(yīng)用都會(huì)使用到它。二重指針的主要作用是做為參數(shù)為一個(gè)
指針變量賦值,在后面的章節(jié)中我們還會(huì)經(jīng)常使用到它。
對(duì)上面的圖2.3作一個(gè)說(shuō)明,pointer和nNumber分別位于兩個(gè)不同的內(nèi)存
區(qū)域,nNumber中存儲(chǔ)的值是100,這是程序中指定的;pointer內(nèi)存儲(chǔ)的值是
0xl2fec8,這個(gè)值正好是nNumber所在的內(nèi)存地址;圖左邊的0xl2fed4是存儲(chǔ)
pointer值的指針變量的地址,我們可以通過(guò)定義一個(gè)二重指針獲得它的內(nèi)容,
在本程序中我們通過(guò)&pointer來(lái)獲得它的內(nèi)容。
從本質(zhì)上來(lái)說(shuō),指針、二重指針、三重指針等等,都是一樣的,從代碼的層
次(我們將在“編譯器基礎(chǔ)”部分詳細(xì)講解軟件的層次問(wèn)題)來(lái)講都是一個(gè)“地
址”型的變量,只不過(guò)使用的時(shí)候會(huì)由編譯器做一下合法性的檢查,目的是為
了防止程序出現(xiàn)錯(cuò)誤;在二進(jìn)制層次來(lái)說(shuō)它們就更加沒(méi)有分別了,都是一個(gè)存
儲(chǔ)內(nèi)容的容器。打個(gè)比方,現(xiàn)在有兩個(gè)盒子,一個(gè)規(guī)定放置籃球,另一個(gè)規(guī)定
放置足球。體現(xiàn)在C語(yǔ)言中這個(gè)“規(guī)定”就是定義了兩個(gè)變量,一個(gè)是int型的,
另一個(gè)是指針型的;這兩個(gè)盒子就是兩塊存儲(chǔ)空間。在現(xiàn)實(shí)生活中“規(guī)定”是
由人來(lái)制定的,在程序中就是由c語(yǔ)言定義的。那么我違反規(guī)定放置籃球的盒子
我放置足球,放置足球的盒子我放置籃球。這是沒(méi)什么問(wèn)題的,不過(guò)會(huì)發(fā)生錯(cuò)誤,
因?yàn)闀?huì)讓一場(chǎng)足球比賽變成了踢籃球的比賽,讓一場(chǎng)籃球比賽變成了足球投籃的
比賽。同樣的對(duì)于指針和變量的關(guān)系也是,它們的內(nèi)容在二進(jìn)制層次是可以互換
的。但是我們?cè)谶@里要考慮兩件事情,一是這個(gè)錯(cuò)誤發(fā)生的前提條件,這個(gè)錯(cuò)誤
要發(fā)生必須是在球的管理者不知道錯(cuò)的情況下。如果球的管理者知道哪個(gè)盒子放
了籃球哪個(gè)盒子放了足球也不會(huì)出錯(cuò)。體現(xiàn)在程序上,就是增加強(qiáng)制類(lèi)型轉(zhuǎn)換
來(lái)告訴編譯器“我知道我要在int變量中放置一個(gè)指針的值”。否則編譯器將檢
查到這個(gè)錯(cuò)誤并報(bào)錯(cuò)。二是要考慮“盒子”容量的問(wèn)題,因?yàn)樽闱虮然@球小,
所以互換的時(shí)候不能讓籃球撐壞了足球盒子。在程序中也就是32位的指針變量
不能放到char型變量中去存儲(chǔ),因?yàn)檫@樣會(huì)丟掉地址信息。
綜上所述,雖然變量的二進(jìn)制本質(zhì)是一樣的,但是在代碼層次要精確控制變
量的類(lèi)型來(lái)避免錯(cuò)誤的發(fā)生。指針也是一個(gè)變量,一個(gè)32位的指針變量也可以
存儲(chǔ)在一個(gè)4字節(jié)的無(wú)符號(hào)整型變量里,前提是我們要知道我們是采用這種方式
來(lái)存儲(chǔ)它們的。
最后使用一個(gè)例子來(lái)做個(gè)演示,新建工程Test2,輸入如下代碼:
#include"stdafx.h"
int_tmain(intargc,_TCHAR*argv[])
{
intnNumber=100;
int"pointer=&nNumber;
unsignedintdwBox;
unsignedshortwBox;
//將指針的值分別賦給無(wú)符號(hào)整型變量
dwBox=(unsignedint)pointer;
wBox=(unsignedshort)pointer;//地址內(nèi)容將被裁減!!!
//輸出Box變量的值,注意wBox與dwBox之間的不同
printf(z/\vBox=0x%xdwBox=0x%x\nz/,wBox,dwBox);
//輸出Box變量指向地址的值,其中*((int*)dwBox)相當(dāng)于*pointer
//此時(shí)不能夠使用*((int*)wBox)輸出值,因?yàn)樗牡刂肥墙?jīng)過(guò)裁減的
printf('*dwBox=%d\n",*((int*)dwBox));
return0;
編譯后運(yùn)行,輸出如下結(jié)果:
wBox=0xfed4dwBox=0xl2fed4
*d\vBox=100
請(qǐng)仔細(xì)體會(huì)上面的代碼,它真正揭示了變量與指針之間微妙的關(guān)系,還有類(lèi)
型轉(zhuǎn)換時(shí)所發(fā)生的數(shù)據(jù)裁減。
2.1.2指針的增減
關(guān)于指針的增減是?個(gè)比較容易讓人迷惑的問(wèn)題,指針本身也是一個(gè)變量,
它里面存儲(chǔ)的是一個(gè)地址,那么這個(gè)變量的自增是將這個(gè)地址值加1嗎?自增操
作和直接加1的操作是?樣的嗎?
為了弄清這個(gè)迷惑,這里我們?cè)赥est2工程中增加一個(gè)Test2T的項(xiàng)目(請(qǐng)
設(shè)置Test2T為啟動(dòng)項(xiàng)目),然后輸入如下代碼:
Sinclude"stdafx.h〃
typedefstruct_Test2{
charTest2[100];
}Test2;
int_tmain(intargc,_TCHAR*argv[])
(
intnValue=1;
charcValue='A';
Test2sValue;
int*intPtr=&nValue;
char*charPtr=&cValue;
Test2*structPtr=&sValue;
printf(/zInt%dChar%d
Struct%d\n〃,(int)intPtr,(int)charPtr,(int)structPtr);
intPtr++;
charPtr++;
structPtr++;
printf(,zInt%dChar%d
Struct%d\n〃,(int)intPtr,(int)charPtr,(int)structPtr);
intPtr+=1;
charPtr+=1;
structPtr+=1;
printf(z,Int%dChar%d
Struct%d\n〃,(int)intPtr,(int)charPtr,(int)structPtr);
return0;
編譯運(yùn)行輸出的結(jié)果如下:
Int1244884Char1244875Struct1244764
Int1244888Char1244876Struct1244864
Int1244892Char1244877Struct1244964
從這個(gè)結(jié)果中,我們可以看出,指針的加減是與指針?biāo)傅淖兞款?lèi)型有
關(guān)的,它所增加的步數(shù)是所指變量的大小,而且指針的+1和++操作是一樣的。
那么void型的沒(méi)有類(lèi)型的指針怎么處理呢?答案是不處理,編譯器會(huì)通知我們
“未知大小”的錯(cuò)誤而終止程序的生成。
2.2重溫C語(yǔ)言的結(jié)構(gòu)
結(jié)構(gòu)是C語(yǔ)言中組織不同數(shù)據(jù)類(lèi)型的一種方式,它將不同的數(shù)據(jù)類(lèi)型組
織到一個(gè)相鄰的地址空間內(nèi)。每個(gè)定義的結(jié)構(gòu)體變量就是一個(gè)多種數(shù)據(jù)的存儲(chǔ)空
間。在這里我們主要講述兒個(gè)相對(duì)“高階”問(wèn)題,之所以在這里加上引號(hào),是因
為對(duì)于某些傳說(shuō)中的高手來(lái)說(shuō),這不過(guò)是小菜一碟。
2.2.1結(jié)構(gòu)體變量賦值
我們已經(jīng)習(xí)慣了為結(jié)構(gòu)體變量中的每個(gè)成員賦值,那么我們可以在兩個(gè)
結(jié)構(gòu)體變更之間直接使用“=”號(hào)賦值嗎?答案是肯定的,因?yàn)榫幾g器支持。例
如定義一個(gè)表示矩形的結(jié)構(gòu)體:
typedefstructRectangle{
intx;//左上角x坐標(biāo)
inty;//左上角y坐標(biāo)
intdx;//矩形寬度
intdy;//矩形高度
}Rectangle;
定義兩個(gè)矩形結(jié)構(gòu)體變量并賦值:
RectangleRecti,Rect2;
Recti,x=100;
Recti,y=100;
Recti,dx=100;
Recti,dy=100;
Rect2=Recti;
上面的賦值在C語(yǔ)言中是支持的,編譯器會(huì)將Rect2=Recti中的值轉(zhuǎn)
化成內(nèi)存拷貝的CPU指令來(lái)實(shí)現(xiàn)賦值操作。可以想象,對(duì)于簡(jiǎn)單的變量賦值,CPU
只需要執(zhí)行一個(gè)MOV指令就可以完成了,因此對(duì)于包含多個(gè)簡(jiǎn)單變量的結(jié)構(gòu)體
來(lái)說(shuō),使用多個(gè)循環(huán)的MOV指令就在情理之中了(在早期16位CPU中,如果對(duì)
一個(gè)32位的int變量執(zhí)行賦值操作都需要兩條MOV指令)。在使用這種賦值方
法的時(shí)候需要注意的是,在這個(gè)結(jié)構(gòu)體變量中最好不要有指針變量,因?yàn)橹羔樧?/p>
量可能在變量1中指向一個(gè)分配的內(nèi)存區(qū)域,當(dāng)變量2通過(guò)賦值操作獲得了這
個(gè)指針值的時(shí)候,有可能這個(gè)指針已經(jīng)釋放了,這樣就導(dǎo)致了空指針情況的發(fā)生,
后果是使用這個(gè)指針的時(shí)候?qū)?huì)導(dǎo)致程序崩潰。舉例說(shuō)明如下:
1、定義一個(gè)包含指針類(lèi)型的結(jié)構(gòu)體:
typedefstruct_TestStruct{
intnMember;
int*Ptr;
}TestStruct;
2、定義兩個(gè)這種類(lèi)型的變量并采用如下使用方法:
TestStructStructl,Struct2;
Struct1.nMember=100;
Structl.Ptr=(int*)malloc(15*sizeof(int));//分配15個(gè)int變量
的空間
//結(jié)構(gòu)體賦值
Struct2=Structl;//此時(shí)Struct2.Ptr與Structl.Ptr的值相等
if(Structl.Ptr)
(
free(Structl.Ptr);
Strictl.Ptr=NULL;
)
//這里有很復(fù)雜的處理,其中包含了malloc等操作
Struct2.Ptr[0]=2;//錯(cuò)誤的賦值操作,因?yàn)榇藭r(shí)Struct2.Ptr所指向
的內(nèi)容已經(jīng)被釋放了。
對(duì)于程序來(lái)說(shuō),修改一個(gè)已經(jīng)釋放了空間的內(nèi)存地址內(nèi)容是十分危險(xiǎn)的。當(dāng)
然,如果程序只有上面那么簡(jiǎn)單的話也不會(huì)出現(xiàn)什么嚴(yán)重的問(wèn)題,頂多只是非
法使用了一塊內(nèi)存區(qū)域;但是,如果中間含有復(fù)雜的處理,Struct2.Ptr[0]=2
將修改程序其他部分使用的內(nèi)存區(qū)域,那么這樣就可能會(huì)有莫名其妙的死機(jī)之類(lèi)
的事情發(fā)生了。由于其發(fā)生問(wèn)題的時(shí)間不固定,因此這類(lèi)問(wèn)題調(diào)試起來(lái)也十分的
困難。
2.2.2結(jié)構(gòu)體嵌套
在一個(gè)結(jié)構(gòu)體中可以聲明另一個(gè)結(jié)構(gòu)體,形成結(jié)構(gòu)體嵌套,如果將內(nèi)部
嵌套的子結(jié)構(gòu)體變量放在父結(jié)構(gòu)體的頂部,那么兩個(gè)結(jié)構(gòu)體之間還可以進(jìn)行類(lèi)型
互換。這個(gè)特性為實(shí)現(xiàn)C語(yǔ)言的數(shù)據(jù)封裝提供了一種方法。例如定義如下結(jié)構(gòu)體:
typedefstruct_Point{
intx;
inty;
}Point;
typedefstructRectangle{
PointLeftTop;
intdx;
intdy;
}Rectangle;
由于結(jié)構(gòu)體中Rectangle嵌套了結(jié)構(gòu)體Point,因此如果定義變量Rectangle
Recti則Recti可以轉(zhuǎn)化成Point使用。例如:
RectangleRecti={{100,100),100,100);
Rectangle*pRect=&Rectl;
Point*pPoint=(Point*)&Rectl;
~~如果需要訪問(wèn)這個(gè)矩形的左上角的x坐標(biāo)值可以有兩種方法:—
pRect->LeftTop.x或者pPoint->xoRectangle結(jié)構(gòu)體的內(nèi)存模式如圖2.4所示:
x
y
dx
dy
Rectangle
Point
圖2.4結(jié)構(gòu)體內(nèi)存模型
從圖2.4可以看出Point是嵌入在Rectangle中的,兩個(gè)結(jié)構(gòu)體的頂端地址
是一樣的,因此他們之間的指針可以互換,并且可以正常操作。
2.3重溫C語(yǔ)言的預(yù)處理
在編譯器編譯源文件之前,會(huì)首先通過(guò)預(yù)處理器來(lái)處理源程序中的預(yù)處
理選項(xiàng)。大名鼎鼎的宏也是預(yù)處理的一?種,預(yù)處理器采用直接替換的方式來(lái)處理
宏,也就是說(shuō)將宏定義的內(nèi)容替換到源文件中之后才開(kāi)始編譯,在每一個(gè)調(diào)用宏
的地方就有一個(gè)宏的替換體。例如定義如下宏:
#defineMAX(a,b)(a>b?a:b)
在程序中使用一次MAX(a,b)對(duì)應(yīng)的預(yù)處理器就把(a>b?a:b)替換
到相應(yīng)的位置,結(jié)果是增加了可執(zhí)行程序的大小(因?yàn)橛衝個(gè)重復(fù)的代碼段)。
如果定義的需要多行的宏,則使用“\”做為行與行之間的連接符,請(qǐng)看下
面的宏定義:
#defineINTI_RECT_VALUE(a,b)\
{\
a=0;\
b=0;\
)
注意最后一行就不再使用“\”了。
除了宏之外還有代碼的選擇編譯也是預(yù)處理器的主要功能之一。在一個(gè)大型
的軟件項(xiàng)目中,有許多功能需要根據(jù)不同的硬件平臺(tái)或者軟件用途來(lái)進(jìn)行選擇,
例如一個(gè)軟件的中文版和英文版,在發(fā)布的時(shí)候就需要使用定義的中文或英文標(biāo)
簽來(lái)決定。
C語(yǔ)言的預(yù)編譯器使用的關(guān)鍵詞和功能描述如下表:
關(guān)鍵詞功能描述
#define用來(lái)進(jìn)行宏和符號(hào)或常量的定義。
Sundef取消通過(guò)#define定義過(guò)的符號(hào)。
#if用來(lái)判斷預(yù)處理?xiàng)l件,需要#endif做為結(jié)束標(biāo)記o相對(duì)應(yīng)的#ifdef和
#ifdefined用來(lái)判斷符號(hào)是否定義;#ifndef和#if[defined()是
判斷符號(hào)是否未定義。
Seise#ifdef>ttifndef的條件分支語(yǔ)句
Sendif#ifdef、#ifndef的條件結(jié)束語(yǔ)句,只要有#if就需要有#endif
Serror無(wú)條件的向預(yù)處理器報(bào)錯(cuò)。通常用在#if…#endif之間,用來(lái)判斷是
否符合編譯條件。例如:
#ifndefENABLE_COMPILE
SerrorDisablecompile
#endif
ttinclude用來(lái)包含文件。通常是用來(lái)包含頭文件,但實(shí)際上它什么文件都可以
包含。它直接將文件的內(nèi)容引入到當(dāng)前的包含文件中,這些包含都是
由預(yù)處理器完成的。
Spragma指定編譯器的參數(shù),這個(gè)和具體的編譯器有關(guān)。例如有些編譯器支持
startup和exitpragmas,允許用戶指定在程序開(kāi)始和結(jié)束時(shí)執(zhí)行的
函數(shù)。
Spragmastartupload_data
Spragmaexitclose_files
_FILE_預(yù)處理常量,代表當(dāng)前編譯的文件名。例如可以使用如下代碼輸出當(dāng)
前的文件名:printf("Thisfilenameis%s",—FILE—);
_LINE_預(yù)處理常量,代表當(dāng)前編譯的行數(shù)。例如可以使用如下代碼輸出當(dāng)前
的行數(shù):printf(uCurrentlineis%d",—LINE—);
_DATE_預(yù)處理常量,代表當(dāng)前編譯的日期。例如可以使用如下代碼輸出當(dāng)前
的編譯日期:printf(^Currentcompiledateis%s”,DATE_);
_TIME_預(yù)處理常量,代表當(dāng)前編譯的時(shí)間。例如可以使用如下代碼輸出當(dāng)前
的編譯時(shí)間:printf("Currentcompiletimeis%s",—TIME—);
在使井預(yù)處理的時(shí)候需要注意兩件事情:
1、在定義宏或常量時(shí)候盡可能的使用括號(hào)。這是因?yàn)轭A(yù)處理器是將宏和常
量采用直接替換的方式,如果不是用括號(hào)則有可能產(chǎn)生錯(cuò)誤的程序處理。看下面
的代碼:
ttdefineDISPLAY1_HEIGHT320
#defineDISPLAY2_HEIGHT240
#defineDISPLAY_SUMDISPLAY1_HEIGHT+DISPLAY2_HEIGHT
if(DISPLAY_SUM*2>200)
上面的判斷語(yǔ)句的真實(shí)想法是如果顯示高度之和的2倍大于200則進(jìn)行相應(yīng)
的處理。而實(shí)際上進(jìn)行預(yù)處理后上面的代碼變成了if(DISPLAY1JEIGHT+
DISPLAY2_HEIGHT*2)也就是if(320+240*2),這與我們期望的值相差太遠(yuǎn)了。因
此在定義防時(shí)候最好加上括號(hào),這樣就不會(huì)出問(wèn)題了。
2、在使用宏的時(shí)候必須不能出現(xiàn)參數(shù)變化。請(qǐng)看下面的代碼:
#defineSQUARE(a)((a)*(a))
inta=2;
intb;
b=SQUARE(a++);//結(jié)果:a=4,即執(zhí)行了兩次增1。
正確的用法是:
b=SQUARE(a);
a++;〃結(jié)果:a=3,即只執(zhí)行了一次增1。
2.4重溫C語(yǔ)言的函數(shù)
理論上我們可以只使用一個(gè)main函數(shù),因?yàn)椴还芏嗌俪绦蛭覀兌伎梢跃?/p>
寫(xiě)在一個(gè)main里面。但是您能設(shè)想有一個(gè)10萬(wàn)行的main函數(shù)嗎?所以我們不
得不把程序分成各個(gè)模塊,不但要分成函數(shù),而且還要將不同類(lèi)型的函數(shù)放在不
同的文件中。就我的經(jīng)驗(yàn)而言,通常程序多于200行的時(shí)候應(yīng)該考慮分成函數(shù),
而一個(gè)文件中的總共代碼最好不要超過(guò)5ooo行。多了就不利于調(diào)試和閱讀。當(dāng)
然,這些內(nèi)容只是為了增強(qiáng)代碼的可讀性和易管理性的問(wèn)題,但是您也要知道,
軟件在達(dá)到一定規(guī)模后就不單單是技術(shù)問(wèn)題了,還有管理的問(wèn)題。
2.4.1形參和實(shí)參
直到現(xiàn)在我還我還記得在我初學(xué)C語(yǔ)言的時(shí)候形參和實(shí)參給我?guī)?lái)的困惑,
我期望能夠用非常簡(jiǎn)單明了的語(yǔ)言來(lái)描述它們,讓我們來(lái)試試吧!
形參:在函數(shù)定義的時(shí)候使用的參數(shù)叫做形參。
實(shí)參:在使用函數(shù)的時(shí)候傳遞的參數(shù)叫做實(shí)參。
舉例說(shuō)明:
intadd(inta,intb)//這里是函數(shù)的定義,a,b都屬于形參
(
//這里全部都是函數(shù)的實(shí)現(xiàn)
return(a+b);
)
上面的函數(shù)展示了一個(gè)函數(shù)的各個(gè)部分——定義和實(shí)現(xiàn),在函數(shù)定義里
的a和b都是形參,在函數(shù)的實(shí)現(xiàn)體內(nèi),也就是a+b,中的a和b是實(shí)參的替身。
這里是最容易讓人困惑的地方,看一個(gè)例子吧,假設(shè)有一個(gè)函數(shù)calc需要調(diào)用
add函數(shù)來(lái)實(shí)現(xiàn)加法的運(yùn)算功能:
intcalc(intcalctype,intpl,intp2)//calctype、pl和p2都
是形參
(
if(calctype==1)
(
returnadd(pl,p2);〃在這里的pl和p2是形參還是實(shí)參?
)
return0;
)
根據(jù)我們對(duì)形參和實(shí)參的定義,Pl和p2對(duì)于calc函數(shù)來(lái)說(shuō)是形參,但是對(duì)
于add函數(shù)來(lái)說(shuō)是實(shí)參。哦,恍然大悟了沒(méi)有?對(duì)于判斷到底是實(shí)參還是形參
是有參考坐標(biāo)的。同樣的此時(shí)在函數(shù)add內(nèi)部a+b等同于pl+p2,因此說(shuō)在函數(shù)
的實(shí)現(xiàn)體內(nèi)形參(形式上的參數(shù))是調(diào)用時(shí)實(shí)參(實(shí)際上的參數(shù))的替身。相
當(dāng)于形參為實(shí)參占了一個(gè)位子,使用函數(shù)的時(shí)候?qū)崊⒕妥诹诉@個(gè)位子上。
如果您理解了上面的內(nèi)容,那么在使用函數(shù)的時(shí)候就再也沒(méi)有必要糾纏于實(shí)
參和形參了,因?yàn)樗鼈兒掀饋?lái)完成了一個(gè)參數(shù)的傳遞過(guò)程。調(diào)用函數(shù)的時(shí)候,
按照形參的類(lèi)型傳遞相應(yīng)的變量就可以了。形參和實(shí)參只是一個(gè)概念上的區(qū)分,
在實(shí)際的二進(jìn)制層次并沒(méi)有這個(gè)概念,因此我們不要過(guò)分拘泥于此,理解了就
好。
2.4.2宏與函數(shù)的比較
對(duì)于一個(gè)初級(jí)的程序員來(lái)說(shuō),什么時(shí)候使用宏,什么時(shí)候使用函數(shù)還是
有些暈的,這里我也順帶的提一下吧。從前面可以知道,當(dāng)預(yù)處理器遇到宏的
引用時(shí),都是將它用宏語(yǔ)句進(jìn)行替換。因此,在每一個(gè)宏引用的地方都會(huì)增加相
應(yīng)的宏代碼,結(jié)果就是使得編譯后的代碼加大。而函數(shù)則是直接調(diào)用函數(shù)的代
碼,不會(huì)增加編譯后的代碼大小。不過(guò)使用函數(shù)的缺點(diǎn)是在函數(shù)調(diào)用的時(shí)候會(huì)增
加?些額外的處理,這使得執(zhí)行函數(shù)的時(shí)間比相應(yīng)的宏代碼的執(zhí)行時(shí)間要長(zhǎng)一
些。因此如果在需要高效率的時(shí)候就應(yīng)該使用宏,如果需要程序變得更小則使用
函數(shù)。關(guān)于函數(shù)的額外處理部分的信息將在下一節(jié)編譯器基礎(chǔ)中進(jìn)行介紹,到
那個(gè)時(shí)候我們就會(huì)對(duì)函數(shù)是如何執(zhí)行的問(wèn)題有一個(gè)全面的了解了。
2.4.3函數(shù)指針
還記得指針嗎?什么,忘了!對(duì)您豎起我的大拇指,恭喜您已經(jīng)修成正
果了,因?yàn)樵谀难劾镆呀?jīng)沒(méi)有了對(duì)指針特別的概念。還記得我們?cè)诮榻B指針
的時(shí)候說(shuō)的嗎,指針就是一種變量。那么函數(shù)呢?其實(shí)每個(gè)函數(shù)也是一樣,每個(gè)
函數(shù)名就是一個(gè)函數(shù)的首地址,因此我們也可以定義一個(gè)指針變量來(lái)存儲(chǔ)它,
這個(gè)就是函數(shù)指針——指向一個(gè)函數(shù)的指針。這一節(jié)將讓您從另一個(gè)深度上加深
對(duì)指針的認(rèn)識(shí)。
函數(shù)指針在多任務(wù)的操作系統(tǒng)環(huán)境下是十分有用的。在多任務(wù)操作系統(tǒng)
環(huán)境下,由于牽涉到多個(gè)任務(wù)之間的交互,因此通常使用函數(shù)指針的形式將一
個(gè)函數(shù)做為參數(shù)傳遞給另一個(gè)任務(wù),以便于當(dāng)另一個(gè)任務(wù)完成操作之后可以調(diào)用
當(dāng)前任務(wù)的函數(shù)通知狀態(tài)。這個(gè)由“另一個(gè)任務(wù)”調(diào)用的函數(shù)就是大名鼎鼎的
回調(diào)(Callback)函數(shù),這個(gè)回調(diào)函數(shù)的傳遞就是通過(guò)函數(shù)指針這個(gè)載體實(shí)現(xiàn)的。
回調(diào)函數(shù)還常用在定時(shí)服務(wù)的程序里,在設(shè)置一個(gè)定時(shí)器的時(shí)候我們會(huì)指定一
個(gè)回調(diào)函數(shù),用來(lái)在到達(dá)定時(shí)時(shí)間的時(shí)候調(diào)用以做出相應(yīng)的處理。
通常情況下使用如下方式定義一個(gè)函數(shù)指針類(lèi)型:
typedefint(*FunctionType)(intparam1,intparam2);
FunctionTypepFunction;
使用的時(shí)候可以為pFunction賦值,在這里FunctionType的函數(shù)類(lèi)型與
我們上面的add函數(shù)的形式相同,因此我們可以改用下面的代碼來(lái)調(diào)用add函數(shù):
intnResult;
FunctionTypepAddFunc=add;
nResult=pAddFunc(1,2);//返回的結(jié)果是3
在這里之所以使用typedef來(lái)定義一個(gè)類(lèi)型是為了保持在C語(yǔ)言環(huán)境下
的函數(shù)調(diào)用一致。為了更好的理解函數(shù)指針,我將使用一個(gè)void型的函數(shù)指針
來(lái)傳遞這個(gè)函數(shù)。打開(kāi)VisualStudio2003.Net,新建VC控制臺(tái)工程Test3
(請(qǐng)參考前面Testi的創(chuàng)建過(guò)程),輸入如下代碼:
Sinclude"stdafx.h"
typedefint(*FunctionType)(intparam1,intparam2);
intadd(inta,intb)
(
return(a+b);
)
int_tmain(intargc,_TCHAR*argv[])
(
intnResult;
void*pVoid=add;//通過(guò)Void型的指針傳遞函數(shù)指針
nResult=((FunctionType)pVoid)(1,2);
printf(,zResultis%d\n/z,nResult,0,0);
return0;
編譯鏈接運(yùn)行,可以看見(jiàn)輸出結(jié)果是3。
從上面的例子可以看出,一個(gè)函數(shù)名其實(shí)就是一個(gè)地址,可以直接賦值給指
針,并且通過(guò)指針類(lèi)型的轉(zhuǎn)換來(lái)實(shí)現(xiàn)函數(shù)的調(diào)用。進(jìn)一步,如果FunctionType
類(lèi)型與函數(shù)add之間的參數(shù)和返回值不一樣可以嗎?還是讓事實(shí)說(shuō)話吧,將上面
的程序修改成如下樣式:
Sinclude"stdafx.h〃
typedefint(*FunctionType)(intparam1);
intadd(inta,intb)
(
return(a+b);
int_tmain(intargc,_TCHAR*argv[])
(
intnResult;
void*pVoid=add;//通過(guò)Void型的指針傳遞函數(shù)指針
nResult=((FunctionType)pVoid)(1);
printf(z,Resultis%d\n/z,nResult,0,0);
return0;
編譯鏈接,可以通過(guò)。運(yùn)行,輸出結(jié)果是2012749654,好大的數(shù)字!
從上面的代碼情況看出,程序是可以運(yùn)行的,只不過(guò)輸出的結(jié)果不對(duì)。這是
因?yàn)閍dd需要兩個(gè)參數(shù),而我們調(diào)用的時(shí)候卻只有一個(gè)參數(shù)。那么另一個(gè)參數(shù)
是什么呢?答案是一個(gè)隨機(jī)數(shù)。前面我們說(shuō)過(guò),函數(shù)中會(huì)通過(guò)形參為實(shí)參留個(gè)
“位子”,那么現(xiàn)在有兩個(gè)“位子”卻只用了一個(gè),那么可以預(yù)見(jiàn)的,這個(gè)空
的位子中就是一個(gè)隨機(jī)數(shù)了。
如果到現(xiàn)在為止,您對(duì)這個(gè)例子還不是很明白的話,請(qǐng)您一定要再重新體會(huì)
一下,因?yàn)檫@部分的內(nèi)容對(duì)于您理解程序會(huì)有很大的幫助。
2.4.4不要使用結(jié)構(gòu)體或數(shù)組做為函數(shù)的參數(shù)
在前面講解函數(shù)參數(shù)的時(shí)候我們?cè)?jīng)提到過(guò),函數(shù)會(huì)通過(guò)聲明的形參為實(shí)參
留“位子”,那么這是一個(gè)多大的“位子”呢?答案是和形參的數(shù)據(jù)類(lèi)型的大
小一樣,而且這個(gè)位子是存放在系統(tǒng)的堆棧中的。結(jié)果是參數(shù)的數(shù)據(jù)類(lèi)型越大,
需要的堆棧空間就越大,這對(duì)于內(nèi)存空間很小的系統(tǒng)來(lái)說(shuō)是很不利的。而且在
進(jìn)行實(shí)參傳遞的時(shí)候還需要將實(shí)參的內(nèi)容拷貝到對(duì)應(yīng)的“位子”中,因此就增加
了很多的調(diào)用函數(shù)時(shí)的額外處理。(這里稱(chēng)呼成堆棧,實(shí)際上應(yīng)該是“棧”,
因?yàn)槲覀兡壳傲?xí)慣于稱(chēng)呼‘'棧"為堆棧。“堆”通常指的是一個(gè)使用malloc等
函數(shù)分配的內(nèi)存空間。)
基于上述原因,推薦不直接使用大的結(jié)構(gòu)體或數(shù)組做為函數(shù)的參數(shù),請(qǐng)
溫馨提示
- 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ì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 編寫(xiě)可維護(hù)代碼的原則試題及答案
- 精確wps數(shù)據(jù)處理方法試題及答案
- 文本與語(yǔ)境的交互影響試題及答案
- 法學(xué)概論科學(xué)復(fù)習(xí)策略試題及答案
- 戰(zhàn)略規(guī)劃與實(shí)施環(huán)節(jié)試題及答案
- WPS批量處理文檔技巧試題及答案
- 普通邏輯考試復(fù)習(xí)的方法與試題及答案
- 現(xiàn)代網(wǎng)絡(luò)架構(gòu)規(guī)劃試題及答案
- 網(wǎng)絡(luò)管理人員的職業(yè)發(fā)展規(guī)劃試題及答案
- 了解Computers與Photoshop結(jié)合2025年試題及答案
- eras婦科腫瘤圍手術(shù)期管理指南解讀
- 簡(jiǎn)單版借款協(xié)議模板
- 【MOOC】全國(guó)大學(xué)生數(shù)學(xué)競(jìng)賽提高課程-山東大學(xué) 中國(guó)大學(xué)慕課MOOC答案
- 腎動(dòng)脈狹窄介入治療與護(hù)理
- 管道閉水試驗(yàn)(自動(dòng)計(jì)算)
- 企業(yè)環(huán)境應(yīng)急知識(shí)培訓(xùn)
- GB/T 24630.2-2024產(chǎn)品幾何技術(shù)規(guī)范(GPS)平面度第2部分:規(guī)范操作集
- 量販?zhǔn)終TV消防應(yīng)急疏散預(yù)案
- 國(guó)開(kāi)(河北)2024年秋《現(xiàn)代產(chǎn)權(quán)法律制度專(zhuān)題》形考作業(yè)1-4答案
- 王者榮耀VS英雄聯(lián)盟:MOBA游戲的對(duì)決
- 新加坡雇傭合同模板
評(píng)論
0/150
提交評(píng)論