什么网站可以做设计,专业公司网站 南通,常州做金属网格公司,推广代理平台C/C基础#xff1a;宏 简述宏的简单使用基础语法带参宏#xff08;宏函数#xff09;宏参字符串化#宏拼接## 宏的陷阱多行定义宏中的空格宏函数不是函数行末分号问题一些建议 宏的奇妙使用 简述
宏作为C/C最有特色的语言性质之一#xff0c;犹如魔法一般#xff0c;合理的… 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.14return 0;
}
带参宏宏函数
我们同样可以定义一个宏函数可以让我们像普通的函数一样来调用他其写法和最普通的文本替换宏无太大区别参数和函数一样用括号包裹用逗号分隔即可如下我们编写一个宏实现乘法的功能。
#define mul(a, b) a*bint main(){std::cout mul(2, 3) std::endl; // mul(2, 3)展开为 2*3return 0;
}宏参字符串化#
有些时候我们希望我们往宏传递的参数可以以字符串的形式展开这个时候我们可以替换列表里使用#来将参数转为字符串。你可能觉得有些抽象我保证看完下面这个例子你就会知道什么是宏参字符串化
#define str1(x) #x
#define str2(x) xint main() {const char* s1 str1(hello); // 本行代码展开为 const char* s1 hello// const char* s2 str2(hello); // 错误的代码本行代码展开为 const char* s2 helloconst char* s3 str2(hello); // 本行代码展开为 const char* s2 helloreturn 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){
(ab)? a: b
}(1,3)
/*发现区别了吗?没错,我们在定义宏的时候,max和(a, b)之间不小心多写了一个空格,这会导致max(1, 3)完全展开为不同的结果.然而,在定义函数的时候,我们将函数名和之间添加一个空格是完全无影响的宏则需要完全避免这类事情。
宏函数不是函数
观察下面的简单宏函数和普通函数
#define mul_m(a, b) a*bint mul_f(int a, int b){return a * b;
}你觉得这个宏可以完全替代函数的功能吗观察以下的代码
#define mul(a, b) a*bint main(){std::cout mul(2 1, 3) std::endl;return 0;
}看出问题了吗mul(21, 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);elseprint(world);return 0;
}你能发现问题吗问题其实出在第一个print这里展开后printf(“hello”)后面有两个分号,导致else与if无法进行匹配了。这里有这许多的解决方案我们很容易保证分号的数量不出错。
一些建议
事实上对于一部分宏函数我们总是建议可以利用dowhile0语句包裹起来这样可以很好的处理宏的一些副作用如下
#define print(str) do {printf(#str)} while(0)这样不仅保证了宏内的生命周期还强制调用宏的时候需要像函数调用一样在其后加上分号考虑直接使用} 包裹的场景。
宏的奇妙使用
你现在已经学完了宏的使用方式那么如何实现文章开头提到的泛型编程呢其实十分简单实现如下
#include iostream
#define mySwap(T, a, b) do{ T tmp a; a b; btmp;}while(0)int main() {int a 1, b 0;mySwap(int, a, b);std::cout a b std::endl;return 0;
}