文件包含:#
當預處理器發(fā)現(xiàn)# 指令時, 會查看后面的文件名并把文件的內容包含到當前文件中, 即替換源文件中的#指令。 這相當于把被包含文件的全部內容輸入到源文件#指令所在的位置。
# ←文件名在尖括號中
# ".h" ←文件名在雙引號中
在 UNIX 系統(tǒng)中, 尖括號告訴預處理器在標準系統(tǒng)目錄中查找該文件。雙引號告訴預處理器首先在當前目錄中(或文件名中指定的其他目錄) 查找該文件.
集成開發(fā)環(huán)境(IDE) 也有標準路徑或系統(tǒng)頭文件的路徑。 許多集成開發(fā)環(huán)境提供菜單選項, 指定用尖括號時的查找路徑。 在 UNIX 中, 使用雙引號意味著先查找本地目錄, 但是具體查找哪個目錄取決于編譯器的設定。 有些編譯器會搜索源代碼文件所在的目錄, 有些編譯器則搜索當前的工作目錄, 還有些搜索項目文件所在的目錄。
ANSI C不為文件提供統(tǒng)一的目錄模型, 因為不同的計算機所用的系統(tǒng)不同。 一般而言, 命名文件的方法因系統(tǒng)而異, 但是尖括號和雙引號的規(guī)則與系統(tǒng)無關。
C語言習慣用.h后綴表示頭文件, 這些文件包含需要放在程序頂部的信息。 頭文件經常包含一些預處理器指令。 有些頭文件(如stdio.h) 由系統(tǒng)提供, 當然你也可以創(chuàng)建自己的頭文件。
包含一個大型頭文件不一定顯著增加程序的大小。 在大部分情況下, 頭文件的內容是編譯器生成最終代碼時所需的信息, 而不是添加到最終代碼中的材料。
頭文件示例
該頭文件包含了一些頭文件中常見的內容: #指令、 結構聲明、和函數(shù)原型。 注意, 這些內容是編譯器在創(chuàng)建可執(zhí)行代碼時所需的信息, 而不是可執(zhí)行代碼。
可執(zhí)行代碼通常在源代碼文件中, 而不是在頭文件中。
()函數(shù)通過()函數(shù)調用了fgets()函數(shù), 避免了目標數(shù)組溢出。
兩個源代碼文件都使用類型結構, 所以它們都必須包含.h頭文件。
必須編譯和鏈接.c和.c源代碼文件。
聲明和指令放在.h頭文件中, 函數(shù)定義放在.c源代碼文件中。
使用頭文件
瀏覽任何一個標準頭文件都可以了解頭文件的基本信息。
明示常量——例如, stdio.h中定義的EOF、 NULL和(標準I/O緩沖區(qū)大小) 。
宏函數(shù)——例如, getc(stdin)通常用()定義, 而getc()經常用于定義較復雜的宏, 頭文件ctype.h通常包含ctype系列函數(shù)的宏定義。
函數(shù)聲明——例如, .h頭文件(一些舊的系統(tǒng)中是.h) 包含字符串函數(shù)系列的函數(shù)聲明。 在ANSI C和后面的標準中, 函數(shù)聲明都是函數(shù)原型形式。
結構模版定義——標準I/O函數(shù)使用FILE結構, 該結構中包含了文件和與文件緩沖區(qū)相關的信息。 FILE結構在頭文件stdio.h中
類型定義——標準 I/O 函數(shù)使用指向 FILE 的指針作為參數(shù)。 通常,stdio.h 用# 或把FILE定義為指向結構的指針。 類似地, 和類型也定義在頭文件中。
源代碼文件中包含該頭文件后也包含了該聲明,但是只要聲明的類型一致, 在一個文件中同時使用定義式聲明和引用式聲明沒問題。
需要包含頭文件的另一種情況是, 使用具有文件作用域、 內部鏈接和const 限定符的變量或數(shù)組。 const 防止值被意外修改, 意味著每個包含該頭文件的文件都獲得一份副本。
其他指令
程序員可能要為不同的工作環(huán)境準備C程序和C庫包。 不同的環(huán)境可能使用不同的代碼類型。 預處理器提供一些指令, 程序員通過修改#的值即可生成可移植的代碼。 #undef指令取消之前的#定義。
#undef指令
#undef指令用于“取消”已定義的#指令。
# LIMIT 400
然后, 下面的指令:
#undef LIMIT
將移除上面的定義。 現(xiàn)在就可以把LIMIT重新定義為一個新值。 即使原來沒有定義LIMIT, 取消LIMIT的定義仍然有效。
從C預處理角度看已定義
處理器在識別標識符時, 遵循與C相同的規(guī)則: 標識符可以由大寫字母、 小寫字母、 數(shù)字和下劃線字符組成, 且首字符不能是數(shù)字。 當預處理器在預處理器指令中發(fā)現(xiàn)一個標識符時, 它會把該標識符當作已定義的或未定義的。 這里的已定義表示由預處理器定義。 如果標識符是同一個文件中由前面的#指令創(chuàng)建的宏名, 而且沒有用#undef 指令關閉, 那么該標識符是已定義的。 如果標識符不是宏, 假設是一個文件作用域的C變量, 那么該標識符對預處理器而言就是未定義的。
已定義宏可以是對象宏, 包括空宏或類函數(shù)宏:
# LIMIT 1000 // LIMIT是已定義的
# GOOD // GOOD 是已定義的
# A(X) ((-(X))*(X)) // A 是已定義的
int q; // q 不是宏, 因此是未定義的
#undef GOOD // GOOD 取消定義, 是未定義的
#宏的作用域從它在文件中的聲明處開始, 直到用#undef指令取消宏為止, 或延伸至文件尾(以二者中先滿足的條件作為宏作用域的結束) 。 另外還要注意, 如果宏通過頭文件引入, 那么#在文件中的位置取決于#指令的位置。
和。 這些宏一定是已定義的, 而且不能取消定義。
條件編譯
可以使用其他指令創(chuàng)建條件編譯( ) 。 也就是說,可以使用這些指令告訴編譯器根據(jù)編譯時的條件執(zhí)行或忽略信息(或代碼)塊。
#ifdef、 #else和#endif指令
#ifdef指令說明, 如果預處理器已定義了后面的標識符(MAVIS),則執(zhí)行#else或#endif指令之前的所有指令并編譯所有C代碼(先出現(xiàn)哪個指令就執(zhí)行到哪里) 。 如果預處理器未定義MAVIS, 且有 #else指令, 則執(zhí)行#else和#endif指令之間的所有代碼。
#ifdef #else很像C的if else。 兩者的主要區(qū)別是, 預處理器不識別用于標記塊的花括號({}) , 因此它使用#else(如果需要) 和#endif(必須存在)來標記指令塊。 這些指令結構可以嵌套。 也可以用這些指令標記C語句塊。
如果省略定義(把它放在C注釋中, 或者使用#undef指令取消它的定義) 并重新編譯該程序, 只會輸出最后一行。 可以用這種方法在調試程序。 定義并合理使用#ifdef, 編譯器將執(zhí)行用于調試的程序代碼, 打印中間值。 調試結束后, 可移除定義并重新編譯。 如果以后還需要使用這些信息, 重新插入定義即可。 這樣做省去了再次輸入額外打印語句的麻煩。 #ifdef還可用于根據(jù)不同的C實現(xiàn)選擇合適的代碼塊。
#指令
#指令與#ifdef指令的用法類似, 也可以和#else、 #endif一起使用,但是它們的邏輯相反。 #指令判斷后面的標識符是否是未定義的, 常用于定義之前未定義的常量。
( 舊的實現(xiàn)可能不允許使用縮進的#)
通常, 包含多個頭文件時, 其中的文件可能包含了相同宏定義。 #指令可以防止相同的宏被重復定義。 在首次定義一個宏的頭文件中用#指令激活定義, 隨后在其他頭文件中的定義都被忽略。
#指令通常用于防止多次包含一個文件。
許多被包含的文件中都包含著其他文件, 所以顯式包含的文件中可能包含著已經包含的其他文件。在被包含的文件中有某些項(如, 一些結構類型的聲明) 只能在一個文件中出現(xiàn)一次。 C標準頭文件使用#技巧避免重復包含。確保待測試的標識符沒有在別處定義用文件名作為標識符、 使用大寫字母、 用下劃線字符代替文件名中的點字符、 用下劃線字符做前綴或后綴(可能使用兩條下劃線) 。
#if和#elif指令
#if指令很像C語言中的if。 #if后面跟整型常量表達式, 如果表達式為非零, 則表達式為真。
是一個預處理運算符, 如果它的參數(shù)是用#定義過, 則返回1; 否則返回0。 這種新方法的優(yōu)點是, 它可以和#elif一起使用。
條件編譯還有一個用途是讓程序更容易移植。 改變文件開頭部分的幾個關鍵的定義, 即可根據(jù)不同的系統(tǒng)設置不同的值和包含不同的文件。
預定義宏
C標準規(guī)定了一些預定義宏
C99 標準提供一個名為_ _的預定義標識符, 它展開為一個代表函數(shù)名的字符串(該函數(shù)包含該標識符) 。 那么, _ _必須具有函數(shù)作用域, 而從本質上看宏具有文件作用域。 因此, _ _是C語言的預定義標識符, 而不是預定義宏。
#line和#error
#line指令重置_ _和_ _宏報告的行號和文件名。
#line 1000 // 把當前行號重置為1000
#line 10 "cool.c" // 把行號重置為10, 把文件名重置為cool.c
#error 指令讓預處理器發(fā)出一條錯誤消息, 該消息包含指令中的文本。
#
在現(xiàn)在的編譯器中, 可以通過命令行參數(shù)或IDE菜單修改編譯器的一些設置。 #把編譯器指令放入源代碼中。
# c9x on
一般而言,編譯器都有自己的編譯指示集。 例如內聯(lián)函數(shù) 內部鏈接, 編譯指示可能用于控制分配給自動變量的內存量, 或者設置錯誤檢查的嚴格程度, 或者啟用非標準語言特性等。
C99還提供預處理器運算符, 該運算符把字符串轉換成普通的編譯指示。
泛型選擇(C11)
泛型編程( ) 指那些沒有特定類型, 但是一旦指定一種類型, 就可以轉換成指定類型的代碼。C++在模板中可以創(chuàng)建泛型算法, 然后編譯器根據(jù)指定的類型自動使用實例化代碼。 C沒有這種功能。 然而, C11新增了一種表達式, 叫作泛型選擇表達式( ) , 可根據(jù)表達式的類型(即表達式的類型是int、 還是其他類型) 選擇一個值。 泛型選擇表達式不是預處理器指令, 但是在一些泛型編程中它常用作#宏定義的一部分。
宏必須定義為一條邏輯行, 但是可以用\把一條邏輯行分隔成多條物理行。 在這種情況下, 對泛型選擇表達式求值得字符串。
()最后兩個示例所用的類型與標簽不匹配,所以打印默認的字符串。 可以使用更多類型標簽來擴展宏的能力內聯(lián)函數(shù) 內部鏈接, 但是該程序主要是為了演示的基本工作原理。
對一個泛型選擇表達式求值時, 程序不會先對第一個項求值, 它只確定類型。 只有匹配標簽的類型后才會對表達式求值。可以像使用獨立類型(“泛型”) 函數(shù)那樣使用 定義宏。
內聯(lián)函數(shù)
函數(shù)調用都有一定的開銷, 因為函數(shù)的調用過程包括建立調用、傳遞參數(shù)、 跳轉到函數(shù)代碼并返回。C99還提供另一種方法: 內聯(lián)函數(shù)( ) 。C99和C11標準中敘述的是: “把函數(shù)變成內聯(lián)函數(shù)建議盡可能快地調用該函數(shù), 其具體效果由實現(xiàn)定義”。 因此, 把函數(shù)變成內聯(lián)函數(shù), 編譯器可能會用內聯(lián)代碼替換函數(shù)調用, 并(或) 執(zhí)行一些其他的優(yōu)化, 但是也可能不起作用。
創(chuàng)建內聯(lián)函數(shù)的定義有多種方法。 標準規(guī)定具有內部鏈接的函數(shù)可以成為內聯(lián)函數(shù), 還規(guī)定了內聯(lián)函數(shù)的定義與調用該函數(shù)的代碼必須在同一個文件中。 因此, 最簡單的方法是使用函數(shù)說明符 和存儲類別說明符。 通常, 內聯(lián)函數(shù)應定義在首次使用它的文件中, 所以內聯(lián)函數(shù)也相當于函數(shù)原型。
內聯(lián)函數(shù)應該比較短小。 把較長的函數(shù)變成內聯(lián)并未節(jié)約多少時間, 因為執(zhí)行函數(shù)體的時間比調用函數(shù)的時間長得多。
編譯器優(yōu)化內聯(lián)函數(shù)必須知道該函數(shù)定義的內容。 這意味著內聯(lián)函數(shù)定義與函數(shù)調用必須在同一個文件中。 鑒于此, 一般情況下內聯(lián)函數(shù)都具有內部鏈接。 因此, 如果程序有多個文件都要使用某個內聯(lián)函數(shù), 那么這些文件中都必須包含該內聯(lián)函數(shù)的定義。 最簡單的做法是, 把內聯(lián)函數(shù)定義放入頭文件, 并在使用該內聯(lián)函數(shù)的文件中包含該頭文件即可。
一般都不在頭文件中放置可執(zhí)行代碼, 內聯(lián)函數(shù)是個特例。 因為內聯(lián)函數(shù)具有內部鏈接, 所以在多個文件中定義同一個內聯(lián)函數(shù)不會產生什么問題。
與C++不同的是, C還允許混合使用內聯(lián)函數(shù)定義和外部函數(shù)定義( 具有外部鏈接的函數(shù)定義)。
函數(shù)(C11)
C99新增關鍵字時, 它是唯一的函數(shù)說明符(關鍵字和是存儲類別說明符, 可應用于數(shù)據(jù)對象和函數(shù)) 。 C11新增了第2個函數(shù)說明符, 表明調用完成后函數(shù)不返回主調函數(shù)。 exit()函數(shù)是 函數(shù)的一個示例, 一旦調用exit(), 它不會再返回主調函數(shù)。 注意, 這與void返回類型不同。 void類型的函數(shù)在執(zhí)行完畢后返回主調函數(shù),只是它不提供返回值。
的目的是告訴用戶和編譯器, 這個特殊的函數(shù)不會把控制返回主調程序。
C庫
最初, 并沒有官方的C庫。 后來, 基于UNIX的C實現(xiàn)成為了標準。 ANSIC委員會主要以這個標準為基礎, 開發(fā)了一個官方的標準庫。 在意識到C語言的應用范圍不斷擴大后, 該委員會重新定義了這個庫, 使之可以應用于其他系統(tǒng)。
訪問C庫
不同的系統(tǒng)搜索這些函數(shù)的方法不同。
自動訪問
在一些系統(tǒng)中, 只需編譯程序, 就可使用一些常用的庫函數(shù)。記住, 在使用函數(shù)之前必須先聲明函數(shù)的類型, 通過包含合適的頭文件即可完成。 在描述庫函數(shù)的用戶手冊中, 會指出使用某函數(shù)時應包含哪個頭文件。
文件包含
如果函數(shù)被定義為宏, 那么可以通過# 指令包含定義宏函數(shù)的文件。 通常, 類似的宏都放在合適名稱的頭文件中。 例如, 許多系統(tǒng)(包括所有的ANSI C系統(tǒng)) 都有ctype.h文件, 該文件中包含了一些確定字符性質(如大寫、 數(shù)字等) 的宏。
庫包含
在編譯或鏈接程序的某些階段, 可能需要指定庫選項。 即使在自動檢查標準庫的系統(tǒng)中, 也會有不常用的函數(shù)庫。 必須通過編譯時選項顯式指定這些庫。 注意, 這個過程與包含頭文件不同。 頭文件提供函數(shù)聲明或原型, 而庫選項告訴系統(tǒng)到哪里查找函數(shù)代碼。
使用庫描述
函數(shù)文檔。
可以在多個地方找到函數(shù)文檔。 你所使用的系統(tǒng)可能有在線手冊, 集成開發(fā)環(huán)境通常都有在線幫助。 C實現(xiàn)的供應商可能提供描述庫函數(shù)的紙質版用戶手冊, 或者把這些材料放在CD-ROM中或網(wǎng)上。 有些出版社也出版C庫函數(shù)的參考手冊。 這些材料中, 有些是一般材料, 有些則是針對特定實現(xiàn)的。 本書附錄B中提供了一個庫函數(shù)的總結。
#
fread(ptr, (*ptr), , )
FILE *;
首先, 給出了應該包含的文件, 但是沒有給出fread()、 ptr、 (*ptr)或的類型。 過去, 默認類型都是int, 但是從描述中可以看出ptr是一個指針(在早期的C中, 指針被作為整數(shù)處理) 。 參數(shù)聲明為指向FILE的指針。 上面的函數(shù)聲明中的第2個參數(shù)看上去像是運算符, 而實際上這個參數(shù)的值應該是ptr所指向對象的大小。
#
int fread(ptr, size, , ;)
char *ptr;
int size, ;
FILE *;
現(xiàn)在, 所有的類型都顯式說明, ptr作為指向char的指針。
ANSI C90標準提供了下面的描述:
#
fread(void *ptr, size, nmemb, FILE *);
首先, 使用了新的函數(shù)原型格式。 其次, 改變了一些類型。 類型被定義為 運算符的返回值類型——無符號整數(shù)類型, 通常是或 long。 .h文件中包含了類型的或#定義。其他文件(包括stdio.h) 通過包含.h來包含這個定義。 許多函數(shù)(包括fread()) 的實際參數(shù)中都要使用運算符, 形式參數(shù)的類型中正好匹配這種常見的情況。ANSI C把指向void的指針作為一種通用指針, 用于指針指向不同類型的情況。
數(shù)學庫
數(shù)學庫中包含許多有用的數(shù)學函數(shù)。 math.h頭文件提供這些函數(shù)的原型。 表16.2中列出了一些聲明在 math.h 中的函數(shù)。 注意, 函數(shù)中涉及的角度都以弧度為單位(1 弧度=180/π=57.296 度) 。 參考資料 V“新增C99和C11標準的ANSI C庫”列出了C99和C11標準的所有函數(shù)。
三角問題