C語言基礎–宏函數
1. 函數和數據類型
-
函數式宏(宏函數)和函數類比會更加靈活,我們通過兩個例子來看一下。
函數
#include <stdio.h> int sqr_int(int x) { return x*x; } double sqr_double(double x) { return x*x; } int main(int argc , char *argv[]) { int n; double x; printf("請輸入一個整數:\n"); scanf("%d",&n); printf("%d的平方是:%d\n",n,sqr_int(n)); printf("請輸入一個實數:\n"); scanf("%lf",&n); printf("%lf的平方是:%lf\n",n,sqr_double(n)); return 0; }
- 當然了,如果計算long int,或者long long int型的數據類型的話我們還得編寫該類型的函數。可以 遇見,如果這樣下去代碼中將會充斥着大量功能相近,名稱相似的函數,看起來非常糟糕。
- 下面來看一下函數式宏的實現:
函數式宏
#include <stdio.h> #define sqr(x) ((x)*(x)) int main(int argc,char *argv[]) { int n; double x; printf("請輸入一個整數:\n"); scanf("%d",&n); printf("%d的平方是:%d\n",n,sqr(n)); printf("請輸入一個實數:\n"); scanf("%lf",&n); printf("%lf的平方是:%lf\n",n,sqr(n)); return 0; }
- 可以看到一個簡單的宏函數就實現了幾個函數才能實現的事情,當然了宏函數可不止這點兒能耐。
- 本例中類似sqr(@) —>展開爲((@)*(@))的形式就稱爲宏展開
- 當調用該宏函數的時候,就會在調用位置將該宏函數展開爲上述形式。
- 值得注意的是宏定義只做替換不做計算,這一點很重要,後面會再提到。
2. 函數和函數式宏
- 從上面可以看到函數式宏在某些時候可以替代函數的作用,它們的區別如下:
-
函數式宏是在編譯時展開並填入程序的
-
而函數定義則需要爲每個形參都定義各自的數據類型,返回值類型也只能爲一種。函數更爲嚴格。
-
函數默默的爲我們進行一些複雜的操作,比如:
- 參數傳遞(將實參值賦值給形參)
- 函數調用和函數返回操作
- 返回值的傳遞
而函數式宏只是做宏展開,並不做上述處理。
-
函數式宏能是程序的運行速度稍微提高一點兒,但是當函數中有大量的宏替換的時候,又會是的程序變得臃腫。
-
宏在使用的時候必須小心謹慎,避免出現問題。這一點是有宏的本身特性決定,即只做替換不做計算。舉例來說明:
宏的副作用
情況一:
#define sqr(a) ((a)*(a))
若調用該函數式宏計算sqr(a++),展開後就變爲:((a++) * (a++)),可以發現a執行了兩次自增操作。這就會造成隱形的錯誤,比如我只是想將a自增1後在求其平方,但是結果卻並非我們所想。
情況二:
宏定義與宏函數
假如我們在sqr 和’('之間多敲了一個空格,如下
#define sqr (a) ((a)*(a))
那麼此時函數式宏就變成了,宏定義了,也成對象式宏。即sqr會被編譯器替換成(x) (x)*(x)
在定義宏函數的時候注意宏函數名和’('之間不能有空格。
情況三:
#define sum(x,y) x + y //注意:不規範的函數式宏的定義 //調用 z = sum(a,b) * sum(c,d); //編譯器將其展開後就變爲: z = a + b * c + d; //這樣是不是偏離了我們的本意
因此,我們在定義函數式宏的時候與一定要每個參數以及整個表達式都用()括起來,就不會出錯了。上面的就可以改爲
#define sum(x,y) ((x) + (y)) //正確的定義方法 //調用 z = sum(a,b) * sum(c,d); /編譯器將其展開後就變爲: z = ((a) + (b)) * ((c) + (d));
總結,在定義和使用函數式宏的時候要注意避免其產生副作用
3. 不帶參數的函數式宏
-
函數式宏也可以像函數那樣不帶參數
#define my_print() (printf("你好啊!\n"))
4. 函數式宏和逗號表達式
- 下面將說明函數式宏使用時的一個重要技巧
#include <stdio.h>
#define puts_alert(str) {putchar('\a');puts(str);}
int main(int argc,char *argv[])
{
int n;
printf("請輸入一個整數:");
scanf("%d",&n);
if(n)
puts_alert("這個數不是0.");
else
puts_alert("這個數是0.");
return 0;
}
-
當我們運行這個程序的時候會報錯,無法運行。提示else 缺少if
因爲將該函數式宏展開後就變爲:
#include <stdio.h>
#define puts_alert(str) {putchar('\a');puts(str);}
//當然了,如果一行定義不下,有時候爲了美觀我們也這樣寫,'\'表示下一行還有內容
#define puts_alert(str) {putchar('\a'); \
puts(str);}
int main(int argc,char *argv[])
{
int n;
printf("請輸入一個整數:");
scanf("%d",&n);
if(n)
{putchar('\a');puts(str);};
else
{putchar('\a');puts(str);};
return 0;
}
- 很明顯,if下的複合語句’}‘後的’;‘會被認爲是空語句,那麼此時else再去找它上面的那個if的時候就找不到了,因此就會報錯。當然了函數式宏中的’{}'也不能少,若少了又會報其他的錯誤。怎麼解決呢?可以思考一下。當然了,也可以參見下面一節
5. 函數式宏和逗號表達式
- 上面一節我們看到了當函數式宏中有多條需要執行的語句的時候我麼遇到了麻煩,下面各處解決方法:
#include <stdio.h>
#define puts_alert(str) (putchar('\a'),puts(str))
int main(int argc,char *argv[])
{
int n;
printf("請輸入一個整數:");
scanf("%d",&n);
if(n)
puts_alert("這個數不是0.");
else
puts_alert("這個數是0.");
return 0;
}
//在if處展開
if(n)
(putchar('\a'),puts(str));
else
(putchar('\a'),puts(str));
-
這裏有個知識點,逗號表達式。逗號運算符:
**表達式a,表達式b —>按順序判斷a和b,整個表達式最終生成表達式b的判斷結果。**當然了,若有多個表達式,可以以此類推。
舉個例子:
int a = 3,b = 4,z = 0;
z = ++a,++b;
//執行時a的值會自增1,b的值也會自增1,最終將b自增後的值賦給變量z
//此時z == 5,也就是最終整個逗號表達式的結果。
可以看到:整個逗號表達式中每個語句都會被計算到。
如果想要了解更多的逗號表達式的知識可以參考下我的另一博文:
逗號運算符與逗號表達式
6. 總結
- 函數式宏在使用時很方便但是也很容易出錯,所以在使用的時候一定要非常小心,避免出錯。