首頁 > 軟體

C語言深入講解宏的定義與使用方法

2022-04-19 13:00:46

一、C語言中的宏定義

  • #define是前處理器處理的單元實體之一
  • #define 定義的宏可以出現在程式的任意位置
  • #define 定義之後的程式碼都可以使用這個宏
  • #define 定義的宏常數可以直接使用
  • #define 定義的宏常數本質為字面量

下面的宏常數定義正確嗎?

編寫程式碼來測試:

#define ERROR -1
#define PATH1 "D:testtest.c"
#define PATH2 D:testtest.c
#define PATH3 D:test
test.c
 
int main()
{
    int err = ERROR;
    char* p1 = PATH1;
    char* p2 = PATH2;
    char* p3 = PATH3;
}

先使用gcc -E Test.c -o Test.i 進行預編譯,預編譯沒有報錯,結果如下:

# 1 "Test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "Test.c"
 
int main()
{
    int err = -1;
    char* p1 = "D:testtest.c";
    char* p2 = D:testtest.c;
    char* p3 = D:testtest.c;
}

直接進行編譯,發現 char* p2 = PATH2; char* p3 = PATH3; 報錯

這說明宏定義是正確的,但是編譯是過不了的,只是

#define PATH2 D:testtest.c

#define PATH3 D:test

不符合語法規範。

二、宏定義表示式

  • #define 表示式的使用類似函數呼叫
  • #define 表示式可以比函數更強大
  • #define 表示式比函數更容易出錯

強大之處其中之一就是可以求陣列的大小,這是不能編寫函數辦到的。

下面看一段宏表示式的程式碼:

#include <stdio.h>
 
#define _SUM_(a, b) (a) + (b)
#define _MIN_(a, b) ((a) < (b) ? (a) : (b))
#define _DIM_(a) sizeof(a)/sizeof(*a)
 
int main()
{
    int a = 1;
    int b = 2;
    int c[4] = {0};
 
    int s1 = _SUM_(a, b);
    int s2 = _SUM_(a, b) * _SUM_(a, b);
    int m = _MIN_(a++, b);
    int d = _DIM_(c);
 
    printf("s1 = %dn", s1);
    printf("s2 = %dn", s2);
    printf("m = %dn", m);
    printf("d = %dn", d);
 
    return 0;
}
 

下面為輸出結果,但是 s2 我們預期的結果應該是 9,m 的值我們預期的結果應該是 1,這是怎麼回事呢?

下面進行預編譯看看程式碼到底是怎麼執行的,輸入 gcc -E Test.c -o Test.i

int main()
{
    int a = 1;
    int b = 2;
    int c[4] = {0};
 
    int s1 = (a) + (b);
    int s2 = (a) + (b) * (a) + (b);
    int m = ((a++) < (b) ? (a++) : (b));
    int d = sizeof(c)/sizeof(*c);
 
    printf("s1 = %dn", s1);
    printf("s2 = %dn", s2);
    printf("m = %dn", m);
    printf("d = %dn", d);
 
    return 0;
}

通過上面宏定義的替換,我們很容易知道為什麼結果跟我們想的不一樣。

三、宏表示式與函數的對比

  • 宏表示式被前處理器處理,編譯器不知道宏表示式的存在
  • 宏表示式用“實參”完全替代形參,不進行任何運算
  • 宏表示式沒有任何的“呼叫”開銷
  • 宏表示式中不能出現遞迴定義

所以,下面遞迴定義就是錯誤的:

四、有趣的問題

宏定義的常數或表示式是否有作用域限制?(沒有)

下面看一個宏作用域分析的程式碼:

#include <stdio.h>
 
void def()
{
    #define PI 3.1415926
    #define AREA(r) r * r * PI
}
 
double area(double r)
{
    return AREA(r);
}
 
int main()
{
    double r = area(5);
 
    printf("PI = %fn", PI);
    printf("d = 5; a = %fn", r);
    
    return 0;
}

下面為輸出結果:

作用域的概念是針對 C 語言中的變數和函數,不針對宏。宏表示式被前處理器處理,編譯器不知道宏表示式的存在。

五、強大的內建宏

含義範例
_FILE_被編譯的檔名file1.c
_LINE_當前行號25
_DATE_編譯時的日期Jan 31 2021
_TIME_編譯時的時間17:01:01
_STDC_編譯器是否遵循標準C規範1

下面看一個宏使用的綜合範例:

#include <stdio.h>
#include <malloc.h>
 
#define MALLOC(type, x) (type*)malloc(sizeof(type)*x)
 
#define FREE(p) (free(p), p=NULL)
 
#define LOG(s) printf("[%s] {%s:%d} %s n", __DATE__, __FILE__, __LINE__, s)
 
#define FOREACH(i, m) for(i=0; i<m; i++)
#define BEGIN {
#define END   }
 
int main()
{
    int x = 0;
    int* p = MALLOC(int, 5);
    
    LOG("Begin to run main code...");
    
    FOREACH(x, 5)
    BEGIN
        p[x] = x;
    END
    
    FOREACH(x, 5)
    BEGIN
        printf("%dn", p[x]);
    END
    
    FREE(p);
    
    LOG("End");
    
    return 0;
}

下面為輸出結果:

可以看到宏定義是很強大的,可以列印出日期,檔名,行號,不使用宏定義很難實現。

六、小結

  • 前處理器直接對宏進行文字替換
  • 宏使用時的引數不會進行求值和運算
  • 前處理器不會對宏定義進行語法檢查
  • 宏定義時出現的語法錯誤只能被編譯器檢測
  • 宏定義的效率高於函數呼叫
  • 宏的使用會帶來一定的副作用

到此這篇關於C語言深入講解宏的定義與使用方法的文章就介紹到這了,更多相關C語言 宏的定義內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com