@C++
【Lesson 3.内联函数和auto关键字和指针空值nullptr】
1. 内联函数
a. 概念
‘为了解决一些多次调用但是代码量小的函数引起的大量消耗栈空间(内存)的问题’
1.以inline修饰的函数叫内联函数。编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销
inline int Add(int left, int right)
{
return left + right;
}
int main()
{
int a = 10;
int b = 20;
int ret = Add(a, b);//查看反汇编,debug和release版本
return 0;
}
在release模式下,编译器生成的汇编代码不存在call Add
int a = 10;
int b = 20;
int ret = Add(a, b);//查看反汇编,debug和release版本
return 0;
在debug模式下,编译器生成的汇编代码存在call Add
因为在的debug模式下,需要供程序员进行调试,编译器默认不会对代码进行优化
int a = 10;
00007FF7354720AB mov dword ptr [a],0Ah
int b = 20;
00007FF7354720B2 mov dword ptr [b],14h
int ret = Add(a, b);//查看反汇编,debug和release版本
00007FF7354720B9 mov edx,dword ptr [b]
00007FF7354720BC mov ecx,dword ptr [a]
00007FF7354720BF call Add (07FF73547147Eh)
00007FF7354720C4 mov dword ptr [ret],eax
return 0;
- 在debug模式下,编译器一般是不会将内联函数直接展开,因为在debug模式下,用户要调试代码。
b. 特性
i. inline是一种以空间换时间的做法
以代码复制为代价,仅仅省去函数调用的开销,从而提高函数执行效率。所以=代码很长或者有循环/递归的函数,复制的消耗比调用消耗还大,那么就不适宜使用作为内联函数
(1)函数内代码很长,使用内联将导致函数消耗代价过高
(2)如果函数体内出现循环/递归,那麽执行函数体内代码的时间要比函数调用开销大
ii. inline对于编译器而言只是一个建议
我们声明了内联函数,编译器会自动优化,定义为inline的函数体内有循环/递归等,函数较为复杂编译器会忽略掉内联,如果调用函数不复杂,则在调用的地方展开,执行内联函数。
iii. inline不建议声明和定义分离
分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
2. auto关键字(C++11)
a. auto简介
i. auto不再是一个存储类型指示符。而是作为一个新的类型指示符来指示编译器,auto声明的变量必须有编译器在编译时期推导得。
int main()
{
auto a = 10;//在写代码时,auto就是一个占位符
auto b = 12.3;
auto c = 'c';
cout << typeid(a).name()<< endl;
cout << typeid(b).name()<< endl;
cout << typeid(c).name() << endl;
return 0;
}
int
double
char
注:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要=根据初始化表达式来推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量的实际类型。
b. auto的使用细则
i. auto与指针和引用结合起来使用
int a=12;
auto* pa=&a;//auto声明指针变量时auto和auto*没有区别
auto pa=&a;
auto& ra=a;//auto生命引用类型时则必须要加上&
ii. 在同一行定义多个变量
int main()
{
auto a = 12, b = 34, c = 56;
auto a = 12, b = 1.2, c = 'c';//编译失败
return 0;
}
为什么声明同一行变量时,变量应为同一类型?
因为编译器实际上只会对第一个类型进行推导,然后用推到的类型定义其他变量
c. auto不能推导的场景
i. auto不能作为函数的参数
ii. auto不能直接用来声明数组
3. 基于范围的for循环(C++11)
a. 范围for的语法
对于一个有范围的数组,由程序员说明范围是多余的。
因此C++11中,引进了 基于范围的for循环
int main()
{
int array[] = { 1,2,3,4,5,6,7,8,9,0 };
//e就是array数组的每个元素的一份拷贝
for (auto e : array)//C++范围for循环
cout << e << " ";
cout << endl;
/*
int* p = array; //无法使用指针表示数组被迭代的范围
for (auto e : p)//int *的范围是不具体的的——编译器编译阶段无法确定p所表示的范围
*/
//e就是array每个元素的别名
for (auto& e : array)
cout << e << " ";
cout << endl;
return 0;
}
1 2 3 4 5 6 7 8 9 0
1 2 3 4 5 6 7 8 9 0
b. 范围for的使用条件
范围for迭代的范围必须是确定的,如果使用指针代替数组,那么就无法确定其迭代范围。(上述代码)
4. 指针空值nullptr
C++98中的指针空值
NULL实际就是一个宏,在传统的c头文件中<stdio.h>
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以知道,NULL可能被定义为常量0,也可以被定义为无类型指针(void*)的常量
void Test(int* a)
{
cout << "Test(int* a)" << endl;
}
void Test(int a)
{
cout << "Test(int a)" << endl;
}
int main()
{
int a = 1;
int* b = &a;
int* pd = NULL;//NULL表示空指针
Test(a);
Test(b);
Test(pd);
Test(NULL);
Test((int*)NULL);//NULL表示空指针,应该调用Test(int*
Test(0);
int* pc = nullptr;//空值指针:(void*)0的指针
Test(pc);
Test(nullptr);
return 0;
}
Test(int a)
Test(int* a)
Test(int* a)
Test(int a)
Test(int* a)
Test(int a)
Test(int* a)
Test(int* a)
通过f(NULL)调用指针版本的f((int *))函数,,但是由于NULL被定义为了0,与程序相违背。
而在C++11中nullptr表示指针空值(void *)0的指针。
注意:
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
- 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。