2.2.3 # 和 ##
我们知道,宏是把参数替换到文本中。那么如何把参数插入到字符串中呢?
比如这种情况,如果只规定传一个参数,使用函数是根本做不到的:
函数传两个参数:
#include <stdio.h> void print(char x,int y) { printf("变量%c的值是%d\n", x,y); } int main() { int a = 10; // 打印内容:变量a的值是10 print('a',a); int b = 20; // 打印内容:变量b的值是20 print('b',b); int c = 30; // 打印内容:变量c的值是30 print('c',c); return 0; }
这种情况,就可以用 宏 传一个参数来实现。
介绍:# 把一个宏参数变成对应的字符串。
使用 # 解决上面的问题:
#include <stdio.h> #define PRINT(X) printf("变量"#X"的值是%d\n", X); // #X 就会变成 X内容所定义的字符串 int main() { // 打印内容:变量a的值是10 int a = 10; PRINT(a); // printf("变量""a""的值是%d\n", a); // 打印内容:变量b的值是20 int b = 20; PRINT(b); // printf("变量""b"的值是%d\n", b); // 打印内容:变量c的值是30 int c = 30; PRINT(c); // printf("变量""c""的值是%d\n", c); return 0; }
改进:让程序不仅仅支持打印整数,还可以打印其他类型的数(比如浮点数):
#include <stdio.h> #define PRINT(X, FORMAT) printf("变量"#X"的值是 "FORMAT"\n", X);//format格式(格式化参数) int main() { // 打印内容:变量a的值是10 int a = 10; PRINT(a, "%d"); // 打印内容:变量f的值是5.5 float f = 5.5f; PRINT(f, "%.1f"); //替换成printf("变量""f""的值是 ""%.1f""\n", f); return 0; }
这操作是不是很奇葩?还有更奇葩的呢:
介绍:## 可以把位于它两边的符号融合成一个符号。它允许宏定义从分离的文本片段创建标识符。
使用 ## 将两边的符号缝合成一个符号:
#include <stdio.h> #define CAT(X,Y) X##Y //发现连3个也行 int main() { int vs2022 = 100; //下面会打印出100 printf("%d\n", CAT(vs, 2022)); //替换成printf("%d\n", vs2022); return 0; }
2.2.4带 "副作用" 的宏参数
什么是副作用?副作用就是表达式求值的时候出现的永久性效果,例如:
// 不带有副作用 x + 1; // 带有副作用 x++; int a = 1; // 不带有副作用 int b = a + 1; // b=2, a=1 // 带有副作用 int b = ++a; // b=2, a=2
当宏参数在宏的定义中出现超过一次的情况下,如果参数带有副作用(后遗症),
那么在使用这个宏的时候就可能出现危险,导致不可预料的后果。
这种带有副作用的宏参数如果传到宏体内,这种副作用一直会延续到宏体内。
举个例子:
#include <stdio.h> #define MAX(X,Y) ((X)>(Y)?(X):(Y)) int main(void){ int a = 5; int b = 8; int m = MAX(a++, b++); printf("m = %d\n", m);//9 printf("a=%d, b=%d\n", a, b);//6 10 return 0; }
所以写宏的时候要尽量避免使用这种带副作用的参数。
2.2.5 宏和函数对比
举个例子:在两数中找较大值
① 用宏:
#include <stdio.h> #define MAX(X,Y) ((X)>(Y)?(X):(Y)) int main() { int a = 10; int b = 20; int m = MAX(a, b); // int m = ((a)>(b) ? (a):(b)) printf("%d\n", m); return 0; }
② 用函数:
#include <stdio.h> int Max(int x, int y) { return x > y ? x : y; } int main() { int a = 10; int b = 20; int m = Max(a, b); printf("%d\n", m); return 0; }
那么问题来了,宏和函数那种更好呢?
这题用宏更好,宏的优势:
用于调用函数和从函数返回的代码可能比实际执行这个小型计算工作所需要的时间更多,
所以宏比函数在程序的规模和速度方面更胜一筹。 更为重要的是函数的参数必须声明为特定的类型。
所以函数只能在类型合适的表达式上使用。反之,宏可以适用于整型、长整型、浮点型等可以用于
比较的类型。因为宏是类型无关的。
当然,宏也有劣势的地方:
① 每次使用宏的时候,一份宏定义的代码将插入到程序中。除非宏比较短,
否则可能大幅度增加程序的长度。
② 宏不能调试。
③ 宏由于类型无关,因为没有类型检查,所以不够严谨。
④ 宏可能会带来运算符优先级的问题,导致程容易出现错。
宏有时候可以做函数做不到的事情。比如:宏的参数可以出现类型,但是函数做不到:
#include <stdio.h> #include <stdlib.h> #define MALLOC(num, type) (type*)malloc(num*sizeof(type)) int main() { // 原本的写法:malloc(10*sizeof(int)); // 但我想这么写:malloc(10, int); int* p = MALLOC(10, int); // (int*)malloc(10*sizeof(int)) return 0; }
宏和函数的对比:
内联函数inline(C99)结合了宏和函数两者的优点(后面C++专栏会讲)
2.2.6宏和函数命名约定
命名约定,一般来讲函数的宏的使用语法很相似,所以语言本身没法区分二者。
约定俗成的一个习惯是: 宏名全部大写,函数名不要全部大写。
不过这也不是绝对的,比如我有时候就是想把一个宏伪装成函数来使用,那么我就全小写给宏取名。并不强制,但是这个约定是每个C/C++程序员大家的一种 "约定俗成" 。
2.3 #undef移除宏定义
#undef NAME 用于移除一个宏定义。(也不用在后面加分号)
代码演示:用完 M 之后移除该定义
#include <stdio.h> #define M 100 int main(void) { int a = M; printf("%d\n", M); #undef M// 移除宏定义 return 0; }
2.4命令行编译
什么是命令行编译?
在编译的时候通过命令行的方式对其进行相关的定义,叫做命令行编译。
介绍:许多C的编译器提供的一种能力,允许在命令行中定义符号。用于启动编译过程。
当我们根据同一个源文件要编译出不同的一个程序的不同版本的时,可以用到这种特性,增加灵活性。
例子:假如某个程序中声明了一个某个长度的数组,假如机器甲内存有限,我们需要一个很小的数据,但是机器丙的内存较大,我们需要一个大点的数组。
#include <stdio.h> int main() { int arr[ARR_SIZE]; int i = 0; for (i = 0; i < ARR_SIZE; i++) { arr[i] = i; } for (i = 0; i < ARR_SIZE; i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
gcc 环境下测试:(VS 里面不太好演示)
gcc test.c -D ARR_SIZE=5
ls
a.out test.c
./a.out
0 1 2 3 4 5
gcc test.c -D ARR_SIZE=20
./a.out
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
2.5条件编译
条件编译介绍
在编译一个程序时,通过条件编译指令将一条语句(一组语句)编译或者放弃是很方便的。
调试用的代码删除了可惜,保留了又碍事。我们就可以使用条件编译来选择性地编译
#include <stdio.h> #define __DEBUG__ // 就像一个开关一样 int main() { int arr[10] = { 0 }; int i = 0; for (i = 0; i < 10; i++) { arr[i] = i; #ifdef __DEBUG__ // 因为__DEBUG__被定义了,所以为真 printf("%d ", arr[i]); // 就打印数组 #endif // 包尾 } return 0; }
如果不想用了,就把 #define __DEBUG__ 注释掉:代码运行后就不打印数组了。
C语言进阶⑳(程序环境和预处理)(#define定义宏+编译+文件包含)(下):https://developer.aliyun.com/article/1513284