第十六章 C 預處理器和 C 庫
16.1 翻譯程序的第一步
- 源代碼中的字符映射到源字符集。
- 編譯器定位每個反斜杠后面跟著換行符的實例赘阀,并刪除它們赎婚。
- 編譯器把文本化糞池預處理記號序列、空白序列和注釋序列
- 一個空格替換所有空白序列和注釋序列
- 開始預處理
16.2 明示常量:#define
指令可以出現(xiàn)在源文件任何地方,其定義從指令出現(xiàn)的地方到文件末尾有效沮尿。
格式:
define 宏 替換列表
有類對象宏,類函數(shù)宏较解。從宏變成最終替換文本的過程稱為宏展開(macro expansion
)畜疾。
從技術角度來看,可以把宏的替換體看作是記號型字符串印衔,而不是字符型字符串啡捶,并不是簡單的純文本替換。
16.3 在 #define 中使用參數(shù)
示例:
#define MEAN(X,Y) (((X)+(Y))/2)
# 運算符
當 x
是個宏參數(shù)時奸焙,#x
就是轉(zhuǎn)化為字符串 "x"
的形參名瞎暑。稱為字符串化(stringizing
)。
#define PSQR(x) printf("The square of " #x " is %d.\n, ((x)*(x)))
## 運算符
把兩個記號進行拼接組成一個記號
#define XNAME(n) X##n
變參宏:... 和 VA_ARGS
一些函數(shù)接收數(shù)量可變的參數(shù)与帆。通過把宏參數(shù)列表中最后的參數(shù)寫成省略號來實現(xiàn)這一功能了赌。
#define PR(...) printf(__VA_ARGS__)
16.4 宏和函數(shù)的選擇
宏函數(shù)優(yōu)點:
- 執(zhí)行效率更高。
- 不用擔心變量類型玄糟。
注意點:
- 宏名中不允許有空格勿她。
- 用圓括號把宏的參數(shù)和整個替換體括起來。防止運算符優(yōu)先級導致不符合預期的結(jié)果茶凳。
- 用大寫字母表示宏函數(shù)的名稱嫂拴。提醒該為宏函數(shù)播揪。
- 如果打算使用宏函數(shù)來加快程序的運行速度贮喧,那么首先要確認使用宏和使用函數(shù)是否會導致較大差異。在程序中值使用一次的宏無法明顯減少程序的運行時間猪狈。
16.5 文件包含:#include
頭文件常用形式:
- 明示常量
- 宏函數(shù)
- 函數(shù)聲明
- 結(jié)構(gòu)模板定義
- 類型定義
16.6 其他指令
#undef
取消已定義的 #define
指令
**#ifdef箱沦、#else 和 #endif **
條件編譯
#ifndef
#if 和 #elif
預定義宏
宏 | 含義 |
---|---|
_DATA_ | 預處理的預期 |
_FILE_ | 當前源代碼文件名的字符串字面量 |
_LINE_ | 當前源代碼文件中行號的整型常量 |
_STDC_ | 設置為 1 時,表明實現(xiàn)遵循 C 標準 |
_STDC_HOSTED_ | 本即環(huán)境設置為 1雇庙;否則設置為 0 |
_STDC_VERSION_ | 支持 C99 標準谓形,設置為 19990L灶伊;支持 C11 標準,設置為 201112L |
_TIME_ | 翻譯代碼的時間 |
_func_ 是預定義標識符寒跳,而不是預定義宏聘萨。
#line
重置 __LINE__
和 __FILE__
宏報告的行號和文件名
#line 10 "cool.c"
#error
讓編譯器發(fā)出一條錯誤消息,該消息包含指令中的文本童太。
#pragma
把編譯器指令放入源代碼中米辐。
C99
還提供 _Pragma
預處理器運算符,把字符串轉(zhuǎn)換成普通的編譯指示书释。
_Pragma("nonstandaertreatmenttypeB on")
/* 等價于 */
#pragma nonstandaertreatmenttypeB on
泛型選擇(C11)
泛型編程(generic programming):指那些沒有特定類型翘贮,但是一旦指定一種類型,就可以轉(zhuǎn)換成指定類型的代碼爆惧。
泛型選擇表達式(generic selection expression):根據(jù)表達式的類型選擇一個指狸页。常和 #define
一起使用。
_Generic(x, int:0, float:1, double:2, defult:3)
類似 switch
語句扯再,根據(jù) x 的類型決定整個表達式的值芍耘。為 int
時值 0
,為 float
時值為 1
...
#define MYTYPE(X) \
_Generic((X), \
int: "int", \
float: "float", \
double: "double", \
default: "other" \
)
16.7 內(nèi)聯(lián)函數(shù)(C99)
把函數(shù)變成內(nèi)聯(lián)函數(shù)建議盡可能快地調(diào)用該函數(shù)熄阻,其具體效果由實現(xiàn)定義齿穗。
只有具有內(nèi)部鏈接的函數(shù)可以成為內(nèi)聯(lián)函數(shù),因此內(nèi)聯(lián)函數(shù)可能會放到頭文件中饺律。
inline static void eatline(void)
{
while(getchar() != '\n')
continue;
}
C
允許混合使用內(nèi)聯(lián)函數(shù)定義和外部函數(shù)定義窃页,不建議如此。
16.8 _Noreturn 函數(shù)(C11)
告訴用戶和編譯器复濒,該函數(shù)不會把控制返回主調(diào)程序脖卖,避免濫用該函數(shù),通知編譯器優(yōu)化一些代碼巧颈。
16.9 C 庫
16.10 通用工具庫
stdlib.h
int atexit (void (func)(void));*
注冊回調(diào)函數(shù)畦木,在程序退出時執(zhí)行。至少可以注冊 32
個函數(shù)砸泛,后進的函數(shù)先執(zhí)行十籍。
void exit (int status)
退出整個程序
- 刷新所有輸出流
- 關閉所有打開的流
- 關閉標準 I/O 函數(shù) tmpfile() 創(chuàng)建的臨時文件。
- 控制權返回主機環(huán)境唇礁,如果可以的話勾栗,向主機報告終止狀態(tài)。UNIX 程序通常使用
0
表示成功終止盏筐,非零值表示終止失敗围俘。
16.12 斷言庫
void assert (int expression)
接受一個整型表達式作為參數(shù)。如果表達式為假(非零),assert() 宏就在標準錯誤流中寫入一條錯誤信息界牡,并調(diào)用 abort() 函數(shù)終止程序簿寂。
_Static_assert
靜態(tài)斷言。
16.13 string.h 庫中的 memcyp() 和 memmove()
void *memcpy(void* restrict s1, const void* restrict s2宿亡,size_t n)
void *memmove(void *sl常遂,const void *s2,size_t n)
memcopy
假設沒有內(nèi)存重疊挽荠,而 memmove
沒有烈钞。
16.14 可變參數(shù):stdarg.h
創(chuàng)建可變參數(shù)函數(shù)步驟
- 提供一個使用省略號的函數(shù)原型;
- 在函數(shù)定義中創(chuàng)建一個 va_list 類型的變量坤按;
- 用宏把該變量初始化為一個參數(shù)列表毯欣;
- 用宏訪問參數(shù)列表;
- 用宏完成清理工作臭脓。
double sum(int lm, ...)
{
va_list ap;
double tot = 0;
int i;
va_start(ap, lim);
for(i = 0; i < lim; ++i)
tot += va_arg(ap, double);
va_end(ap);
return tot;
}