c++详解(宏与内联函数,nullptr)
目录
1.宏
2.c++中宏的代替者
3.宏自身的优缺点。
3.1宏的缺点
3.2宏的优点
4.inline
5.使用inline的注意点
6.nullptr
摘要
1.宏
关于宏的定义,不了解的朋友可以点击链接跳转至编译与链接博客,其中预编译部分有详细介绍。
2.c++中宏的代替者
我们知道,c语言中宏可以定义常量,定义函数,虽然在cpp中宏仍可用,但在cpp中我们建议使用
const 、enum(枚举,不了解的朋友可以点击链接跳转至联合体与枚举博客)来代替宏创建常量,用inline(内联)来代替宏函数。
原因在于
3.宏自身的优缺点。
3.1宏的缺点
宏的操作本质上是一个“代替”的操作,在预编译阶段,会对宏进行简单的文本替换,这个特性使得使用宏时要考虑宏的各种副作用(常量的前后置++--等)和各种操作符的优先级(写宏函数要加各种括号),导致宏函数很复杂还容易出错,因为是文本替换的原因,宏在预处理被展开,宏函数不会建立函数栈帧,这也使得宏函数没法调试。
3.2宏的优点
由于宏函数的使用(区别与调用)不需要创建函数栈帧,且高频调用的小函数的函数结构比较简单,常被写成宏函数,可以节省空间,提高效率。
4.inline
用于修饰函数,被inline修饰的函数就叫内联函数,与宏的展开相似,内联函数在编译(在预处理的下一时期,编译的第二阶段,不了解的朋友可以点击链接跳转至编译与链接博客了解编译过程)时,编译器会在调用函数的地方展开成相应的逻辑汇编代码,不需要创建函数栈帧,提高效率。因而可以代替宏。
5.使用inline的注意点
- inline对于编译器来说是一个建议性指令,inline修饰的函数是否被展开,取决于编译器,一般来说代码行数较多的函数,或者递归函数,即使加inline也不会被展开(就是普通的函数)。
- vs编译器debug版本下,inline默认是不展开的,这样方便调试(展开的话不会创建函数栈帧,无法调试)
- inline不建议把声明和定义分开在两个文件里,在编译与链接博客中我们知道,在链接时需要把编译阶段产生的符号表进行汇总,并进行符号决议,然后才分配地址内存,如果将声明和定义分开,在没有函数定义的源文件中包含了有函数声明的头文件,在编译时,inline修饰的函数理应展开,但是inline规定,在编译时必须在每个调用点直接会展开,必须在源文件中看到完整的函数定义,只有声明时展不开的,此时会发生编译错误(尝试内联但找不到定义),即使不发生编译错误(取决于编译器),函数成功展开,最后生成的.o文件的符号表中也没有函数的地址(逻辑上内联函数既然已经展开了,也就不需要再去找函数的地址了)(如果定义也在头文件中的话,就能找到函数的地址了,因为定义的第一行的地址就是函数的地址),在链接阶段要汇总编译产生的符号表,进行符号决议和重定位,在符号表中找不到函数地址,会发生链接错误。 总而言之,把inline修饰的函数直接定义在头文件里一点问题都没有!
- 需要大量调用的函数,不要用内联或者宏,会造成代码的恶性膨胀 ,比如一个用inline修饰的小函数展开后有20行代码,只调用几次,不用建立函数栈帧,确实提高效率,但是要是调用1万次呢,代码量就会剧增,导致项目文件变得非常大,原来1个g的安装包,变成50个g,得不偿失。
6.nullptr
NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:
#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif
#endif
void f(int x)
{cout << "f(int x)" << endl;
}void f(int* ptr)
{cout << "f(int* ptr)" << endl;
}int main()
{f(0);//调用上边一个f(NULL);//在c++中,想要调用下边函数,但会错调成上边的函数//f((void*)0);//c语言中f(NULL);相当于f((void*)0);想调用下边的函数,类型不同,还需要强转f(nullptr);//成功调用下边的函数}
摘要
本文介绍了C++中宏的替代方案及其优缺点,重点讨论了内联函数(inline)和nullptr的应用。宏在C++中虽仍可使用,但存在副作用和调试困难等问题,建议用const/enum替代宏常量,用inline替代宏函数。内联函数在编译时展开,避免函数调用开销,但不适用于复杂或递归函数,且声明与定义需在同一文件以避免链接错误。此外,宏的优点在于节省空间和提升效率,但过度使用会导致代码膨胀。最后,文章指出C++11引入的nullptr解决了NULL在类型推导中的二义性问题,能明确表示空指针,提升代码安全性。