在C程序中,以 # 開頭的命令就是預處理命令,這些命令都是放在函數之外,而且一般都放在源文件的前面,如下面的兩條命令:
#include <stdio.h>
#define PI 3.1415926
宏可以看做是一些命令的集合,在預編譯階段將 宏名 展開替換為后面的替換體. 在 c 語言中用 #define 來定義宏,格式如下:
#define macro_name(var_list) stuff
// macro_name 為宏名,va_list 為宏的參數,參數可選
// stuff 為宏的替換體,是一個字符串,也是可選的
// var_list 與 macro_name 之間不能有空格
// stuff 與 macro_name 之間用空格隔開
看下面例子:
對于宏定義,以下幾點需要注意:
1)宏定義只是簡單用宏名來表示一個字符串,字符串中可以含任何字符,可以是常數、也可以是表達式,預處理程序不對它進行任何檢查
2)宏定義不是說明或語句,在行末尾不必加上分號,如果加上分號會連分號一起置換
3)宏定義必須寫在函數之外,其作用域為從宏定義命令到源程序結束,如果要提前終止其作用可以使用 #undef 命令,也就是說宏定義命令的作用域為:
#define 宏
....作用域
#undef 宏
4)宏名如果在源程序中使用引號括起來,則預處理程序不會對宏進行替換,如下:
5)宏定義允許嵌套
6)習慣上宏名用大寫字母表示,如上面的PI
7)可以用宏定義表示數據類型,使程序書寫方便,如下:
#define INTEGER int
注意區別宏定義和 typedef 說明符:
宏定義只是簡單的字符串替換,是在預處理階段完成的,而 typedef 是在編譯時處理的,它不是簡單的替換而是對類型說明符進行重命名,被命名的標識符具有類型定義說明的功能;
來看下面的例子:
由于 define 僅僅是做字符串的替換,所以23行相當于:
char* x, y;
x為指針類型,而 y 為char 類型,所以打印的結果如上;而 typedef 是對類型說明符進行重命名,所以24行相當于:
char *a, *b;
所以a和b都是指針類型;注意,對比18和19行可以看到,宏定義語句后不帶分號,而 typedef 語句后帶分號,這也是二者重要的區別;
8)宏的字符串可以使用 來續行,目的是增加代碼的可讀性,如下:
#define PFINT printf("hello world!n");
printf("goodbye world!n");
帶參數的宏定義
C語言中允許宏帶有參數,在宏定義中的參數稱為形式參數,在宏調用中的參數稱為實際參數;
在調用帶參數的宏是,不僅宏要展開,而且要用實參去代換形參,帶參宏的定義的一般形式為:
#define 宏名(形參表) 字符串
調用帶參宏的形式:
宏名(實參列表);
上面的第8行調用了帶參宏,相當于:max = (x>y)?x:y;打印結果如下:
對于帶參宏,以下幾點需要特別注意:
1)在定義帶參宏時,宏名和形參列表之間不能存在空格
例如把上面的第2行寫成下面的形式是錯誤的:
#define MAX (a, b) (a>b)?a:b
// 此時如果引用宏 MAX,會將 (a, b) (a>b)?a:b 一起引用
2)在帶參宏的定義時,形參不分配內存單元,因此不必作類型定義;而宏調用時,實參要用具體的值去代換形參,因此必須作類型說明;
這一點與函數不同,函數的形參和實參是兩個不同的量,有各自的作用域,調用時需要把實參通過“值傳遞”的方式傳遞給形參,而在帶參宏中,僅僅是符號代換,不存在值傳遞;
3)在宏定義中,形參是標識符,而宏調用過程中的實參可以是表達式;如下:
4)在宏定義中,字符串內的形參通常需要用括號括起來以避免出錯,來看下面的例子:
如果將括號去掉,改為如下:
此時第22行的宏調用時,替換后的結果如上面的注釋所示,所以打印結果如下:
所以,不建議用宏來替換表達式,這樣的結果很可能會出錯!!!
條件編譯
c語言中不支持嵌套注釋,比如下面的例子:
/*
這是注釋
/*
這也是注釋
*/
*/
這個時候就可以使用條件編譯,它包括以下幾種方式:
第一種形式:
#ifdef 標識符
程序段1
#else
程序段2
#endif
如果標識符已經被 #define 命令定義過則執行程序段1,否則執行程序段2;也可以是下面的形式:
#ifdef 標識符
程序段1
#endif
第二種形式:
#ifndef 標識符
程序段1
#else
程序段2
#endif
第三種形式:
#ifdef 常量表達式
程序段1
#else
程序段2
#endif
#if 常量表達式
//...
#endif
//常量表達式由預處理器求值
預定義符號
預定義符號都是語言內置的,可以認為是系統預定義的宏,主要包括:
__FILE__ //進行編譯的源文件
__LINE__ //文件當前的行號
__DATE__ //文件被編譯的日期
__TIME__ //文件被編譯的時間
__STDC__ //如果編譯器遵循ANSI C,其值為1,否則未定義