inline
1.用inline修饰的函数叫做内联函数,它是C++的一个关键字。内联函数是C++为提高程序运行速度所做的一项改进。常规函数和内联函数之间的主要区别不在于编写方式,而在于C++编译器如何将他们组合到程序中。
2.内联函数在编译时会被编译器在调用处展开,而不是进行函数的调用,意味着内联函数在使用时不会建立栈帧区别于正常函数在调用时会建立栈帧,这样就可以提高效率了。
3.C语言实现宏函数也会在预处理时替换展开,但是宏函数的实现很复杂且很容易出现错误,还不方便调试,C++设计了inline的目的就是替代C语言中的宏函数。
下面举个实现加法的宏例子:
C语言掌握不熟悉的可能会出现以下问题:
下面对正确的宏进行详解一下,以便我们等会儿更方便地理解inline的作用:
eg:
宏的本质是替换,经过预处理后代码可以变成:
现有以下几个问题:
1.为什么不能加分号
因为宏函数我们是作为函数一样使用的,那我们去进行if条件语句的的判断时因为分号会把if语句结束了然后会导致else找不到匹配的if而发生语法报错,这个时候就是不行的。而像你在刚刚举的例子中多一个分号虽然不会报错也可以正常运行但我们不推荐。
2.为什么要加外面的括号
我们期望的是20,而运行的结果却是16.这是为什么呢,因为宏的本质是替换,换成int he = (1)+(3)* 5;这个之后因为*(乘)的优先级高于+,所以他先完成3*5然后再+1,就导致了运行结果偏离我们期望的结果了。所以加括号来控制优先级的问题。
3.为什么要加里面的括号
其实这个问题和上个问题类似。加括号也是为了控制优先级的问题,只不过这次是针对a,b,而不是针对和的问题。因为a和b也有可能是一个表达式,此时我们就需要先计算a,b的值再来求和了。
eg:
因为 &(逻辑与)|(逻辑或)的优先级没有+高,所以会先执行b+a,而不是a&b,a|b.
我们可以看到宏函数的实现操作难度有点大而他的目的仅仅只是不创建栈帧提高效率。而内联函数就没有这么多的问题,因为他的写法就和函数的写法是一样的,只不过需要在函数声明或定义前加一个关键字inline。
注意:宏函数虽然有函数二字,但它不是函数,它的本质是替换,也就没有栈帧的开销。
接下来我们看内联函数实现两数相加:
我们可以看到内联函数相比宏函数就方便多了。
如何查看它有没有被展开呢,我们可以通过反汇编来查看
我们可以看到反汇编中没有call指令调用函数,此时说明这个inline函数被展开了。
4.inline对于编译器而言只是一个建议,也就是说,你加了inline编译器也可以选择在调用的地方不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适用于频繁调用的短小函数,对于递归函数,代码相对多一些的函数,加上inline也会被编译器忽略。
5.inline不建议声明和定义分离到两个文件中,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时就会报错。
我们举一个例子:
我们看到编译错误,为什么呢,因为我们没有Add函数的地址,虽然有#include"f.h"但只有声明所以无法展开,链接错误。
nullptr
1.NULL实际上是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
但这样定义会有一些隐藏的风险,如:
我们可以看到Fun(int* p)和Fun(int a)是构成了重载函数的,那第二个应该打印Fun(int* p),但事实并非如此。因为我们也可以看到NULL是用宏定义成了0,所以不匹配Fun(int* p)这个函数的参数了。那我们如果进行强制类型转换会出现什么情况呢,展示如下:
我们可以看到编译都通不过。因为这里虽然强制类型转换了,但是传给两个函数都不匹配,然后这个时候就需要隐式类型转换,但是转换成int还是转换成int*呢,所以这时候就会错误。
2.C++中NULL可能被定义为字面常量0,或者C中被定义为无类型指针(void*)的常量。不论采取哪种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,本想通过Fun(NULL)调用Fun(int*p)函数,但是由于NULL被定义为0,反而调用了Fun(int x)函数,与我们所期望的违背。而强制类型转换Fun((void*)NULL)甚至会报错。
还有一个小的例子,我们在前面两个章节中说过C++兼容C,但是C++更严格一些,所以这样写是不行的。代码展示:
左边是C++右边是C语言,我们可以看到C++出现报错而C语言可以通过是因为在这里它支持隐式类型的转换也就是可以void*转换成int*,而C++想要通过只能把红色代码换为int* q =(int*) p;也就是我们需要强制类型转换。
3.C++11中引入了nullptr来解决这个问题。nullptr是一个特殊的关键字,nullptr是一种特殊类型的字面常量,它可以转换成任意的其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题。
此时,我们再来看上面的指针参数函数Fun(int*p)就可以通过了,代码展示:
注意:nullptr只能隐式地转换成指针类型,不能转换成整数类型。
也就说这种是不被允许的int p = nullptr;。