考試大編輯整理C++編程知識(shí)
一個(gè)實(shí)用的程序必須通過某種手段把它的結(jié)果或需求轉(zhuǎn)達(dá)給用戶。為了實(shí)現(xiàn)這種與用戶之間的交流,C語言提供了一個(gè)內(nèi)容豐富的函數(shù)庫,即標(biāo)準(zhǔn)輸入/輸出庫。本章的內(nèi)容就是針對(duì)這些函數(shù)的,并回答了有關(guān)它們的一些常見問題。
17.1 為什么直到程序結(jié)束時(shí)才看到屏幕輸出?
有時(shí),依賴于所使用的編譯程序和操作系統(tǒng),系統(tǒng)會(huì)對(duì)輸出進(jìn)行緩沖?!熬彌_”是指任何要送到設(shè)備上的輸出,無論設(shè)備是屏幕、磁盤還是打印機(jī),都被存儲(chǔ)起來,直到輸出量大到足以進(jìn)行高效的輸出。當(dāng)存儲(chǔ)了足夠多的輸出信息時(shí),再整塊地向指定的設(shè)備輸出。
這種過程會(huì)給不了解其作用的程序員帶來兩個(gè)問題。首先,在程序送出輸出內(nèi)容后,它可能要再過一段時(shí)間后才會(huì)在屏幕上顯示出來。如果程序員正在試圖跟蹤程序的當(dāng)前運(yùn)行狀態(tài),他就會(huì)被這種效果所困擾。
其次,更可怕的是,在程序顯示提示信息并等待用戶輸入時(shí),很可能就會(huì)發(fā)生問題。當(dāng)程序試圖從用戶那里得到輸入信息時(shí),輸出緩沖區(qū)可能還未被“填滿”,因此送往屏幕的提示信息可能不會(huì)顯示出來,用戶也就不知道程序已經(jīng)在等待他進(jìn)行輸入了——他所能得出的結(jié)論只能是這個(gè)“可愛”的程序突然停止工作了。
如何解決這個(gè)問題呢?有兩種辦法。第一種辦法是在程序的開始部分,在進(jìn)行任何輸出之前,加入下述語句:
setvbuf(stdout,NULL,_IONBF,O);
該語句的作用是實(shí)現(xiàn)程序到屏幕的無緩沖輸出。當(dāng)這條命令被執(zhí)行后,每一個(gè)被送往屏幕的字符都會(huì)立即顯示出來。
用這種辦法解決這個(gè)問題確實(shí)比較方便,但是還不夠理想。筆者不想在這里對(duì)屏幕輸入和輸出展開一次技術(shù)討論,但筆者要指出這樣一點(diǎn),即對(duì)屏幕輸出進(jìn)行緩沖是有充分的理由的,并且你還會(huì)希望這樣做。
這佯一來,就引出了解決輸出緩沖問題的另一種辦法。當(dāng)fflush()命令作用于一個(gè)輸出緩沖區(qū)時(shí),它會(huì)使該緩沖區(qū)“倒空”自身,而不管它是否已被填滿。因此,為了解決屏幕緩沖問題,在需要“倒空”輸出緩沖區(qū)時(shí),你只需插入如下命令:
fflush(stdout):
在程序要求用戶輸入之前,或者在程序開始一項(xiàng)耗時(shí)的大型的計(jì)算工作之前,先“倒空”輸出緩沖區(qū)。這樣,當(dāng)程序暫時(shí)停住時(shí),你就能清楚地知道其原因了。
17.2 怎樣在屏幕上定位光標(biāo)?
C標(biāo)準(zhǔn)并沒有提供在屏幕上定位光標(biāo)的方法,其原因很多。C被設(shè)計(jì)成能在各種各樣的計(jì)算機(jī)上工作,而其中的許多機(jī)型都有不同的屏幕類型。例如,在行式打印終端上,不能向上移動(dòng)光標(biāo);一個(gè)嵌入式系統(tǒng)甚至也可能是用c編寫的,而在它的應(yīng)用場(chǎng)合可能根本就沒有屏幕。
盡管這樣,在屏幕上定位光標(biāo)對(duì)你的程序來說還是有用的。你可能希望給用戶一個(gè)吸引人的視覺效果,并且只能通過移動(dòng)光標(biāo)來實(shí)現(xiàn);你還可能想用相應(yīng)的輸出命令嘗試一點(diǎn)動(dòng)畫效果。盡管這方面沒有標(biāo)準(zhǔn)的處理方法,但還是有好幾種方法可以解決這個(gè)問題。
首先,編譯程序的開發(fā)者會(huì)提供一個(gè)函數(shù)庫,專門處理基于他們的編譯程序的屏幕輸出操作,其中肯定會(huì)有定位光標(biāo)的函數(shù)。但是,很多人認(rèn)為這是最差的解決辦法,因?yàn)槊恳粋€(gè)開發(fā)商都可以自由地開發(fā)自己的實(shí)現(xiàn)方法,所以在一種編譯程序上開發(fā)的程序,當(dāng)移到另一種編譯程序上時(shí),幾乎必然要重寫,更別說移到另一種計(jì)算機(jī)上了。
其次,可以定義一套標(biāo)準(zhǔn)的庫函數(shù),并使編譯程序的開發(fā)者在他的編譯程序中實(shí)現(xiàn)這套函數(shù)。流行的Curses軟件包就起源于這種思路。在大多數(shù)計(jì)算機(jī)和編譯程序中都可以使用Curses,因此,用Curses實(shí)現(xiàn)屏幕輸出的程序在大多數(shù)計(jì)算機(jī)和編譯程序中都可以工作。
第三,你可以利用這樣一個(gè)事實(shí),即你想打印到其上的設(shè)備會(huì)用一種特定的方式解釋你送過去的字符。終端(或屏幕)應(yīng)設(shè)計(jì)成按一種標(biāo)準(zhǔn)方式去解釋送給它們的字符,這就是ANSI標(biāo)準(zhǔn)。如果你認(rèn)為你的計(jì)算機(jī)是遵循ANSI標(biāo)準(zhǔn)的,你就可以通過打印相應(yīng)的字符來控制屏幕把光標(biāo)定位在所需的位置上,并且可以把這種操作和其它操作組合在一起。
17.3 向屏幕上寫數(shù)據(jù)的最簡(jiǎn)單的方法是什么?
C語言包含了大約幾百個(gè)向屏幕上寫數(shù)據(jù)的函數(shù),很難決定在某一時(shí)刻最適合用哪一個(gè)函數(shù)來向屏幕上寫數(shù)據(jù)。許多程序員只是簡(jiǎn)單地選擇一個(gè)或兩個(gè)打印函數(shù),并且以后只使用這些函數(shù)。這是一種可以接受的編程風(fēng)格,盡管這樣的程序員也許不是總能寫出的代碼。
一個(gè)程序員應(yīng)該做的就是把每個(gè)打印函數(shù)的設(shè)計(jì)目的和用法都回顧一遍,這樣,當(dāng)他需要向屏幕上打印數(shù)據(jù)時(shí),他就能選出的函數(shù),甚至還可以自己編寫一些打印函數(shù)。
要成為一個(gè)真正熟練的程序員,第一步要做的工作的一部分就是學(xué)會(huì)正確地使用標(biāo)準(zhǔn)C語言庫中的打印函數(shù)。讓我們仔細(xì)地分析一下其中的幾個(gè)函數(shù)。
printf(,variables);
printf()是使用最廣泛的打印函數(shù)。在把文本輸出到屏幕上時(shí),有些程序員只使用這個(gè)函數(shù)。盡管如此,該函數(shù)的設(shè)計(jì)目的只是用來把帶格式的文本打印到屏幕上。實(shí)際上,“printf\"是“print formatted(帶格式打印)”的縮寫。帶格式的文本是指文本中不僅僅包含寫到代碼中的字符串,還包含由程序動(dòng)態(tài)生成的數(shù)字、字符和其它數(shù)據(jù);此外,它的內(nèi)容可以按一種特定的方式顯示,例如,它可以按所指定的小數(shù)點(diǎn)前后的位數(shù)來顯示實(shí)數(shù)。正是由于這個(gè)原因,所以
printf()函數(shù)是不可缺少的!
那么,為什么有時(shí)又不使用printf()呢?這里有幾個(gè)原因。
第一個(gè)原因是程序員想更清楚地表達(dá)他的意圖。程序員可能只對(duì)printf()函數(shù)提供的諸多功能中的一小部分感興趣,在這種情況下,他可能想使用只提供這一小部分功能的那個(gè)函數(shù),例如:
putchar(char);
該函數(shù)的作用是把一個(gè)字符送到屏幕上。如果你只需做這部分工作,那么它是十分合適的。除此之外,它就不見得有什么好處了。然而,通過使用這個(gè)函數(shù),你就能非常清楚地表達(dá)相應(yīng)的那部分代碼的意圖,即把單個(gè)字符送到屏幕上。
puts(char*);
該函數(shù)的作用是把一個(gè)字符串寫到屏幕上。它不能象printf()一樣接受額外的數(shù)據(jù),也不能對(duì)傳遞過來的字符串加以處理。同樣,通過使用這個(gè)函數(shù),你就能非常清楚地表達(dá)相應(yīng)的那部分代碼的意圖。
程序員不使用printf()的第二個(gè)原因是為了提高程序的執(zhí)行效率。printf()函數(shù)的額外開銷太多,也就是說,即使是進(jìn)行一次簡(jiǎn)單的操作,它也需要做大量的工作。它需要檢查傳遞過來的字符串與格式說明符是否匹配,還需要檢查傳遞過來的參數(shù)個(gè)數(shù),等等。上面提到過的另外兩個(gè)函數(shù)沒有這些額外的開銷,因此它們可以執(zhí)行得非??臁_@個(gè)因素對(duì)大多數(shù)向屏幕上寫數(shù)據(jù)的程序來說并不重要,但是,在處理磁盤文件中的大量數(shù)據(jù)時(shí),它就顯得很重要了。
不使用printf()的第三個(gè)原因是程序員想減小可執(zhí)行程序的大小。當(dāng)你在程序中使用了標(biāo)準(zhǔn)C函數(shù)時(shí),它們必須被“連接進(jìn)來”,也就是說,它們必須被包含進(jìn)所生成的可執(zhí)行文件中。對(duì)于象putchar()和puts()這樣簡(jiǎn)單的打印函數(shù),對(duì)應(yīng)的程序段是很短的,而對(duì)應(yīng)于printf()的程序段卻相當(dāng)長——特別是因?yàn)樗厝灰皟蓚€(gè)函數(shù)。
第二個(gè)原因可能是最不重要的一個(gè)原因,然而,如果你在使用靜態(tài)連接程序,并且想保持較小的可執(zhí)行文件的話,那么這就是一項(xiàng)重要的技巧了。例如,盡可能減小TSR和其它一些程序的大小是很值得的。
無論如何,程序員都應(yīng)根據(jù)自己的目的來選擇需要使用的函數(shù)。
17.4 向屏幕上寫文本的最快的方法是什么?
通常,你不會(huì)過分關(guān)心程序?qū)懫聊坏乃俣?。但是,在有些?yīng)用中,需要盡可能快地寫屏幕,這樣的程序可能包括:
·文本編輯器。如果不能很快地寫屏幕,則由用戶輸入文本所造成的屏幕滾動(dòng)和其它有關(guān)操作可能會(huì)顯得太慢。
·活動(dòng)的文本。在同一區(qū)域快速地打印字符,是獲得動(dòng)畫效果的一種常用手段,如果不能快速地把文本打印到屏幕上,那么動(dòng)畫就太慢了,視覺效果就不會(huì)好。
·監(jiān)視器程序。這樣的程序要連續(xù)地監(jiān)視系統(tǒng)、其它程序或硬件設(shè)備,它可能需要每秒在屏幕上打印多次狀態(tài)的更新信息,而通過標(biāo)準(zhǔn)c庫函數(shù)實(shí)現(xiàn)的屏幕打印對(duì)這樣的程序來說很可能顯得太慢。
那么,在這些情況下應(yīng)該怎么辦呢?有三種辦法可以加快程序?qū)懫聊坏乃俣龋哼x用額外開銷較小的打印函數(shù);使用提供了快速打印功能的軟件包或函數(shù)庫;跳過操作系統(tǒng),直接寫屏幕。下面將按從簡(jiǎn)到繁的順序分析這幾種辦法。
選用額外開銷較小的打印函數(shù)
有些打印函數(shù)的額外開銷比別的打印函數(shù)要多?!邦~外開銷”是指與其它函數(shù)相比,某個(gè)函數(shù)必須做的額外工作。例如,printf()的額外開銷就比puts()多。那么,為什么會(huì)這樣呢?
puts()函數(shù)是很簡(jiǎn)單的,它接受一個(gè)字符串并把它寫到顯示器屏幕上。當(dāng)然,printf()函數(shù)也能做同樣的工作,但它還要做大量其它的工作——它要分析送給它的字符串,以找出指示如何打印內(nèi)部數(shù)據(jù)的那部分特殊代碼。
也許你的程序中沒有特殊字符,而且你也沒有傳遞任何這樣的字符,但不幸的是,printf()無法知道這一點(diǎn),它每次都必須檢查字符串中是否有特殊字符。
函數(shù)putch()和puts()之間也有一點(diǎn)微小的差別——在只打印單個(gè)字符時(shí),putch()的效果更好(額外開銷更少)。
遺憾的是,與真正把字符寫到屏幕上所帶來的額外開銷相比,這些C函數(shù)本身的額外開銷是微不足道的。因此,除了在一些特殊情況下之外,這種辦法對(duì)程序員不會(huì)有太大的幫助。
使用提供了快速打印功能的軟件包或函數(shù)庫
這可能是有效地提高寫屏速度的最簡(jiǎn)單的辦法。你可以得到這樣的一個(gè)軟件包,它或者會(huì)用更快的版本替換編譯程序中固有的打印函數(shù),或者會(huì)提供一些更快的打印函數(shù)。
這種辦法使程序員的工作變得十分輕松,因?yàn)樗麕缀醪恍枰膭?dòng)自己的程序,并且可以使用別人花了大量時(shí)間優(yōu)化好了的代碼。這種辦法的缺點(diǎn)是這些代碼可能屬于另一個(gè)程序員,在你的程序中使用它們的費(fèi)用可能是昂貴的。此外,你可能無法把你的程序移植到另一種平臺(tái)上,因?yàn)槟欠N平臺(tái)上可能沒有相應(yīng)的軟件包。
不管怎樣,對(duì)程序員來說,這是一種既實(shí)用又有效的辦法。
跳過操作系統(tǒng),直接寫屏幕
由于多種原因,這種辦法有時(shí)不太令人滿意。事實(shí)上,這種辦法在有些計(jì)算機(jī)和操作系統(tǒng)上根本無法實(shí)現(xiàn)。此外,這種辦法的具體實(shí)現(xiàn)通常會(huì)因計(jì)算機(jī)的不同而不同,甚至在同一臺(tái)計(jì)算機(jī)上還會(huì)因編譯程序的不同而不同。
不管怎樣,為了提高視頻輸出速度,直接寫屏是非常必要的。對(duì)全屏幕文本來說,你可能可以每秒種寫幾百屏。如果你需要這樣的性能(可能是為了視頻游戲),采用這種辦法是值得的。
因?yàn)槊糠N計(jì)算機(jī)和操作系統(tǒng)對(duì)這個(gè)問題的處理方法是不同的,所以要寫出適用于所有操作系統(tǒng)的程序是不現(xiàn)實(shí)的。下文將介紹如何用Borland c在MS-DOS下實(shí)現(xiàn)這種辦法。即使你不使用這些系統(tǒng),你也應(yīng)該能從下文中了解到正確的方法,這樣你就可以在你的計(jì)算機(jī)和操作系統(tǒng)上寫出類似的程序了。
首先,你需要某種能把數(shù)據(jù)寫到屏幕上的方法。你可以創(chuàng)建一個(gè)指向視頻緩沖區(qū)的指針。在MS-DOS下使用Borland C時(shí),可以用下述語句實(shí)現(xiàn)這一點(diǎn):
char far*Sereen=MK_FP(0xb800,Ox0000);
far指針?biāo)赶虻牡刂凡⒉痪窒抻诔绦虻臄?shù)據(jù)段中,它可以指向內(nèi)存中的任何地方。MK_FP()產(chǎn)生一個(gè)指向指定位置的far指針。有些其它的編譯程序和計(jì)算機(jī)并不要求區(qū)分指針的類型,或者沒有類似的函數(shù),你應(yīng)該在編譯程序手冊(cè)中查找相應(yīng)的信息。
現(xiàn)在,你有了一個(gè)“指向”屏幕左上角的指針。只要你向該指針?biāo)赶虻膬?nèi)存位置寫入若干字節(jié),相應(yīng)的字符就會(huì)從屏幕的左上角開始顯示。下面這個(gè)程序就是這樣做的:
#include
main()
{
int a:
char far*Screen=MK_FP(Oxb800。Ox0000):
for(a=0;a<26;++a)
sereen[a*2]=\'a\'+a:
return(O);
}
該程序運(yùn)行后,屏幕頂端就會(huì)打印出小寫的字母表。
你將會(huì)發(fā)現(xiàn),字符在視頻緩沖區(qū)中并不是連續(xù)存放的,而是每隔一個(gè)字節(jié)存放一個(gè)。這是為什么呢?這是因?yàn)橐粋€(gè)字符雖然僅占一個(gè)字節(jié),但緊接著它的下一個(gè)字節(jié)要用來存放該字符的顏色值。因此,屏幕上顯示的每個(gè)字符在計(jì)算機(jī)內(nèi)存中都占兩個(gè)字節(jié):一個(gè)字節(jié)存放字符本身,另一個(gè)字節(jié)存放它的顏色值。
這說明了兩點(diǎn):首先,必須把字符寫入內(nèi)存中相隔的字節(jié)中,否則你將會(huì)只看到相隔的字符,并且?guī)в泄殴值念伾?。其次,如果要寫帶顏色的文本,或者改變某個(gè)位置原有的顏色,你就需要自己去寫相應(yīng)的顏色字節(jié)。如果不這樣做,文本仍然會(huì)按原來的顏色顯示。每個(gè)描述顏色的字節(jié)既要描述字符的顏色(即前景色),又要描述字符的背景色。一共有16種前景色和16種背景色,分別用顏色字節(jié)的低4位和高4位來表示。
一個(gè)實(shí)用的程序必須通過某種手段把它的結(jié)果或需求轉(zhuǎn)達(dá)給用戶。為了實(shí)現(xiàn)這種與用戶之間的交流,C語言提供了一個(gè)內(nèi)容豐富的函數(shù)庫,即標(biāo)準(zhǔn)輸入/輸出庫。本章的內(nèi)容就是針對(duì)這些函數(shù)的,并回答了有關(guān)它們的一些常見問題。
17.1 為什么直到程序結(jié)束時(shí)才看到屏幕輸出?
有時(shí),依賴于所使用的編譯程序和操作系統(tǒng),系統(tǒng)會(huì)對(duì)輸出進(jìn)行緩沖?!熬彌_”是指任何要送到設(shè)備上的輸出,無論設(shè)備是屏幕、磁盤還是打印機(jī),都被存儲(chǔ)起來,直到輸出量大到足以進(jìn)行高效的輸出。當(dāng)存儲(chǔ)了足夠多的輸出信息時(shí),再整塊地向指定的設(shè)備輸出。
這種過程會(huì)給不了解其作用的程序員帶來兩個(gè)問題。首先,在程序送出輸出內(nèi)容后,它可能要再過一段時(shí)間后才會(huì)在屏幕上顯示出來。如果程序員正在試圖跟蹤程序的當(dāng)前運(yùn)行狀態(tài),他就會(huì)被這種效果所困擾。
其次,更可怕的是,在程序顯示提示信息并等待用戶輸入時(shí),很可能就會(huì)發(fā)生問題。當(dāng)程序試圖從用戶那里得到輸入信息時(shí),輸出緩沖區(qū)可能還未被“填滿”,因此送往屏幕的提示信息可能不會(huì)顯示出來,用戶也就不知道程序已經(jīng)在等待他進(jìn)行輸入了——他所能得出的結(jié)論只能是這個(gè)“可愛”的程序突然停止工作了。
如何解決這個(gè)問題呢?有兩種辦法。第一種辦法是在程序的開始部分,在進(jìn)行任何輸出之前,加入下述語句:
setvbuf(stdout,NULL,_IONBF,O);
該語句的作用是實(shí)現(xiàn)程序到屏幕的無緩沖輸出。當(dāng)這條命令被執(zhí)行后,每一個(gè)被送往屏幕的字符都會(huì)立即顯示出來。
用這種辦法解決這個(gè)問題確實(shí)比較方便,但是還不夠理想。筆者不想在這里對(duì)屏幕輸入和輸出展開一次技術(shù)討論,但筆者要指出這樣一點(diǎn),即對(duì)屏幕輸出進(jìn)行緩沖是有充分的理由的,并且你還會(huì)希望這樣做。
這佯一來,就引出了解決輸出緩沖問題的另一種辦法。當(dāng)fflush()命令作用于一個(gè)輸出緩沖區(qū)時(shí),它會(huì)使該緩沖區(qū)“倒空”自身,而不管它是否已被填滿。因此,為了解決屏幕緩沖問題,在需要“倒空”輸出緩沖區(qū)時(shí),你只需插入如下命令:
fflush(stdout):
在程序要求用戶輸入之前,或者在程序開始一項(xiàng)耗時(shí)的大型的計(jì)算工作之前,先“倒空”輸出緩沖區(qū)。這樣,當(dāng)程序暫時(shí)停住時(shí),你就能清楚地知道其原因了。
17.2 怎樣在屏幕上定位光標(biāo)?
C標(biāo)準(zhǔn)并沒有提供在屏幕上定位光標(biāo)的方法,其原因很多。C被設(shè)計(jì)成能在各種各樣的計(jì)算機(jī)上工作,而其中的許多機(jī)型都有不同的屏幕類型。例如,在行式打印終端上,不能向上移動(dòng)光標(biāo);一個(gè)嵌入式系統(tǒng)甚至也可能是用c編寫的,而在它的應(yīng)用場(chǎng)合可能根本就沒有屏幕。
盡管這樣,在屏幕上定位光標(biāo)對(duì)你的程序來說還是有用的。你可能希望給用戶一個(gè)吸引人的視覺效果,并且只能通過移動(dòng)光標(biāo)來實(shí)現(xiàn);你還可能想用相應(yīng)的輸出命令嘗試一點(diǎn)動(dòng)畫效果。盡管這方面沒有標(biāo)準(zhǔn)的處理方法,但還是有好幾種方法可以解決這個(gè)問題。
首先,編譯程序的開發(fā)者會(huì)提供一個(gè)函數(shù)庫,專門處理基于他們的編譯程序的屏幕輸出操作,其中肯定會(huì)有定位光標(biāo)的函數(shù)。但是,很多人認(rèn)為這是最差的解決辦法,因?yàn)槊恳粋€(gè)開發(fā)商都可以自由地開發(fā)自己的實(shí)現(xiàn)方法,所以在一種編譯程序上開發(fā)的程序,當(dāng)移到另一種編譯程序上時(shí),幾乎必然要重寫,更別說移到另一種計(jì)算機(jī)上了。
其次,可以定義一套標(biāo)準(zhǔn)的庫函數(shù),并使編譯程序的開發(fā)者在他的編譯程序中實(shí)現(xiàn)這套函數(shù)。流行的Curses軟件包就起源于這種思路。在大多數(shù)計(jì)算機(jī)和編譯程序中都可以使用Curses,因此,用Curses實(shí)現(xiàn)屏幕輸出的程序在大多數(shù)計(jì)算機(jī)和編譯程序中都可以工作。
第三,你可以利用這樣一個(gè)事實(shí),即你想打印到其上的設(shè)備會(huì)用一種特定的方式解釋你送過去的字符。終端(或屏幕)應(yīng)設(shè)計(jì)成按一種標(biāo)準(zhǔn)方式去解釋送給它們的字符,這就是ANSI標(biāo)準(zhǔn)。如果你認(rèn)為你的計(jì)算機(jī)是遵循ANSI標(biāo)準(zhǔn)的,你就可以通過打印相應(yīng)的字符來控制屏幕把光標(biāo)定位在所需的位置上,并且可以把這種操作和其它操作組合在一起。
17.3 向屏幕上寫數(shù)據(jù)的最簡(jiǎn)單的方法是什么?
C語言包含了大約幾百個(gè)向屏幕上寫數(shù)據(jù)的函數(shù),很難決定在某一時(shí)刻最適合用哪一個(gè)函數(shù)來向屏幕上寫數(shù)據(jù)。許多程序員只是簡(jiǎn)單地選擇一個(gè)或兩個(gè)打印函數(shù),并且以后只使用這些函數(shù)。這是一種可以接受的編程風(fēng)格,盡管這樣的程序員也許不是總能寫出的代碼。
一個(gè)程序員應(yīng)該做的就是把每個(gè)打印函數(shù)的設(shè)計(jì)目的和用法都回顧一遍,這樣,當(dāng)他需要向屏幕上打印數(shù)據(jù)時(shí),他就能選出的函數(shù),甚至還可以自己編寫一些打印函數(shù)。
要成為一個(gè)真正熟練的程序員,第一步要做的工作的一部分就是學(xué)會(huì)正確地使用標(biāo)準(zhǔn)C語言庫中的打印函數(shù)。讓我們仔細(xì)地分析一下其中的幾個(gè)函數(shù)。
printf(
printf()是使用最廣泛的打印函數(shù)。在把文本輸出到屏幕上時(shí),有些程序員只使用這個(gè)函數(shù)。盡管如此,該函數(shù)的設(shè)計(jì)目的只是用來把帶格式的文本打印到屏幕上。實(shí)際上,“printf\"是“print formatted(帶格式打印)”的縮寫。帶格式的文本是指文本中不僅僅包含寫到代碼中的字符串,還包含由程序動(dòng)態(tài)生成的數(shù)字、字符和其它數(shù)據(jù);此外,它的內(nèi)容可以按一種特定的方式顯示,例如,它可以按所指定的小數(shù)點(diǎn)前后的位數(shù)來顯示實(shí)數(shù)。正是由于這個(gè)原因,所以
printf()函數(shù)是不可缺少的!
那么,為什么有時(shí)又不使用printf()呢?這里有幾個(gè)原因。
第一個(gè)原因是程序員想更清楚地表達(dá)他的意圖。程序員可能只對(duì)printf()函數(shù)提供的諸多功能中的一小部分感興趣,在這種情況下,他可能想使用只提供這一小部分功能的那個(gè)函數(shù),例如:
putchar(char);
該函數(shù)的作用是把一個(gè)字符送到屏幕上。如果你只需做這部分工作,那么它是十分合適的。除此之外,它就不見得有什么好處了。然而,通過使用這個(gè)函數(shù),你就能非常清楚地表達(dá)相應(yīng)的那部分代碼的意圖,即把單個(gè)字符送到屏幕上。
puts(char*);
該函數(shù)的作用是把一個(gè)字符串寫到屏幕上。它不能象printf()一樣接受額外的數(shù)據(jù),也不能對(duì)傳遞過來的字符串加以處理。同樣,通過使用這個(gè)函數(shù),你就能非常清楚地表達(dá)相應(yīng)的那部分代碼的意圖。
程序員不使用printf()的第二個(gè)原因是為了提高程序的執(zhí)行效率。printf()函數(shù)的額外開銷太多,也就是說,即使是進(jìn)行一次簡(jiǎn)單的操作,它也需要做大量的工作。它需要檢查傳遞過來的字符串與格式說明符是否匹配,還需要檢查傳遞過來的參數(shù)個(gè)數(shù),等等。上面提到過的另外兩個(gè)函數(shù)沒有這些額外的開銷,因此它們可以執(zhí)行得非??臁_@個(gè)因素對(duì)大多數(shù)向屏幕上寫數(shù)據(jù)的程序來說并不重要,但是,在處理磁盤文件中的大量數(shù)據(jù)時(shí),它就顯得很重要了。
不使用printf()的第三個(gè)原因是程序員想減小可執(zhí)行程序的大小。當(dāng)你在程序中使用了標(biāo)準(zhǔn)C函數(shù)時(shí),它們必須被“連接進(jìn)來”,也就是說,它們必須被包含進(jìn)所生成的可執(zhí)行文件中。對(duì)于象putchar()和puts()這樣簡(jiǎn)單的打印函數(shù),對(duì)應(yīng)的程序段是很短的,而對(duì)應(yīng)于printf()的程序段卻相當(dāng)長——特別是因?yàn)樗厝灰皟蓚€(gè)函數(shù)。
第二個(gè)原因可能是最不重要的一個(gè)原因,然而,如果你在使用靜態(tài)連接程序,并且想保持較小的可執(zhí)行文件的話,那么這就是一項(xiàng)重要的技巧了。例如,盡可能減小TSR和其它一些程序的大小是很值得的。
無論如何,程序員都應(yīng)根據(jù)自己的目的來選擇需要使用的函數(shù)。
17.4 向屏幕上寫文本的最快的方法是什么?
通常,你不會(huì)過分關(guān)心程序?qū)懫聊坏乃俣?。但是,在有些?yīng)用中,需要盡可能快地寫屏幕,這樣的程序可能包括:
·文本編輯器。如果不能很快地寫屏幕,則由用戶輸入文本所造成的屏幕滾動(dòng)和其它有關(guān)操作可能會(huì)顯得太慢。
·活動(dòng)的文本。在同一區(qū)域快速地打印字符,是獲得動(dòng)畫效果的一種常用手段,如果不能快速地把文本打印到屏幕上,那么動(dòng)畫就太慢了,視覺效果就不會(huì)好。
·監(jiān)視器程序。這樣的程序要連續(xù)地監(jiān)視系統(tǒng)、其它程序或硬件設(shè)備,它可能需要每秒在屏幕上打印多次狀態(tài)的更新信息,而通過標(biāo)準(zhǔn)c庫函數(shù)實(shí)現(xiàn)的屏幕打印對(duì)這樣的程序來說很可能顯得太慢。
那么,在這些情況下應(yīng)該怎么辦呢?有三種辦法可以加快程序?qū)懫聊坏乃俣龋哼x用額外開銷較小的打印函數(shù);使用提供了快速打印功能的軟件包或函數(shù)庫;跳過操作系統(tǒng),直接寫屏幕。下面將按從簡(jiǎn)到繁的順序分析這幾種辦法。
選用額外開銷較小的打印函數(shù)
有些打印函數(shù)的額外開銷比別的打印函數(shù)要多?!邦~外開銷”是指與其它函數(shù)相比,某個(gè)函數(shù)必須做的額外工作。例如,printf()的額外開銷就比puts()多。那么,為什么會(huì)這樣呢?
puts()函數(shù)是很簡(jiǎn)單的,它接受一個(gè)字符串并把它寫到顯示器屏幕上。當(dāng)然,printf()函數(shù)也能做同樣的工作,但它還要做大量其它的工作——它要分析送給它的字符串,以找出指示如何打印內(nèi)部數(shù)據(jù)的那部分特殊代碼。
也許你的程序中沒有特殊字符,而且你也沒有傳遞任何這樣的字符,但不幸的是,printf()無法知道這一點(diǎn),它每次都必須檢查字符串中是否有特殊字符。
函數(shù)putch()和puts()之間也有一點(diǎn)微小的差別——在只打印單個(gè)字符時(shí),putch()的效果更好(額外開銷更少)。
遺憾的是,與真正把字符寫到屏幕上所帶來的額外開銷相比,這些C函數(shù)本身的額外開銷是微不足道的。因此,除了在一些特殊情況下之外,這種辦法對(duì)程序員不會(huì)有太大的幫助。
使用提供了快速打印功能的軟件包或函數(shù)庫
這可能是有效地提高寫屏速度的最簡(jiǎn)單的辦法。你可以得到這樣的一個(gè)軟件包,它或者會(huì)用更快的版本替換編譯程序中固有的打印函數(shù),或者會(huì)提供一些更快的打印函數(shù)。
這種辦法使程序員的工作變得十分輕松,因?yàn)樗麕缀醪恍枰膭?dòng)自己的程序,并且可以使用別人花了大量時(shí)間優(yōu)化好了的代碼。這種辦法的缺點(diǎn)是這些代碼可能屬于另一個(gè)程序員,在你的程序中使用它們的費(fèi)用可能是昂貴的。此外,你可能無法把你的程序移植到另一種平臺(tái)上,因?yàn)槟欠N平臺(tái)上可能沒有相應(yīng)的軟件包。
不管怎樣,對(duì)程序員來說,這是一種既實(shí)用又有效的辦法。
跳過操作系統(tǒng),直接寫屏幕
由于多種原因,這種辦法有時(shí)不太令人滿意。事實(shí)上,這種辦法在有些計(jì)算機(jī)和操作系統(tǒng)上根本無法實(shí)現(xiàn)。此外,這種辦法的具體實(shí)現(xiàn)通常會(huì)因計(jì)算機(jī)的不同而不同,甚至在同一臺(tái)計(jì)算機(jī)上還會(huì)因編譯程序的不同而不同。
不管怎樣,為了提高視頻輸出速度,直接寫屏是非常必要的。對(duì)全屏幕文本來說,你可能可以每秒種寫幾百屏。如果你需要這樣的性能(可能是為了視頻游戲),采用這種辦法是值得的。
因?yàn)槊糠N計(jì)算機(jī)和操作系統(tǒng)對(duì)這個(gè)問題的處理方法是不同的,所以要寫出適用于所有操作系統(tǒng)的程序是不現(xiàn)實(shí)的。下文將介紹如何用Borland c在MS-DOS下實(shí)現(xiàn)這種辦法。即使你不使用這些系統(tǒng),你也應(yīng)該能從下文中了解到正確的方法,這樣你就可以在你的計(jì)算機(jī)和操作系統(tǒng)上寫出類似的程序了。
首先,你需要某種能把數(shù)據(jù)寫到屏幕上的方法。你可以創(chuàng)建一個(gè)指向視頻緩沖區(qū)的指針。在MS-DOS下使用Borland C時(shí),可以用下述語句實(shí)現(xiàn)這一點(diǎn):
char far*Sereen=MK_FP(0xb800,Ox0000);
far指針?biāo)赶虻牡刂凡⒉痪窒抻诔绦虻臄?shù)據(jù)段中,它可以指向內(nèi)存中的任何地方。MK_FP()產(chǎn)生一個(gè)指向指定位置的far指針。有些其它的編譯程序和計(jì)算機(jī)并不要求區(qū)分指針的類型,或者沒有類似的函數(shù),你應(yīng)該在編譯程序手冊(cè)中查找相應(yīng)的信息。
現(xiàn)在,你有了一個(gè)“指向”屏幕左上角的指針。只要你向該指針?biāo)赶虻膬?nèi)存位置寫入若干字節(jié),相應(yīng)的字符就會(huì)從屏幕的左上角開始顯示。下面這個(gè)程序就是這樣做的:
#include
main()
{
int a:
char far*Screen=MK_FP(Oxb800。Ox0000):
for(a=0;a<26;++a)
sereen[a*2]=\'a\'+a:
return(O);
}
該程序運(yùn)行后,屏幕頂端就會(huì)打印出小寫的字母表。
你將會(huì)發(fā)現(xiàn),字符在視頻緩沖區(qū)中并不是連續(xù)存放的,而是每隔一個(gè)字節(jié)存放一個(gè)。這是為什么呢?這是因?yàn)橐粋€(gè)字符雖然僅占一個(gè)字節(jié),但緊接著它的下一個(gè)字節(jié)要用來存放該字符的顏色值。因此,屏幕上顯示的每個(gè)字符在計(jì)算機(jī)內(nèi)存中都占兩個(gè)字節(jié):一個(gè)字節(jié)存放字符本身,另一個(gè)字節(jié)存放它的顏色值。
這說明了兩點(diǎn):首先,必須把字符寫入內(nèi)存中相隔的字節(jié)中,否則你將會(huì)只看到相隔的字符,并且?guī)в泄殴值念伾?。其次,如果要寫帶顏色的文本,或者改變某個(gè)位置原有的顏色,你就需要自己去寫相應(yīng)的顏色字節(jié)。如果不這樣做,文本仍然會(huì)按原來的顏色顯示。每個(gè)描述顏色的字節(jié)既要描述字符的顏色(即前景色),又要描述字符的背景色。一共有16種前景色和16種背景色,分別用顏色字節(jié)的低4位和高4位來表示。