C之宏定義(十九)
#define 定義的宏常量可以直接使用,其本質為字面量。它與 const 定義的常量的區別是:const 修飾的常量本質是變量,占用內存;而字面量是不占用內存的。我們來看看下面這幾個宏常量定義是否正確
#define ERROR -1 #define PATH1 "D:\test\test.c" #define PATH2 D:\test\test.c #define PATH3 D:\testtest.c int main() { int err = ERROR; char* p1 = PATH1; char* p2 = PATH2; char* p3 = PATH3; }
我們分析下,前兩個肯定正確,第三種猜測是正確的,因為宏定義只是簡單的替換。第四種也是正確的,最後的 \ 我們看成是前面介紹的接續符。我們來單步編譯下,看看結果
我們看到單步編譯的時候,它沒出錯。就是進行簡單的替換,我們再加上頭文件,進行編譯。結果如下
我們看到編譯出錯了,因為宏只是被預處理期處理,預處理期不會去檢查語法,所以會單步編譯通過。所以在編譯檢查語法的時候出錯了。我們下來看個示例代碼,代碼如下
#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 = %d\n", s1); printf("s2 = %d\n", s2); printf("m = %d\n", m); printf("d = %d\n", d); return 0; }
我們分析,第14行返回相加和,因而第19行打印 3;第15行返回加和的平方,因而第20行打印 9;第16行返回最小值,因而第21行打印 1;第17行返回的是數組的個數,所以第22行打印 4。我們看看編譯結果
結果跟我們分析的不太一樣,中間兩個不一樣。我們來單步編譯下,看看是什麽樣的
我們看到它的 main 函數是這樣的,因而我們分析的是錯的。那麽在這塊我們是忽略了宏表達式和函數的差異,那麽宏表達式有哪些特性呢?如下:a> 宏表達式被預處理器處理,編譯器不知道宏表達式的存在;b> 宏表達式用“實參”完全代替形參,不進行任何運算;c> 宏表達式沒有任何的“調用”開銷;d> 宏表達式中不能出現遞歸定義
那麽我們來思考下:宏定義的常量或表達式是否有作用域限制?我們來看看下面的代碼
#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 = %f\n", PI); printf("d = 5; a = %f\n", r); return 0; }
那麽在 def 函數裏定義的宏能否在 area 函數裏使用呢?也就是說宏定義的作用域是否是具有函數作用域呢,我們來看看編譯結果
它並沒有報錯,而是成功運行。在這裏我們註釋掉頭文件和打印語句,我們來單步編譯下,看看函數裏是怎樣的?
明顯在 area 函數裏直接展開宏,也就是說宏定義的常量沒有作用域的限制。我們再來看看 C 語言中強大的內置宏
我們利用內置宏編寫析下面的代碼,代碼如下:
#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("%d\n", p[x]); END FREE(p); LOG("End"); return 0; }
我們在第4行定義 MALLOC 為申請堆空間並用指針來存儲地址,第6行利用之前學習的逗號表達式來釋放申請到的指針。第8行則是利用內置宏定義 LOG 打印信息,第10-12行分別定義 for 循環和{ }。我們來看看編譯後打印的結果
我們看到內置宏是如此的強大,目前在 C 語言中是利用函數辦不到來打印文件名和行數信息的。通過對宏定義的學習,總結如下:1、預處理期直接對宏進行文本替換,宏使用時的參數不會進行求值和運算;2、預處理期不會對宏定義進行語法檢查,宏定義出現的緣分錯誤只能被編譯器檢測;3、宏定義的效率高於函數調用但會帶來一定的副作用。後面我們會繼續對 C 語言的學習。
歡迎大家一起來學習 C 語言,可以加我QQ:243343083。
C之宏定義(十九)