1. 程式人生 > >C/C++預處理、巨集定義--你和大牛之間只差一個巨集定義#define

C/C++預處理、巨集定義--你和大牛之間只差一個巨集定義#define

  今天讀一個github上下載的C語言程式碼,讀得時候就像在讀天書,完全不像C。在此之前,我一直以為自己C語言掌握的還不錯的。所以惡補了以下關於預處理的知識。

  相信很多讀者和我一樣,只是會用C語言而已,只會迴圈跳躍閉著眼,但是學了巨集定義之後就馬上可以走上人生巔峰了。廢話到此為止,下面開整:

引用經典裡的一句話:前處理器的工作就是把一些文字轉換成另外以下文字。--《C Primer Plus》

明示常量#define

 會使用C的都知道這個簡單的命令,我們也經常使用它定義一些常量,什麼字串最大長度了之類的,下面就從這個基本功能講起:

  1. #define定義明示常量,它的主要作用就是將程式中所有出現定義名稱的地方替換為設定的值。示例如下:
    #include <stdio.h>
    #define
    TWO 2 //最簡單的巨集定義,將TWO定義為2,程式裡所有TWO將被2替換 #define NIHAO "你好啊,小夥子!\n" //將NIHAO定義為一個字串,所有NIHAO都會替換為對應字串 #define FOUR TWO*TWO //在一個巨集定義裡可以引用別的定義 #define PX printf("x is %d.\n",x)//可以將一個巨集定義為一個函式,但是在使用PX之前,必須保證x存在/* * */ int main(int argc, char** argv) { printf("TWO= %d\n",TWO); //
    預處理完成之後將被替換為 printf("TWO= %d\n",2); printf(NIHAO); //預處理完成之後將被替換為 printf("你好啊,小夥子!"); printf("FOUR= %d\n",FOUR); //預處理完成之後將被替換為 printf("FOUR= %d\n",2*2); int x=1; PX; //預處理完成之後將被替換為 printf("x is %d.\n",x); //雖然使用方便,但是隻能輸出x,而不能輸出其他任何變數
    return 0; }
    /*
    output:

    TWO= 2
    你好啊,小夥子!
    FOUR= 4
    x is 1.
    */

     

  2. 帶有引數的巨集定義,正如上述程式碼所示,不帶引數的巨集定義簡單明瞭,但是功能單一。上述巨集定義PX也只能輸出x變數的值,而帶引數的巨集定義就可以解決這些問題,示例如下:
    #include <stdio.h>
    
    #define SQUARE(x) x*x
    #define PR(x) printf("num is %d\n",x)
    /*
     * 
     */
    int main(int argc, char** argv) {
        
        int b=2;
        int z=SQUARE(b);
        printf("square b is %d\n",z);
        PR(SQUARE(b));
        PR(b);
        
        return 0;
    }

    /*
    output:

    square b is 4
    num is 4
    num is 2
    */

    讀到這裡是不是覺得巨集定義無所不能了?然而,並不是的,下面是巨集定義裡面的各種陷阱:

    #include <stdio.h>
    
    #define SQUARE(x) x*x
    #define PR(x) printf("num is %d\n",x)
    /*
     * 
     */
    int main(int argc, char** argv) {
     
      
        
        int b=2;
        int z=SQUARE(b);
        printf("square b is %d\n",z);
        PR(SQUARE(b));
        PR(b);
        
        //陷阱一: 修改為#define SQUARE(x) (x)*(x)   可以避免
        printf("square of b+2 is %d\n",SQUARE(b+2));
        
        //陷阱二: 修改為#define SQUARE(x) ((x)*(x))  可以避免
        printf("10/SQUARE(b) is %d\n",10/SQUARE(b));
        
        //陷阱三:巨集定義引數不要使用自增
        printf("SQUARE(++b) is %d\n",SQUARE(++b));
    /*
    output:
    
    square b is 4
    num is 4
    num is 2
    square of b+2 is 8
    10/SQUARE(b) is 10
    SQUARE(++b) is 16
    
    */
        return 0;
    }
        

    上述程式碼有三個陷阱,這些陷阱都是對原理不理解造成的,所謂預處理就是把一種文字處理為另外一種文字

    對於陷阱一:printf("square of b+2 is %d\n",SQUARE(b+2)); 預處理結束之後SQUARE(b+2)會被處理為:b+2*b+2  ,b=2  所以結果為8,解決方法:將巨集定義改為#define SQUARE(x) (x)*(x) 對於陷阱二:printf( "10/SQUARE(b) is %d\n",10/SQUARE(b)); 預處理結束之後10/SQUARE(b)會被處理為: 10/2*2,所以結果是10,而不是我們預期的2,解決方法:將巨集定義改為#define SQUARE(x) ((x)*(x)) 對於陷阱三:printf( "SQUARE(++b) is %d\n",SQUARE(++b)); 預處理結束之後SQUARE(++b) 會被處理為: ++b*++b  ,就是3×4=12,而避免此類陷阱的方法比較粗暴,就是不要將自增用於巨集定義引數!!。。   
  3. 變參巨集