C/C++基础:宏

简述

宏作为C/C++最有特色的语言性质之一,犹如魔法一般,合理的使用可以极大的提高开发效率。

宏(Macro) 是C/C++的一个预处理指令,本质上是编译开始前进行的简单文本替换,我们可以定义一组宏代码片段,在程序中多次使用, 从而减少开发时间和精力。甚至我们可以使用宏来实现一些奇怪的操作,例如在C语言中利用宏做到如同模版一样的泛型编程。

宏的简单使用

基础语法

宏的基础使用方式如下:

#define 标识符 替换列表

比如,我们可以定义一个简单的宏PII,将其替换为浮点型字面量3.14

#define PII 3.14

// 我们可以在代码里使用PII这个宏
int main(){
	std::cout << PII << std::endl;		// PII 被展开为 3.14
	return 0;
}

带参宏(宏函数)

我们同样可以定义一个宏函数,可以让我们像普通的函数一样来调用他,其写法和最普通的文本替换宏无太大区别,参数和函数一样用括号包裹,用逗号(,)分隔即可,如下我们编写一个宏实现乘法的功能。

#define mul(a, b) a*b

int main(){
	std::cout << mul(2, 3) << std::endl;	// mul(2, 3)展开为 2*3
	return 0;
}

宏参字符串化#

有些时候,我们希望我们往宏传递的参数可以以字符串的形式展开,这个时候我们可以替换列表里使用#来将参数转为字符串。你可能觉得有些抽象,我保证看完下面这个例子,你就会知道什么是宏参字符串化:

#define str1(x) #x
#define str2(x) x

int main() {
	const char* s1 = str1(hello);		// 本行代码展开为 const char* s1 = "hello"
	// const char* s2 = str2(hello);	// 错误的代码,本行代码展开为 const char* s2 = hello
	const char* s3 = str2("hello");		// 本行代码展开为 const char* s2 = "hello"
	return 0;
}

宏拼接##

我们经常会有这样的需求,我们需要定义很多操作类似的函数,仅仅函数的前缀名不同,比如你可能需要在某处声明一系列这样的函数:

void func_1(int a, int b);
void func_2(int a, int b);
...
void func_n(int a, int b);

如果参数列表非常长的话,重复写这么多相似的函数声明无疑是难受的,你也许会想到利用宏来简化这个问题:

// 注意这是错误的实现
#define XX(n) void func_ n (int a, int b)

int main(){
	XX(1);
	XX(2);
	...
	XX(n);
}

然而代码并没有像我们希望的一样展开,因为 func_ 和 n 被空格分开了,并没有正确拼接在一起, 我们可以通过##将参数和其他东西拼接起来,如下:

#define XX(n) void func_ ## n (int a, int b)

int main(){
	XX(1);		//展开为 void func_1(int a, int b)
	XX(2);
	...
	XX(n);
}

宏的陷阱

看完上面的内容,恭喜你,基本已经学会了宏的语法.但是在你实际运用这些宏之前,还需要了解宏常见的陷阱,防止写出劣质乃至错误的代码.

多行定义

如果你是一个使用宏的新手,且习惯于使用函数,那么你是很有可能写出以下这样的宏的:

#define max(a, b){
	(a > b) ? a : b
}

int main(){
	int c = max(1, 3);
	std::cout << c << std::endl;
	return 0;
}

然而当你兴致勃勃的写完代码,编译,发现编译器无情的报错了,你可以改成一行来保证正确性,如下:

#define max(a, b){(a > b) ? a : b}

但是如果这个宏的替换列表很长呢?写在一行未免过于臃肿了,其实我们通过换行符\来进行换行,如下:

// 这是正确的实现
#define max(a, b){		\
	(a > b) ? a : b		\
}
// 注意最后一行是不需要\的

int main(){
	int c = max(1, 3);
	std::cout << c << std::endl;
	return 0;
}

宏中的空格

你也许会觉得,一个空格有什么大不了的,那么请你看下面的这个例子:

#define max (a, b){	\
(a > b) ? a : b		\
}


int main() {
	int c = max(1, 3);
	std::cout << c << std::endl;
	return 0;
}

/*
max(1, 3)展开如下
(a, b){
(a>b)? a: b
}(1,3)
/*

发现区别了吗?没错,我们在定义宏的时候,max和(a, b)之间不小心多写了一个空格,这会导致max(1, 3)完全展开为不同的结果.然而,在定义函数的时候,我们将函数名和()之间添加一个空格是完全无影响的,宏则需要完全避免这类事情。

宏函数不是函数

观察下面的简单宏函数和普通函数:

#define mul_m(a, b) a*b

int mul_f(int a, int b){
	return a * b;
}

你觉得这个宏可以完全替代函数的功能吗?观察以下的代码:

#define mul(a, b) a*b

int main(){
	std::cout << mul(2 + 1, 3) << std::endl;
	return 0;
}

看出问题了吗?mul(2+1, 3)被展开为了2 + 1 * 3 ,由于运算符的优先级问题,我们得到了期望外的结果,如何避免这种问题呢?其实只要加上括号来保证优先级即可,如下:

#define mul(a, b) ((a) * (b))

这下看起来终于完美了!
还是说,并没有?考虑下面这个代码,思考为什么这个代码和我们的预期不太一样:

#define mul(a) ((a) * (a))

int main(){
	int a = 2;
	std::cout << mul(++a)<< std::endl;
	return 0;
}

相信通过文本展开你已经发现了,没错,mul(++a)被展开后出现了两个++a,这不是我们期望的,所以我们并不能盲目的用宏来替换函数。
实际上,宏还不能像函数那样进行自递归定义,如下的这样的宏是错误的:

#define A(x) 3
#define B(x) A(x) + B(x) + C(x)
#define C(x) A(x) + B(x)

行末分号问题

考虑下面的代码:

#define print(str) printf(#str);

int main() {
	if (true) 
		print(hello);
	else
		print(world);
	return 0;
}

你能发现问题吗?问题其实出在第一个print,这里展开后printf(“hello”)后面有两个分号,导致else与if无法进行匹配了。这里有这许多的解决方案,我们很容易保证分号的数量不出错。

一些建议

事实上,对于一部分宏函数我们总是建议可以利用do{}while(0)语句包裹起来,这样可以很好的处理宏的一些副作用,如下:

#define print(str) do {printf(#str)} while(0)

这样不仅保证了宏内的生命周期,还强制调用宏的时候需要像函数调用一样,在其后加上分号(考虑直接使用{} 包裹的场景)。

宏的奇妙使用

你现在已经学完了宏的使用方式,那么如何实现文章开头提到的泛型编程呢?其实十分简单,实现如下:

#include <iostream>
#define mySwap(T, a, b) do{ T tmp = a; a = b; b=tmp;}while(0)

int main() {
	int a = 1, b = 0;
	mySwap(int, a, b);
	std::cout << a << ' ' << b << std::endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值