宏
- 宏名称通常都是由大写英文字母构成的
- 宏名称里不可以包含空格
- 用宏给数字起名字的时候不可以使用赋值运算符,不要自增自减
- 可以在编写程序的时候直接使用宏名称替代数字,编译器在编译的时候会把程序里的宏替换成它所代表的数字
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
}