宏
- 宏名稱通常都是由大寫英文字母構成的
- 宏名稱里不可以包含空格
- 用宏給數字起名字的時候不可以使用賦值運算符,不要自增自減
- 可以在編寫程序的時候直接使用宏名稱替代數字,編譯器在編譯的時候會把程序里的宏替換成它所代表的數字
1. 為什么要使用宏?
1.1 名稱直觀:
宏可以用有意義的名稱,比如使用PI
去替代抽象的數值3.14
,讓代碼更易懂。開發者看到PI
時能直接聯想到“圓周率”,而無需記憶數字的含義,尤其是在復雜程序中,這種直觀性能顯著提高代碼的可理解性。
1.2 便于更新:
當需要修改宏代表的值時,如PI
的精度從3.14
調整為3.14159
,只需在宏定義處修改一次,編譯器會在預處理階段自動替換所有引用該宏的地方。如果不使用宏,直接在代碼中多出寫死數值,修改時需要逐個查找并替換,不僅繁瑣,還容易遺漏,增加出錯風險。
2. 無參宏(基本宏)
#include<stdio.h>// 定義無參宏PI,代表圓周率3.14159
// 宏的作用:用有意義的名稱替代常量,提高代碼可讀性
// 后續若需調整精度(如改為3.1415926),只需修改此處即可,所有引用處會自動替換
#define PI (3.14159)int main(void){float r = 0;printf("請輸入一個半徑值:");scanf("%f",&r);// 計算圓的面積:使用宏PI替代3.14159,代碼更直觀printf("圓的面積是:%g\n", PI * r * r);// 計算圓的周長:同樣使用宏PI,確保所有圓周率值一致printf("圓的周長是:%g\n", 2 * PI * r);return 0;
}
3. 帶參宏(類似函數的宏)
帶參宏的特性:僅在預處理階段做文本替換,不進行參數類型檢查,適用于多種數據類型
#include<stdio.h>// 定義帶參宏SQUARE,用于計算參數的平方
// 宏參數x:表示要計算平方的數值或表達式
// 替換文本((x)*(x)):外層和內層都加括號是為了避免因運算符優先級導致的計算錯誤
#define SQUARE(x) ((x)*(x)) int main(void){// 調用帶參宏SQUARE,參數為整數5// 預處理階段會替換為:((5)*(5)),結果為25printf("%d\n", SQUARE(5));// 調用帶參宏SQUARE,參數為浮點數5.5// 預處理階段會替換為:((5.5)*(5.5)),結果為30.25// 體現宏不檢查類型的特性,可同時處理整數和浮點數printf("%lg\n", SQUARE(5.5));// 調用帶參宏SQUARE,參數為表達式2+3// 預處理階段會替換為:((2+3)*(2+3)),結果為25// 因宏定義中參數x被括號包裹,避免了"2+3*2+3"的錯誤計算printf("%d\n", SQUARE(2+3));return 0;
}
4. 編譯時定義宏(通過編譯選項)
無需在代碼中用#define
定義,可通過編譯器選項-D
在編譯時指定宏的值
適用于需要根據不同場景(如調試/發布版本、不同硬件配置)動態修改宏值的場景
語法:gcc -D 宏名=值 源文件 -0 輸出文件
例如:gcc -DSIZE=10 test.c -o test
(定義SIZE
為10)
也可以gcc -D SIZE=10 test.c -o test
(定義SIZE
為10)
// 代碼中無需定義SIZE,編譯時通過-D指定
#include<stdio.h>
int main(void){int arr[SIZE] = {}; // SIZE由編譯選項指定for(int i=0; i<SIZE; i++){arr[i] = i + 100;}for(int i=0; i<SIZE; i++){printf("%d ", arr[i]);}return 0;
}
5. 宏運算符
#
:將宏的參數轉換為字符串(字符串化)。
例:#define STR(x) #x
,則STR(123)
會替換為"123"
。##
:將兩個標識符連接為一個新的標識符(連接符)。
例:#define JOIN(a,b) a##b
,則JOIN(num,1)
會替換為num1
。
#include <stdio.h>// 1. #運算符:將宏參數轉換為字符串(字符串化)
#define STR(x) #x // 定義宏STR,使用#將參數x轉換為字符串// 2. ##運算符:將兩個參數連接為一個新的標識符(連接符)
#define JOIN(a, b) a##b // 定義宏JOIN,使用##連接a和b為新標識符int main(void) {// 測試#運算符printf("使用#運算符的結果:\n");printf("STR(123) = %s\n", STR(123)); // 替換為"123",輸出字符串"123"printf("STR(abc) = %s\n", STR(abc)); // 替換為"abc",輸出字符串"abc"printf("STR(3.14) = %s\n", STR(3.14)); // 替換為"3.14",輸出字符串"3.14"// 測試##運算符printf("\n使用##運算符的結果:\n");int num1 = 100; // 定義變量num1int num2 = 200; // 定義變量num2printf("JOIN(num, 1) = %d\n", JOIN(num, 1)); // 連接為num1,輸出100printf("JOIN(num, 2) = %d\n", JOIN(num, 2)); // 連接為num2,輸出200// ##運算符也可用于函數名或其他標識符int student10 = 95; // 定義變量student10printf("JOIN(student, 10) = %d\n", JOIN(student, 10)); // 連接為student10,輸出95return 0;
}
6. 預定義宏(編譯器自帶)
預定義宏 | 占位符 | 含義 |
---|---|---|
__FILE__ | %s | 所在文件名 |
__func__ | %s | 所在函數名 |
__LINE__ | %d | 所在行號 |
__DATE__ | %s | 編譯該文件日期 |
__TIME__ | %s | 編譯該文件時間 |
注意:前后都是兩個下劃線
#include<stdio.h>
int main(void){int* p = NULL;int a = 10;//p = &a;if(p == NULL){printf("指針為NULL\n");// 把一個大串拆成多個小串(編譯器會自動拼接相鄰字符串)// 以下使用C語言預定義宏輸出調試信息:printf("文件:%s\n" // __FILE__:預定義宏,所在文件名"函數:%s\n" // __func__:預定義宏,所在函數名"行號:%d\n" // __LINE__:預定義宏,所在行號"日期:%s\n" // __DATE__:預定義宏,編譯該文件日期"時間:%s\n", // __TIME__:預定義宏,編譯該文件時間__FILE__, __func__, __LINE__, __DATE__, __TIME__);return -1; // 默認意外退出返回-1}return 0; // 正常退出返回0
}