宏与内联函数都可提高代码的效率,但在实现和行为上有显著差异。
宏:预处理器指令,使用 #define
进行定义。它们在编译之前由预处理器进行文本替换。
特点:
- 文本替换:宏在编译前进行文本替换,没有类型检查或错误检查。
- 无类型安全:由于宏仅是文本替换,所以不进行类型检查。
- 避免函数调用开销:宏可以减少函数调用的开销,但如果宏体较大,可能会导致代码膨胀。
- 调试困难:宏在编译前就被替换,所以在调试时可能会带来困难。
所以一般定义宏或者宏函数的代码量都很少,很小,基本上是一些简单操作。
示例1:平方运算
#define SQUARE(x) ((x) * (x))
示例2:cuda的错误检查函数
#define CHECK(call) \
do \
{ \
const cudaError_t error_code = call; \
if (error_code != cudaSuccess) \
{ \
printf("CUDA Error:\n"); \
printf(" File: %s\n", __FILE__); \
printf(" Line: %d\n", __LINE__); \
printf(" Error code: %d\n", error_code); \
printf(" Error text: %s\n", \
cudaGetErrorString(error_code)); \
exit(1); \
} \
} while (0)
//调用宏
if(){
CHECK(call)
}
else{
}
//等价于:
if(){
do \
{ \
const cudaError_t error_code = call; \
if (error_code != cudaSuccess) \
{ \
printf("CUDA Error:\n"); \
printf(" File: %s\n", __FILE__); \
printf(" Line: %d\n", __LINE__); \
printf(" Error code: %d\n", error_code); \
printf(" Error text: %s\n", \
cudaGetErrorString(error_code)); \
exit(1); \
} \
} while (0)
}
else{
}
//如果像定义函数一样加 { },会造成if语句中有两个{},虽然能运行,但可读性差并且容易生成错误
可以看到宏的定义不包含参数数据类型,宏仅是文本替换,不进行参数替换。
第二个示例中do {...}whlie(0)是宏定义的内容,可以认为是一个函数,这里do{...}while中的内容才是宏的主体,也就是函数内容。do { ... } while (0)
是一个循环结构,但在这里它只执行一次。这种结构在宏定义中常用于确保宏在任何地方都能安全地使用,特别是在条件语句中。
那么宏函数的定义为什么不用{}呢,首先宏#define CHECK(x),要确保后面是一个单独的语句,如果在if,else使用宏时会导致else与if的错误匹配。do { ... } while (0)
结构提供了一种在宏定义中创建复杂逻辑的安全和一致的方式。所以在这里定义简单的宏只要使用单句即可,比如示例1,如果需要生成多行,最好使用do{}while(0)来确保宏定义使用的安全。
内联函数(Inline Function)
内联函数是一种通知编译器尝试在调用点展开函数体的方法,以减少函数调用的开销。使用 inline
关键字定义。
特点
- 类型检查:与普通函数一样,内联函数进行类型检查和错误检查。
- 编译器优化:内联是一种请求,编译器可以选择忽略这个请求。如果函数体较大或者包含复杂的控制结构,编译器可能不会进行内联。
- 减少函数调用开销:如果编译器接受内联请求,它会在调用点展开函数体,减少函数调用的开销。
- 可能的代码膨胀:如果内联函数在多个地方被调用,可能会导致代码膨胀。
inline int square(int x) {
return x * x;
}
宏函数 vs 内联函数
- 类型安全:内联函数比宏函数更安全,因为它们进行类型检查。
- 调试:内联函数比宏更易于调试,因为它们是实际的函数。
- 控制:编译器可以选择不内联一个内联函数,但宏总是进行文本替换。
- 使用场景:宏功能更强大,可以包含任何东西(如多个语句、控制结构等),而内联函数受到常规函数的限制。
在C++中,在类的内部定义了函数体的函数,被默认为是内联函数。而不管你是否有inline关键字。当然在类的外部定义类的成员函数加上inline可以达到同样的效果。
小结,内联函数与宏函数都可在使用处展开,可以避免函数调用的开销,宏函数是由预处理机制展开,不会检查类型安全,所以在传入错误参数时会出问题,而内联函数是一个函数,是由编译器决定是否内联,会对传入的参数进行类型检查。