編譯程式之前,先由前處理器檢查程式。根據程式中使用的前處理器指令,前處理器用符號縮略語所代表的內容替換程式中的縮略語。
1.編譯器的處理步驟:
- 第一步:翻譯處理:工作內容(1)把原始碼中出現的字元對映到源字符集。
(2)編譯器查詢反斜線後面緊跟換行符的例項並刪除這些例項。將多個物理行替換為一個邏輯行。
(3)編譯器將文字劃分為預處理的語言符號(token)序列和空白字元及註釋序列(編譯器會將註釋序列替換為一個空格字元。所以註釋序列可以出現在原始碼的任何地方)。
- 第二步:預處理:
- 預處理的格式:ANSI標準允許#符合前後有空格或製表符,還允許在#和指令的其餘部分之間有空格。但是舊版本均不允許這些空格出現。
- 預處理範圍:預處理指令從#開始,到其後第一個換行符為止。所以預處理指令不允許換行。所以可以使用反斜線和換行符組合使預處理指令跨越多行。
- 系統把巨集的主體定義為語言符號(Token)型別字串。而不是字元型字串。Token是指由空格符隔開的單詞。“2*3”是一個Token,“2 * 3”是三個Token組成的字串。
- 第三步:把原始碼翻譯為機器語言。第一步和第二步只是對原始碼檔案進行文字處理,進行字串方面的替換。真正的根據語義翻譯為機器語言是在這步。
2.前處理器(preprocessor)種類
前處理器可以使用反斜線和換行符組合可以橫跨多個物理行,但是其最終只佔用一個邏輯行。
第一部分:#define定義常量
- #define :格式:#define macro body
- macro是被稱為巨集的縮略語,其是一個根據變數命名規則的命名的單詞。
- body被稱為實體。可以是任何字串。前處理器將程式中巨集例項替換為實體。
- #define使用引數:#define macro body 其中marco的格式類似函式.由name(args)組成。但是name和(之間不能有空格。括號內部和body一樣,可以有空格)
- 注意在body中的引數和body都要用括號括起來。如
#define ADD(X, Y) X + Y //compiled rightly, but not safe enough #define ADD(X, Y) ((X) + (Y)) //good #define ADD (X, Y) ((X) + (Y)) //error! No spaces between ADD and (X, Y)
- 注意在body中的引數和body都要用括號括起來。如
- #運算子:常量字串中和引數相同的字元子串不會被替換為引數的字串形式。如果想在常量字串中使用引數字串,那麼ANSI C允許在body中使用#符號。如果x為引數名,那麼#x就是相應的“x”。
- ##運算子:#是獲取引數的字串形式,而##是將兩個Token拼接起來得到一個新的Token.
如 #define NAME(N) x N 中x N就是空格符隔開的兩個token。但是如果#define NAME(N) x ## N 就是將NAME(N)替換為xN.
- 總之,#用來拼接字元型字串,##用來拼接語言符號型符號串。
- 可變巨集:…和__VA_ARGS__
- 函式巨集中引數列表的最後一個引數為省略號(…)。預定義巨集__VA_ARGS__用在body中代表省略號代表什麼。在巨集擴充中,將__VA_ARGS__替換為…位置實際上的引數。注意,省略號只能替代最後的巨集引數。
- 總結:
- 1,巨集的名字中macro名字部分不能有空格,但是在body替代字串中可以使用空格。在引數列表中可以使用空格。
- 2,在body部分,用圓括號括住每個引數,並括住巨集的整體定義。
第二部分:檔案包含 #include
- #include <XX.h> 優先搜尋系統目錄。 #include “XX.h”優先搜尋當前目錄
- 標頭檔案中可以包含:
- 1.明顯常量,如stdio.h中的EOF,NULL和BUFFSIZE 2.巨集函式 3.函式宣告
- 4.結構模板定義。如stdio.h中的FILE結構定義。5.型別定義,使用#define或者typedef。
#ifndef _FILE_DEFINED #define _FILE_DEFINED typedef struct _iobuf { char* _ptr; int _cnt; char* _base; int _flag; int _file; int _charbuf; int _bufsiz; char* _tmpfname; } FILE; #endif /* Not _FILE_DEFINED */
-
6,如果某個變數要多個檔案共享。則可以在標頭檔案中進行引用宣告。
extern int status; //標頭檔案中 int status; //原始檔中
-
7,如果某個const常量需要多個檔案共享。則可採用在標頭檔案中宣告static const int MAX = 100;
-
8,行內函數,inline function。因為編譯器優化時,必須知道函式定義的內容。所以原始檔呼叫的行內函數的定義,必須在此原始檔中。inline 函式具有內部連結,可以將inline function定義放在標頭檔案中。但是普通函式具有外部連結,則只能有一次定義。
第三部分:其它指令。因為系統變數多用巨集定義。所以它們多用於更改巨集定義。改變編譯時編譯環境,方便移植系統。
-
#undef指令:取消定義一個給定的#define,即使該巨集之前未被定義。可用於重定義某個巨集。
-
條件編譯指令:#if、#ifdef、#ifndef、#else、#elif、#endif
- #ifdef、#ifndef後面跟上識別符號
,判斷該識別符號是否已經被#define或者是否還未被#define定義。#ifndef多用於防止多次包含同一標頭檔案。#else作用類似if-else語句中的else,選擇不同塊。
- #if後面跟的是常量整數表示式和邏輯表示式。
如果表示式非零或者為真,則為真。#if 可以和defined運算子結合。defined(X),如果識別符號X已經定義,返回1,否則返回0.#if defined優點為可以和#elif結合。
-
#if、#ifndef、#ifdef後面必須有對應的#endif。這樣相當於if-else語句中的{},劃分出指令的作用範圍。
- #ifdef、#ifndef後面跟上識別符號
-
#line :指令用於重置由__LINE__和__FILE__巨集報告的行號和檔名 。#error:指令使前處理器發出一條錯誤訊息,該訊息包含指令中的文字。#warning:指令是前處理器發出一條警告訊息,該訊息包含指令中的文字。
-
#line 100 //將__LINE__設定為100 #line 100 "new.c" //將__LINE__設定為100, 檔名設定為"new.c" #line "new.c" //錯誤,#line引數只能為上述兩種形式 #error This is a example.//編譯器編譯到這句會報錯。 #warning This is a warning//編譯器編譯到這句,會發出警告
第四部分:重要的C庫
-
通用工具庫:#include <stdlib.h>
-
快排函式qsort() 函式原型:void qsort(void *base, size_t nmemb, size_t size, int (*cmpar)(const void *, const void *));
-
ANSI C支援將任何資料型別指標轉換為void型別指標。第一個引數為要排序的陣列頭部的指標,第二個引數為參與排序的專案數量。因為第一個引數為void *,所以第三個引數為資料物件的大小。第三個為排序函式,返回值分別為正,零,負,代表大,相等,小於。