· 正 · 文 · 來 · 啦 ·
------在上篇文章里面,我們分析了預(yù)處理的一個完整過程,這能夠讓我們理解一個寫好的程序,在生成一個可執(zhí)行文件,到底發(fā)生了什么,對我們在大型工程項目里面有助于對程序的理解;今天我們繼續(xù)接著上篇文章的基礎(chǔ)上,來分享有關(guān)c語言里面關(guān)于宏定義的用法!
每個#define行(即邏輯行)由三部分組成:第一部分是指令 #define 自身,“#”表示這是一條預(yù)處理命令,“define”為宏命令。第二部分為宏(macro),一般為縮略語,其名稱(宏名)一般大寫,而且不能有空格,遵循C變量命令規(guī)則。第三部分“替換文本”可以是任意常數(shù)、表達(dá)式、字符串等。在預(yù)處理工作過程中,代碼中所有出現(xiàn)的“宏名”,都會被“替換文本”替換。這個替換的過程被稱為“宏代換”或“宏展開”(macro expansion)?!昂甏鷵Q”是由預(yù)處理程序自動完成的。在C語言中,“宏”分為兩種:無參數(shù) 和 有參數(shù)(這里有參數(shù)先不舉例子,下面具體分析的話,讀者可以詳細(xì)看到示例來理解這個)。下面是宏定義的基本形式:
#define 宏名 宏體
注意:宏體后面不要加分號“;”,這個在寫代碼的時候要小心點哦
------優(yōu)點:
1、方便程序的修改:
使用簡單宏定義可用宏代替一個在程序中經(jīng)常使用的常量,這樣在將該常量改變時,不用對整個程序進(jìn)行修改,只修改宏定義的字符串即可,而且當(dāng)常量比較長時, 我們可以用較短的有意義的標(biāo)識符來寫程序,這樣更方便一些(特別當(dāng)跨平臺的時候,要修改程序一些參數(shù)的時候,用宏定義的話,只需要修改宏定義的宏名就可以代表修改了整個程序里面用到這個宏名,就不用一個個去改了,極大的提升了工作效率!)。
2、提高程序的運(yùn)行效率:
這里我們就拿帶參宏和函數(shù)來對比了:
(1)宏定義是在預(yù)處理期間處理的,而函數(shù)是在編譯期間處理的。這個區(qū)別帶來的實質(zhì)差異是:宏定義最終是在調(diào)用宏的地方把宏體原地展開,而函數(shù)是在調(diào)用函數(shù)處跳轉(zhuǎn)到函數(shù)中去執(zhí)行,執(zhí)行完后再跳轉(zhuǎn)回來。
注:宏定義和函數(shù)的最大差別就是:宏定義是原地展開,因此沒有調(diào)用開銷;而函數(shù)是跳轉(zhuǎn)執(zhí)行再返回,因此函數(shù)有比較大的調(diào)用開銷。所以宏定義和函數(shù)相比,優(yōu)勢就是沒有調(diào)用開銷,沒有傳參開銷,所以當(dāng)函數(shù)體很短(尤其是只有一句話時)可以用宏定義來替代,這樣效率高。
(2)帶參宏和帶參函數(shù)的一個重要差別就是:宏定義不會檢查參數(shù)的類型,返回值也不會附帶類型;而函數(shù)有明確的參數(shù)類型和返回值類型。當(dāng)我們調(diào)用函數(shù)時編譯器會幫我們做參數(shù)的靜態(tài)類型檢查,如果編譯器發(fā)現(xiàn)我們實際傳參和參數(shù)聲明不同時會報警告或錯誤。
注:用函數(shù)的時候程序員不太用操心類型不匹配因為編譯器會檢查,如果不匹配編譯器會警告(但是實際測試并沒有警告,理論上是有的);用宏的時候程序員必須很注意實際傳參和宏所希望的參數(shù)類型一致,否則可能編譯不報錯但是運(yùn)行有誤(一般所希望的是整型數(shù)據(jù)類型,不然結(jié)果一般會出錯,下面的例子就是);而且最好在宏體里面每個參數(shù)都帶小括號,因為有時候會涉及到運(yùn)算符號的優(yōu)先級問題,這樣一來寫程序的話就不會引起bug了。
#include <stdio.h>
#define MAX(a, b) (((a)>(b)) ? (a) : (b))
int max(int a, int b)
{
if (a > b)
return a;
else
return b;
}
int main(void)
{
float a, b, c;
a = 1.5;
b = 4.7;
c = MAX(a, b); // 展開后:c = (((a)>(b)) ? (a) : (b));
printf("c = %d.\n", c);
c = max(a, b); // 無法展開,只能調(diào)用
printf("c = %d.\n", c);
return 0;
}
我們來看一下它預(yù)處理過后成了什么樣了:
# 2 "b.c" 2
# 6 "b.c"
int max(int a, int b)
{
if (a > b)
return a;
else
return b;
}
int main(void)
{
float a, b, c;
a = 1.5;
b = 4.7;
c = (((a)>(b)) ? (a) : (b));
printf("c = %d.\n", c);
c = max(a, b);
printf("c = %d.\n", c);
return 0;
}
演示結(jié)果(你會看到):
b.c: In function ‘main’:
b.c:25:15: warning: format ‘%d’ expects argument of type
‘int’, but argument 2 has type ‘double’ [-Wformat=]
printf("c = %d.\n", c);
~^
%f
b.c:27:15: warning: format ‘%d’ expects argument of type
‘int’, but argument 2 has type ‘double’ [-Wformat=]
printf("c = %d.\n", c);
~^
%f
root@ubuntu-virtual-machine:/home/ubuntu# ./a.out
c = -1272947832.
c = 4.
總結(jié):宏和函數(shù)各有千秋,各有優(yōu)劣。總的來說,如果代碼比較多用函數(shù)適合而且不影響效率;但是對于那些只有一兩句話的函數(shù)開銷就太大了,適合用帶參宏。但是用帶參宏又有缺點:不檢查參數(shù)類型。
------缺點:
由于是直接嵌入的,所以代碼可能相對多一點。
嵌套定義過多可能會影響程序的可讀性,而且很容易出錯,不容易調(diào)試。
對帶參的宏而言,由于是直接替換,并不會檢查參數(shù)是否合法,存在安全隱患。
1、嵌套宏的使用:
#include <stdio.h>
#define M 10
#define N M
int main(void)
{
printf("the M is %d\n",M);
printf("the N is %d\n",N);
return 0;
}
預(yù)處理之后:
# 5 "b.c"
int main(void)
{
printf("the M is %d\n",10);
printf("the N is %d\n",10);
return 0;
}
演示結(jié)果(簡單來講嵌套宏說白了還是直接替換的作用):
root@ubuntu-virtual-machine:/home/ubuntu# ./a.out
the M is 10
the N is 10
2、#運(yùn)算符:
出現(xiàn)在宏定義中的#運(yùn)算符把跟在其后的參數(shù)轉(zhuǎn)換成一個字符串。有時把這種用法的#稱為字符串化運(yùn)算符。例如:
#include <stdio.h>
#define M(n) "hhh"#n
int main(void)
{
printf("the M(6) is %s\n",M(6));
return 0;
}
演示結(jié)果:
the M(6) is hhh6
3、##運(yùn)算符:
##運(yùn)算符用于把參數(shù)連接到一起。預(yù)處理程序把出現(xiàn)在##兩側(cè)的參數(shù)合并成一個符號。看下面的例子:
#include <stdio.h>
#define M(a,b,c) a##b##c
int main(void)
{
printf("the M(2,3,4) is %d\n",M(2,3,4));
return 0;
}
演示結(jié)果:
the M(2,3,4) is 234
4、宏定義中使用了do{} while(0) (這種形式在代碼還是經(jīng)常能看的到的,下面我還是用例子來慢慢引導(dǎo)大家來看懂這用這個的含義):
#include <stdio.h>
#define M(n) \
printf("the n is %d\n",n);\
printf("the M(n) is %d\n",n);
int main(void)
{
int n=8;
int a=1;
if(a)
M(n);
return 0;
}
預(yù)處理后(你可以看到這樣一來,第二條語句就沒有在我們if語句的范圍內(nèi)了,而且讀者應(yīng)該注意到,帶參宏有點像函數(shù)調(diào)用,調(diào)用這個也是一條語句,所以語句后面加了“;”,這里在實際編譯過程中是多加了,會導(dǎo)致編譯報錯;但是不在這條語句后面加的話,就不像一條語句了,不過它是可以編譯通過的,下面改進(jìn)后的程序就是這種情況):
# 8 "b.c"
int main(void)
{
int n=8;
int a=1;
if(a)
printf("the n is %d\n",n);printf("the M(n) is %d\n",n);;
return 0;
}
改進(jìn)后(加了{(lán)}):
#include <stdio.h>
#define M(n) \
{printf("the n is %d\n",n);\
printf("the M(n) is %d\n",n);}
int main(void)
{
int n=8;
int a=1;
if(a)
M(n);
else
printf("error\n");
return 0;
}
預(yù)處理后(這個程序就會報錯了,就是我上面分析的原因):
# 8 "b.c"
int main(void)
{
int n=8;
int a=1;
if(a)
{printf("the n is %d\n",n);printf("the M(n) is
%d\n",n);};
else
printf("error\n");
return 0;
}
演示結(jié)果:
root@ubuntu-virtual-machine:/home/ubuntu# gcc b.c
b.c: In function ‘main’:
b.c:15:2: error: ‘else’ without a previous ‘if’
else
^~~~
最后我們來使用這個結(jié)構(gòu)再次來改進(jìn)上面的代碼看看效果如何:
#include <stdio.h>
#define M(n) \
do{\
printf("the n is %d\n",n);\
printf("the M(n) is %d\n",n);\
}while(0)
int main(void)
{
int n=8;
int a=1;
if(a)
M(n);
else
printf("error\n");
return 0;
}
預(yù)處理后(加了do{}while(0)后,上面的問題就全部解決了):
# 10 "b.c"
int main(void)
{
int n=8;
int a=1;
if(a)
do{printf("the n is %d\n",n);printf("the M(n) is
%d\n",n);}while(0);
else
printf("error\n");
return 0;
}
5、可變宏的使用:
C99中規(guī)定宏可以像函數(shù)一樣帶有可變參數(shù),實現(xiàn)思想就是宏定義中參數(shù)列表的最后一個參數(shù)為省略號(也就是三個英文輸入法下的句號)。這樣預(yù)定義宏__VA_ARGS__就可以被用在替換部分中,以表明省略號代表什么:
#include<stdio.h>
#define Variable_Macro(...) printf(__VA_ARGS__)
int main(void)
{
Variable_Macro("This is a variable macro test...\n");
Variable_Macro("My age is %d",22);
return 0;
}
預(yù)處理后:
# 3 "b.c"
int main(void)
{
printf("This is a variable macro test...\n");
printf("My age is %d",22);
return 0;
}
演示結(jié)果:
This is a variable macro test...
My age is 22
注意:帶參宏后面不能再有參數(shù),而我們的帶參函數(shù)前面必須要有參數(shù)(這里我就不舉例子關(guān)于帶參函數(shù)了):
#include<stdio.h>
#define Variable_Macro(...,a) printf(__VA_ARGS__)
int main(void)
{
Variable_Macro("This is a variable macro test...\n");
Variable_Macro("My age is %d",22);
return 0;
}
演示結(jié)果:
root@ubuntu-virtual-machine:/home/ubuntu# gcc b.c
b.c:2:27: error: missing ')' in macro parameter list
#define Variable_Macro(...,a) printf(__VA_ARGS__)
^
今天的分享就到這里了,晚安!
關(guān)注公眾號,每周分享至少3篇開源技術(shù)干貨,文章中如有沒看懂的地方可以私聊我,我看到了會立馬回復(fù)你,個人微信號:a18879499804
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!