1.預處理概述和文件包含命令
前面各章中,已經多次使用過#include
命令。使用庫函數之前,應該用#include
引入對應的頭文件。這種以#
號開頭的命令稱為預處理命令。
C語言源文件要經過編譯、鏈接才能生成可執行程序:
1) 編譯(Compile)會將源文件(.c文件)轉換為目標文件。對於VC/VS,目標文件後綴為 .obj;對於GCC,目標文件後綴為 .o。
編譯是針對單個源文件的,一次編譯操作只能編譯一個源文件,如果程序中有多個源文件,就需要多次編譯操作。2) 鏈接(Link)是針對多個文件的,它會將編譯生成的多個目標文件以及系統中的庫、組件等合並成一個可執行程序。
#
開頭的命令,例如#include
等。預處理命令要放在所有函數之外,而且一般都放在源文件的前面。.i
文件中,例如
main.c 的預處理結果在 main.i 中。和.c
一樣,.i
也是文本文件,可以用編輯器打開直接查看內容。#include
是文件包含命令,主要用來引入對應的頭文件。#include
的處理過程很簡單,就是將頭文件的內容插入到該命令所在的位置,從而把頭文件和當前源文件連接成一個源文件,這與復制粘貼的效果相同。#include使用尖括號#include "myHeader.h"
<
>
和雙引號"
"
的區別在於頭文件的搜索路徑不同,我們將在《C語言頭文件的路徑》一節中深入探討,請大家先記住:包含標准庫的頭文件一般用尖括號,包含自定義的頭文件一般用雙引號。運行結果:
- #include
- #define N 100
- int main(){
- int sum = 20 + N;
- printf("%d\n", sum);
- return 0;
- }
int
sum = 20 + N;
,N
被100
代替了。#define
N 100
就是宏定義,N
為宏名,100
是宏的內容。在編譯預處理時,對程序中所有出現的“宏名”,都用宏定義中的字符串去代換,這稱為“宏代換”或“宏展開”。#define
完成的,宏代換是由預處理程序完成的。#define 宏名 字符串
#
表示這是一條預處理命令,所有的預處理命令都以#開頭。define
是預處理命令。宏名
是標識符的一種,命名規則和標識符相同。字符串
可以是常數、表達式等。
這裡所說的字符串是一般意義上的字符序列,不要和C語言中的字符串等同,它不需要雙引號。程序中反復使用的表達式就可以使用宏定義,例如:
#define M (n*n+3*n)它的作用是指定標識符
M
來代替表達式(y*y+3*y)
。在編寫源程序時,所有的(y*y+3*y)都可由M代替,而對源程序編譯時,將先由預處理程序進行宏代換,即用(y*y+3*y)表達式去替換所有的宏名M,然後再進行編譯。運行結果:
- #include
- #define M (n*n+3*n)
- int main(){
- int sum, n;
- printf("Input a number: ");
- scanf("%d", &n);
- sum = 3*M+4*M+5*M;
- printf("sum=%d\n", n);
- return 0;
- }
sum=3*M+4*M+5*M
中作了宏調用。在預處理時經宏展開後該語句變為:
sum=3*(n*n+3*n)+4*(n*n+3*n)+5*(n*n+3*n);需要注意的是,在宏定義中表達式
(n*n+3*n)
兩邊的括號不能少,否則會發生錯誤。如當作以下定義後:
#difine M n*n+3*n在宏展開時將得到下述語句:
s=3*n*n+3*n+4*n*n+3*n+5*n*n+3*n;這相當於: 3n2+3n+4n2+3n+5n2+3n 這顯然是不正確的。所以進行宏定義時要注意,應該保證在宏代換之後不發生錯誤。
#undef
命令。例如:
表示PI只在main函數中有效,在func中無效。
- #define PI 3.14159
- int main(){
- // Code
- return 0;
- }
- #undef PI
- void func(){
- // Code
- }
運行結果:
- #include
- #define OK 100
- int main(){
- printf("OK\n");
- return 0;
- }
#define PI 3.1415926 #define S PI*y*y /* PI是已定義的宏名*/對語句:
printf("%f", S);在宏代換後變為:
printf("%f", 3.1415926*y*y);
#define UINT unsigned int在程序中可用UINT作變量說明:
UINT a, b;應注意用宏定義表示數據類型和用typedef定義數據說明符的區別。宏定義只是簡單的字符串代換,是在預處理完成的,而typedef是在編譯時處理的,它不是作簡單的代換,而是對類型說明符重新命名。被命名的標識符具有類型定義說明的功能。
#define PIN1 int * typedef (int *) PIN2;從形式上看這兩者相似, 但在實際使用中卻不相同。
PIN1 a,b;在宏代換後變成:
int *a,b;表示a是指向整型的指針變量,而b是整型變量。然而:
PIN2 a,b;表示a、b都是指向整型的指針變量。因為PIN2是一個類型說明符。由這個例子可見,宏定義雖然也可表示數據類型, 但畢竟是作字符代換。在使用時要分外小心,以避出錯。
#define M(y) y*y+3*y //宏定義 // Code k=M(5); //宏調用在宏調用時,用實參5去代替形參y,經預處理宏展開後的語句為
k=5*5+3*5
。運行結果:
- #include
- #define MAX(a,b) (a>b) ? a : b
- int main(){
- int x , y, max;
- printf("input two numbers: ");
- scanf("%d %d", &x, &y);
- max = MAX(x, y);
- printf("max=%d\n", max);
- return 0;
- }
MAX
表示條件表達式(a>b)
? a : b
,形參a、b均出現在條件表達式中。程序第7行max=MAX(x,
y)
為宏調用,實參x、y,將代換形參a、b。宏展開後該語句為:
max=(x>y) ? x : y;用於計算x、y中的大數。
#define MAX(a,b) (a>b)?a:b寫為:
#define MAX (a,b) (a>b)?a:b將被認為是無參宏定義,宏名MAX代表字符串
(a,b)
(a>b)?a:b
。宏展開時,宏調用語句:
max=MAX(x,y);將變為:
max=(a,b)(a>b)?a:b(x,y);這顯然是錯誤的。
運行結果:
- #include
- #define SQ(y) (y)*(y)
- int main(){
- int a, sq;
- printf("input a number: ");
- scanf("%d", &a);
- sq = SQ(a+1);
- printf("sq=%d\n", sq);
- return 0;
- }
sq=(a+1)*(a+1);這與函數的調用是不同的,函數調用時要把實參表達式的值求出來再賦予形參,而宏代換中對實參表達式不作計算直接地照原樣代換。
運行結果為:
- #include
- #define SQ(y) y*y
- int main(){
- int a, sq;
- printf("input a number: ");
- scanf("%d", &a);
- sq = SQ(a+1);
- printf("sq=%d\n", sq);
- return 0;
- }
sq=a+1*a+1;由於a為9故sq的值為19。這顯然與題意相違,因此參數兩邊的括號是不能少的。即使在參數兩邊加括號還是不夠的,請看下面程序:
本程序與前例相比,只把宏調用語句改為:
- #include
- #define SQ(y) (y)*(y)
- int main(){
- int a,sq;
- printf("input a number: ");
- scanf("%d", &a);
- sq = 200 / SQ(a+1);
- printf("sq=%d\n", sq);
- return 0;
- }
sq=160/SQ(a+1);運行本程序如輸入值仍為9時,希望結果為2。但實際運行的結果如下:
sq=200/(a+1)*(a+1);a為9時,由於“/”和“*”運算符優先級和結合性相同,則先作200/(9+1)得20,再作20*(9+1)最後得200。為了得到正確答案應在宏定義中的整個字符串外加括號,程序修改如下:
由此可見:對於宏定義不僅應在參數兩側加括號,也應在整個字符串外加括號。
- #include
- #define SQ(y) ((y)*(y))
- int main(){
- int a,sq;
- printf("input a number: ");
- scanf("%d", &a);
- sq = 200 / SQ(a+1);
- printf("sq=%d\n", sq);
- return 0;
- }
運行結果:
- #include
- int SQ(int y){
- return ((y)*(y));
- }
- int main(){
- int i=1;
- while(i<=5){
- printf("%d^2 = %d\n", (i-1), SQ(i++));
- }
- return 0;
- }
VC 6.0下運行結果:
- #include
- #define SQ(y) ((y)*(y))
- int main(){
- int i=1;
- while(i<=5){
- printf("%d^2 = %d\n", i, SQ(i++));
- }
- return 0;
- }
之所以出現不同的結果,與 printf() 參數列表中表達式的計算順序和優先級有關,這裡不再深究。分析如下:在示例①中,函數調用是把實參 i 值傳給形參 y 後自增 1,然後輸出函數值,所以要循環5次,輸出1~5的平方值。而在示例②中宏調用時只作代換,SQ(i++) 被代換為 ((i++)*(i++))。第一次循環,i 的值為1,(i++)*(i++)=1;第二次循環 i 的值為 3,(i++)*(i++)=9;第三次循環 i 的值為 5,(i++)*(i++)=25;第四次循環,i 的值為7,終止循環。
運行結果:
- #include
- #define SSSV(s1, s2, s3, v) s1=l*w; s2=l*h; s3=w*h; v=w*l*h;
- int main(){
- int l=3, w=4, h=5, sa, sb, sc, vv;
- SSSV(sa, sb, sc, vv);
- printf("sa=%d\nsb=%d\nsc=%d\nvv=%d\n", sa, sb, sc, vv);
- return 0;
- }
#
和##
兩個符號,它們能夠對宏參數進行操作。
#
用來將宏參數轉換為字符串,也就是在宏參數的開頭和末尾添加引號。例如有如下宏定義:
#define STR(s)那麼:
printf("%s", STR(c.biancheng.net)); printf("%s", STR("c.biancheng.net"));分別被展開為:
printf("%s", "c.biancheng.net"); printf("%s", "\"c.biancheng.net\"");可以發現,即使給宏參數“傳遞”的數據中包含引號,使用
#
仍然會在兩頭添加新的引號,而原來的引號會被轉義。運行結果:
- #include
- #define STR(s) #s
- int main() {
- printf("%s\n", STR(c.biancheng.net));
- printf("%s\n", STR("c.biancheng.net"));
- return 0;
- }
##
稱為連接符,用來將宏參數或其他的串連接起來。例如有如下的宏定義:
#define CON1(a, b) a##e##b #define CON2(a, b) a##b##00那麼:
printf("%f\n", CON1(8.5, 2)); printf("%d\n", CON2(12, 34));將被展開為:
printf("%f\n", 8.5e2); printf("%d\n", 123400);將上面的例子補充完整:
運行結果:
- #include
- #define CON1(a, b) a##e##b
- #define CON2(a, b) a##b##00
- int main() {
- printf("%f\n", CON1(8.5, 2));
- printf("%d\n", CON2(12, 34));
- return 0;
- }
VS下的輸出結果:
- #include
- #include
- int main() {
- printf("Date : %s\n", __DATE__);
- printf("Time : %s\n", __TIME__);
- printf("File : %s\n", __FILE__);
- printf("Line : %d\n", __LINE__);
- system("pause");
- return 0;
- }
運行結果:
- #include
- #define WIN16 true
- int main(void){
- #ifdef WIN16
- printf("The value of sizeof(int) is 2.\n");
- #else
- printf("The value of sizeof(int) is 4.\n");
- #endif
- return 0;
- }
#define WIN16也具有同樣的意義。只有取消程序的第2行才會去編譯第二個 printf 語句。
ifdef
改為ifndef
。它的功能是,如果標識符未被#define命令定義過則對程序段1進行編譯,否則對程序段2進行編譯。這與第一種形式的功能正相反。運行結果:
- #include
- #define R 1
- int main(){
- float len, area_round, area_square;
- printf ("input a number: ");
- scanf("%f", &len);
- #if R
- area_round = 3.14159*len*len;
- printf("Area of round is: %f\n", area_round);
- #else
- area_square = len*len;
- printf("Area of square is: %f\n", area_square);
- #endif
- return 0;
- }
#error error_message例如,我們的程序針對Linux編寫,不保證兼容Windows,那麼可以這樣做:
WIN32 是Windows下的預定義宏。當用戶在Windows下編譯該程序時,由於定義了WIN32這個宏,所以會執行
- #ifdef WIN32
- #error This programme cannot compile at Windows Platform
- #endif
#error
命令,提示用戶發生了編譯錯誤,錯誤信息是:
This programme cannot compile at Windows Platform
這和發生語法錯誤的效果是一樣的,程序編譯失敗。請看下面的截圖:"
"
,如果加上,引號會被一起輸出。例如將上面的#error命令改為:
#error "This programme cannot compile at Windows Platform"那麼錯誤信息如下:
復制純文本新窗口
- #ifndef __cplusplus
- #error 當前程序必須以C++方式編譯
- #endif
#
號開頭的代碼行,#號必須是該行除了任何空白字符外的第一個字符。#後是指令關鍵字,在關鍵字和#號之間允許存在任意個數的空白字符,整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對源代碼做某些轉換。