宏定義

C++語言中用到宏定義的地方很多,如在頭文件中為了防止頭文件被重復(fù)包含,則用到:

#ifndef cTest_Header_h
#define cTest_Header_h
//頭文件內(nèi)容
#endif

從開始寫C++語言到生成執(zhí)行程序的流程大致如下(姑且忽略預(yù)處理之前的編譯器的翻譯處理流程等),在進(jìn)行編譯的第一次掃描(詞法掃描和語法分析)之前,會有由預(yù)處理程序負(fù)責(zé)完成的預(yù)處理工作。


預(yù)處理

預(yù)處理工作是系統(tǒng)引用預(yù)處理程序?qū)υ闯绦蛑械念A(yù)處理部分做處理,而預(yù)處理部分是指以“#”開頭的、放在函數(shù)之外的、一般放在源文件的前面的預(yù)處理命令,如:包括命令 #include,宏命令 #define 等,合理地利用預(yù)處理功能可以使得程序更加方便地閱讀、修改、移植、調(diào)試等,也有利于模塊化程序設(shè)計(jì)。本文主要介紹宏定義的以下幾個(gè)部分:

1、概念及無參宏

一種最簡單的宏的形式如下:

  一種最簡單的宏的形式如下:
  #define   宏名   替換文本

每個(gè)#define行(即邏輯行)由三部分組成:第一部分是指令 #define 自身,“#”表示這是一條預(yù)處理命令,“define”為宏命令。第二部分為宏(macro),一般為縮略語,其名稱(宏名)一般大寫,而且不能有空格,遵循C變量命令規(guī)則。“替換文本”可以是任意常數(shù)、表達(dá)式、字符串等。在預(yù)處理工作過程中,代碼中所有出現(xiàn)的“宏名”,都會被“替換文本”替換。這個(gè)替換的過程被稱為“宏代換”或“宏展開”(macro expansion)?!昂甏鷵Q”是由預(yù)處理程序自動完成的。在C語言中,“宏”分為兩種:無參數(shù) 和 有參數(shù)。

無參宏是指宏名之后不帶參數(shù),上面最簡單的宏就是無參宏。

#define M 5                // 宏定義
#define PI 3.14            //宏定義
int a[M];                  // 會被替換為: int a[5];
int b = M;                 // 會被替換為: int b = 5;
printf("PI = %.2f\n", PI); // 輸出結(jié)果為: PI = 3.14

注意宏不是語句,結(jié)尾不需要加“;”,否則會被替換進(jìn)程序中,如:

#define N 10;               // 宏定義
int c[N];                   // 會被替換為: int c[10;]; 
//error:… main.c:133:11: Expected ']'

以上幾個(gè)宏都是用來代表值,所以被成為類對象宏(object-like macro,還有類函數(shù)宏,下面會介紹)。

如果要寫宏不止一行,則在結(jié)尾加反斜線符號使得多行能連接上,如:

#define HELLO "hello \
the world"

注意第二行要對齊,否則,如:

#define HELLO "hello the wo\
  rld"
printf("HELLO is %s\n", HELLO);
//輸出結(jié)果為: HELLO is hello the wo  rld 

也就是行與行之間的空格也會被作為替換文本的一部分

而且由這個(gè)例子也可以看出:宏名如果出現(xiàn)在源程序中的“”內(nèi),則不會被當(dāng)做宏來進(jìn)行宏代換。

宏可以嵌套,但不參與運(yùn)算:

#define M 5                 // 宏定義
#define MM M * M            // 宏的嵌套
printf("MM = %d\n", MM);    // MM 被替換為: MM = M * M, 然后又變成 MM = 5 * 5

宏代換的過程在上句已經(jīng)結(jié)束,實(shí)際的 5 * 5 相乘過程則在編譯階段完成,而不是在預(yù)處理器工作階段完成,所以宏不進(jìn)行運(yùn)算,它只是按照指令進(jìn)行文字的替換操作。再強(qiáng)調(diào)下,宏進(jìn)行簡單的文本替換,無論替換文本中是常數(shù)、表達(dá)式或者字符串等,預(yù)處理程序都不做任何檢查,如果出現(xiàn)錯(cuò)誤,只能是被宏代換之后的程序在編譯階段發(fā)現(xiàn)。

宏定義必須寫在函數(shù)之外,其作用域是 #define 開始,到源程序結(jié)束。如果要提前結(jié)束它的作用域則用 #undef 命令,如:

#define M 5                 // 宏定義
printf("M = %d\n", M);      // 輸出結(jié)果為: M = 5
#define M 100               // 取消宏定義
printf("M = %d\n", M);      // error:… main.c:138:24: Use of undeclared identifier 'M'

也可以用宏定義表示數(shù)據(jù)類型,可以使代碼簡便:

#define STU struct Student      // 宏定義STU
struct Student{                 // 定義結(jié)構(gòu)體Student
    char *name;
    int sNo;
};
STU stu = {"Jack", 20};         // 被替換為:struct Student stu = {"Jack", 20};
printf("name: %s, sNo: %d\n", stu.name, stu.sNo);

如果重復(fù)定義宏,則不同的編譯器采用不同的重定義策略。有的編譯器認(rèn)為這是錯(cuò)誤的,有的則只是提示警告。Xcode中采用第二種方式。如:

#define M 5                 //宏定義
#define M 100               //重定義,warning:… main.c:26:9: 'M' macro redefined

這些簡單的宏主要被用來定義那些顯式常量(Manifest Constants)(Stephen Prata,2004),而且會使得程序更加容易修改,特別是某一常量的值在程序中多次被用到的時(shí)候,只需要改動一個(gè)宏定義,則程序中所有出現(xiàn)該變量的值都可以被改變。而且宏定義還有更多其他優(yōu)點(diǎn),如使得程序更容易理解,可以控制條件編譯等。

#define 與 #typedef 的區(qū)別

兩者都可以用來表示數(shù)據(jù)類型,如:

#define INT1 int
typedef int INT2;

兩者是等效的,調(diào)用也一樣:

INT1 a1 = 3;
INT2 a2 = 5;

但當(dāng)如下使用時(shí),問題就來了:

#define INT1 int *
typedef int * INT2;
INT1 a1, b1;
INT2 a2, b2;
b1 = &m;         //... main.c:185:8: Incompatible pointer to integer conversion assigning to 'int' from 'int *'; remove &
b2 = &n;         // OK

因?yàn)?INT1 a1, b1; 被宏代換后為: int * a1, b1;即定義的是一個(gè)指向int型變量的指針 a1 和一個(gè)int型的變量b1.而INT2 a2, b2;表示定義的是兩個(gè)變量a2和b2,這兩個(gè)變量的類型都是INT2的,也就是int *的,所以兩個(gè)都是指向int型變量的指針。

所以兩者區(qū)別在于,宏定義只是簡單的字符串代換,在預(yù)處理階段完成。而typede不是簡單的字符串代換,而是可以用來做類型說明符的重命名的,類型的別名可以具有類型定義說明的功能,在編譯階段完成的。

2、有參宏

C++中宏是可以有參數(shù)的,這樣的宏就成了外形與函數(shù)相似的類函數(shù)宏(function-like macro),如:

而且和無參宏不同的一點(diǎn)是,有參宏在調(diào)用中,不僅要進(jìn)行宏展開,而且還要用實(shí)參去替換形參。如:

#define M 5                          //無參宏
#define COUNT(M) M * M               //有參宏
printf("COUNT = %d\n", COUNT(10));   // 替換為: COUNT(10) = 10 * 10
                                     // 輸出結(jié)果: COUNT = 100

這看上去用法與函數(shù)調(diào)用類似,但實(shí)際上是有很大差別的。如:

#define COUNT(M) M * M               //定義有參宏
int x = 6;
printf("COUNT = %d\n", COUNT(x + 1));// 輸出結(jié)果: COUNT = 13
printf("COUNT = %d\n", COUNT(++x));  // 輸出結(jié)果: COUNT = 56                                                                                               //warning:... main.c:161:34: Multiple unsequenced             modifications to 'x'

這兩個(gè)結(jié)果和調(diào)用函數(shù)的方法的結(jié)果差別很大,因?yàn)槿绻窍窈瘮?shù)那樣的話,COUNT(x + 1)應(yīng)該相當(dāng)于COUNT(7),結(jié)果應(yīng)該是 7 * 7 = 49,但輸出結(jié)果卻是21。原因在于,預(yù)處理器不進(jìn)行技術(shù),只是進(jìn)行字符串替換,而且也不會自動加上括號(),所以COUNT(x + 1)被替換為 COUNT(x + 1 * x + 1),代入 x = 6,即為 6 + 1 * 6 + 1 = 13。而解決辦法則是:盡量用括號把整個(gè)替換文本及其中的每個(gè)參數(shù)括起來:

#define COUNT(M) ((M) * (M))  

但即使用括號,也不能解決上面例子的最后一個(gè)情況,COUNT(++x) 被替換為 ++x * ++x,即為 7 * 8 = 56,而不是想要 7 * 7 = 49,解決辦法最簡單的是:不要在有參宏用使用到“++”、“–”等。

上面說到宏名中不能有空格,宏名與形參表之間也不能有空格,而形參表中形參之間可以出現(xiàn)空格:

#define SUM (a,b) a + b              //定義有參宏
printf("SUM = %d\n", SUM(1,2));      //調(diào)用有參宏。Build Failed!
因?yàn)?SUM 被替換為:(a,b) a + b

如果用函數(shù)求一個(gè)整數(shù)的平方,則是:

int count(int x){
    return x * x;
}

所以在宏定義中:#define COUNT(M) M * M 中的形參不分配內(nèi)存單元,所以不作類型定義。而函數(shù) int count(int x)中形參是局部變量,會在棧區(qū)分配內(nèi)存單元,所以要作類型定義,而且實(shí)參與形參之間是“值傳遞”。而宏只是符號代換,不存在值傳遞。

宏定義也可以用來定義表達(dá)式或者多個(gè)語句。如:

#define JI(a,b) a = i + 3; b = j + 5;   //宏定義多個(gè)語句
int i = 5, j = 10;
int m = 0, n = 0;
JI(m, n);                               // 宏代換后為: m = i + 3, n = j + 5;
printf("m = %d, n = %d\n", m, n);       // 輸出結(jié)果為: m = 8, n = 15

3、# 運(yùn)算符

比如如果我們宏定義了:

#define SUM (a,b) ((a) + (b)) 

我們想要輸出“1 + 2 + 3 + 4 = 10”,用以下方式顯得比較麻煩,有重復(fù)代碼,而且中間還有括號:

printf("(%d + %d) + (%d + %d) = %d\n", 1, 2, 3, 4, SUM(1 + 2, 3+ 4));

那么這時(shí)可以考慮用 # 運(yùn)算符來在字符串中包含宏參數(shù),# 運(yùn)算符的用處就是把語言符號轉(zhuǎn)化為字符串。例如,如果 a 是一個(gè)宏的形參,則替換文本中的 #a 則被系統(tǒng)轉(zhuǎn)化為 “a”。而這個(gè)轉(zhuǎn)化的過程成為 “字符串化(stringizing)”。用這個(gè)方法實(shí)現(xiàn)上面的要求:

#define SUM(a,b) printf(#a " + "#b" = %d\n",((a) + (b)))    //宏定義,運(yùn)用 # 運(yùn)算符
SUM(1 + 2, 3 + 4);                                          //宏調(diào)用
//輸出結(jié)果:1 + 2 + 3 + 4 = 10

調(diào)用宏時(shí),用 1 + 2 代替 a,用 3 + 4 代替b,則替換文本為:printf(“1 + 2” ” + ” “3 + 4” ” = %d\n”,((1 + 2) + (3 + 4))),接著字符串連接功能將四個(gè)相鄰的字符串轉(zhuǎn)換為一個(gè)字符串:

"1 + 2 + 3 + 4 = %d\n"

4、## 運(yùn)算符

和 # 運(yùn)算符一樣,## 運(yùn)算符也可以用在替換文本中,而它的作用是起到粘合的作用,即將兩個(gè)語言符號組合成一個(gè)語言符號,所以又稱為“預(yù)處理器的粘合劑(Preprocessor Glue)”。用法:

#define NAME(n) num ## n            //宏定義,使用 ## 運(yùn)算符
int num0 = 10;
printf("num0 = %d\n", NAME(0));     //宏調(diào)用

NAME(0)被替換為 num ## 0,被粘合為: num0。

5、可變宏:… 和 VA_ARGS

我們經(jīng)常要輸出結(jié)果時(shí)要多次使用 prinf(“…”, …); 如果用上面例子#define SUM(a,b) printf(#a ” + “#b” = %d\n”,((a) + (b))),則格式比較固定,不能用于輸出其他格式。

這時(shí)我們可以考慮用可變宏(Variadic Macros)。用法是:

#define PR(...) printf(__VA_ARGS__)     //宏定義
PR("hello\n");                          //宏調(diào)用
//輸出結(jié)果:hello

在宏定義中,形參列表的最后一個(gè)參數(shù)為省略號“…”,而“VA_ARGS”就可以被用在替換文本中,來表示省略號“…”代表了什么。而上面例子宏代換之后為: printf(“hello\n”);

還有個(gè)例子如:

#define PR2(X, ...) printf("Message"#X":"__VA_ARGS__)   //宏定義
double msg = 10;
PR2(1, "msg = %.2f\n", msg);                            //宏調(diào)用
//輸出結(jié)果:Message1:msg = 10.00

在宏調(diào)用中,X的值為10,所以 #X 被替換為”1”。宏代換后為:

printf("Message""1"":""msg = %.2f\n", msg);

接著這4個(gè)字符串連接成一個(gè):

printf("Message1:msg = %.2f\n", msg);

要注意的是:省略號“…”只能用來替換宏的形參列表中最后一個(gè)!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容