2、内联函数
2.1 概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
我们来看一下平常我们写的代码:
int Add(int x, int y) { return x + y; } int main() { int ret = 0; ret = Add(1, 2); return 0; }
我们可以看到,这里是在调用函数,但是我们要是不断要用Add函数的时候,不断的调用效率会比较低,因此在C++中,我们引入了内联函数(inline)。
inline int Add(int x, int y) { return x + y; } int main() { int ret = 0; ret = Add(1, 2); return 0; }
我们可以看到,加了inline变为内联函数后,就不再是调用了,直接用函数体替换了函数调用,不用开栈帧,可以提高效率。
看到这是不是想到,C++的内联函数像是C语言的宏。
C++中的内联函数确实和C语言的宏用途是一样的,对于短小且频繁调用的函数,C语言用宏来代替函数,C++中用内联函数。
C++是全面兼容C语言的,我们直接用宏就可以了,那为什么我们还要使用内联函数呢?
1、宏在写的时候容易出错,且没有类型的检查,还不能调试。
2、内联函数会对参数的类型进行检查,还可以调试,书写上就是正常的写功能函数,在返回值类型前加inline。
如果想要看底层是调用还是直接展开的,查看方式:
1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2019的设置方式)
2.2 特性
1. inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
2. inline对于编译器而言只是一个建议,编译器会自动优化,如果内联函数内存在循环/递归的时候,编译器会自动优化忽略掉内联。(一般建议10行以内)
3. inline不建议声明和定义分离,分离会导致链接错误。因为inline直接再调用处被展开,(不会出现在符号表中)就没有函数地址了,链接就会找不到。
对于第二点我们做一下实验:
inline int Add(int x, int y) { int sum = x + y; sum += x * y; sum += x * y; sum += x * y; sum += x * y; sum += x * y; sum += x * y; sum += x * y; sum += x * y; sum += x * y; return sum; } int main() { int ret = 0; ret = Add(1, 2); return 0; }
这里内联函数函数体一共写了十一行就算是函数调用了。
3、auto
3.1 auto简介
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量。C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
auto我们在C语言期间就接触过,C语言期间定义的局部变量默认是用auto修饰,因此我们在定义变量的时候从来不加auto,也就没人在意。但是到了C++11时期,auto有了新的身份,它可以自动推导类型。
我们来看一段代码,看看auto的自动推导类型:
int testAuto() { return 1; } int main() { int a = 0; auto b = a; auto c = 'c'; auto ret = testAuto(); cout << typeid(b).name() << endl; cout << typeid(c).name() << endl; cout << typeid(ret).name() << endl; return 0; }
运行结果:
这段代码里面 typeid(变量名).name() 是推导变量类型的一个函数。
我们可以看到auto很智能,可以根据赋的值来推导类型。
注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
3.2 auto的使用细则
1. auto与指针和引用结合起来使用:用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&。
int main() { int x = 10; auto a = &x; auto* b = &x; auto& c = x; cout << typeid(a).name() << endl; cout << typeid(b).name() << endl; cout << typeid(c).name() << endl; return 0; }
运行结果:
2. 在同一行定义多个变量:当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
void TestAuto() { auto a = 1, b = 2; auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同 }
3.3 auto不能推导的场景
1. auto不能作为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导 void TestAuto(auto a) {}
2. auto不能直接用来声明数组
void TestAuto() { int a[] = {1,2,3}; auto b[] = {4,5,6}; }
3.4 auto与for合用
按照C语言我们的写法,遍历数组是下面的代码
int main() { int array[] = { 1, 2, 3, 4, 5 }; for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i) array[i] *= 2; for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i) cout << array[i] << " "; cout << endl; return 0; }
运行结果:
我们现在也可以使用auto这样来遍历数组:
int main() { int array[] = { 1, 2, 3, 4, 5 }; for (auto e : array) cout << e << " "; return 0; }
运行结果:
我们这里使用的是范围 for ,for循环后的括号由冒号”:“分为两个部分:第一部分是范围内用于迭代的变量,第二部分表示迭代的范围。这里会自动判断结束的。
这里的e是取到数组里的元素,然后打印,不会影响数组元素。
如果想改变数组元素,我们可以使用auto& e,这是对数组元素起别名,直接改变数组元素,auto取到元素后会自动推导类型的。如下:
int main() { int array[] = { 1, 2, 3, 4, 5 }; for (auto& e : array) e *= 2; for (auto e : array) cout << e << " "; return 0; }
运行结果:
我们可以看到结果,这样写就把数组元素改了。