【C++杂货铺】内联函数、auto、范围for、nullptr
一、内联函数
普通的函数在调用的时候会开辟函数栈帧,会产生一定量的消耗,在C语言中可以用宏函数来解决这个问题,但是宏存在以下缺陷:复杂、容易出错、可读性差、不能调试。为此,C++中引入了内联函数这种方法。
1.1 定义
以inline
修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,所以内联函数可以提高程序的运行效率。
🪆普通函数:
int Add(int x, int y)//这里的Add是一个普通函数
{
return x + y ;
}
int main()
{
int ret = 0;
ret = Add(3, 5);
cout << ret << endl;
return 0;
}
🪆内联函数:
inline int Add(int x, int y)
{
return x + y ;
}
int main()
{
int ret = 0;
ret = Add(3, 5);
cout << ret << endl;
return 0;
}
注意:在默认的Debug模式下,内联函数是不会展开的,需要进行设置,设置过程如下:
1.2 特性
- inline是一种以时间换空间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。缺陷:可能会使目标文件变大,优点:少了调用开销,提高程序运行效率。
- inline对编译器而言只是建议,不同的编译器关于inline的实现机制可能不同,一般建议:将函数规模小的(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
- inline建议函数声明和定义不能分离,因为内联函数在预处理阶段就直接展开,因此内联函数不会进符号表,因此如果声明和定义分离,头文件只有声明,在预处理阶段,头文件展开,只知道该函数是一个内联函数,没有对应函数的定义,因此就无法完成替换,那就只能等通过call在链接阶段去找该函数,但是它是内联函数,没有进符号表,所以链接阶段就会报错。
🪆为什么是函数规模小?
假设一个函数经过编译,得到五十条汇编指令。普通情况下,调用此函数只需要一条call指令,调用10000此也就10000条call指令,但是如果把这个函数设置成内联函数,指令的数量就会大大增加,因为内联函数完成的是替换,把所有调用它的地方,都用函数体去替换,这也就意味着,原来1条call指令就能完成的任务,现在替换后就变成了50条指令,假如还是调用了10000次该函数,那就从10000条call指令,变成了500000条指令,其实这就是代码膨胀。
inline int Add(int x, int y)
{
cout << "xxxxxxxxxxxx" << endl;
cout << "xxxxxxxxxxxx" << endl;
cout << "xxxxxxxxxxxx" << endl;
cout << "xxxxxxxxxxxx" << endl;
cout << "xxxxxxxxxxxx" << endl;
cout << "xxxxxxxxxxxx" << endl;
cout << "xxxxxxxxxxxx" << endl;
cout << "xxxxxxxxxxxx" << endl;
cout << "xxxxxxxxxxxx" << endl;
cout << "xxxxxxxxxxxx" << endl;
cout << "xxxxxxxxxxxx" << endl;
cout << "xxxxxxxxxxxx" << endl;
return x + y ;
}
int main()
{
int ret = 0;
ret = Add(3, 5);
cout << ret << endl;
return 0;
}
对于上面函数体比较长的函数,即使我们人为规定了它是内联,但最终还是通过call指令去调用函数。
🪆为什么是被频繁调用?
因为普通函数在调用的时候会创建函数栈帧,若频繁调用就会频繁的创建栈帧,增加消耗。宏和内联,就是为了解决开销问题。如果调用的次数不多,开辟一点栈帧是无所谓的。
二、auto关键字
2.1 简介
C++11中规定:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。简单来说,auto会根据表达式自动推导类型。
int main()
{
int a = 0;
auto b = a;//自动推导出b的类型是int
auto c = 1.11 + 1;//自动推导出c的类型是double
cout << typeid(b).name() << endl;//typeid可用来查看变量类型
cout << typeid(c).name() << endl;
return 0;
}
🪆注意:
使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式,来推导auto的实际类型。因此,auto并非是一种“类型”的声明,而是一个类型声明的“占位符”,编译器在编译阶段会将auto替换为变量实际的类型。
int main()
{
auto a;//错误,必须要初始化
return 0;
}
2.2 auto使用细则
🪆auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和aauto*没有任何区别,但是auto声明引用类型时,必须要加&,如下,如果c不加&的话,就是x的一份拷贝。
int main()
{
int x = 10;
auto a = &x;//根据右边推出,a是一个指针类型
auto* b = &x;//右边必须是一个地址,因为前面加了*
auto& c = x;//引用必须要加&
}
🪆在同一行定义多个变量
当在同一行声明多个变量的时候,这些变量必须是相同的类型,否则编译器会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
int main()
{
auto a = 10, b = 30;
auto c = 60, d = 1.1;//该行编译失败,c和d的初始化类型不同
}
2.3 不能使用auto的场景
- auto不能作为函数的参数
//错误,编译器无法对x的实际类型进行推导
void Text(auto x)
{}
- ·auto不能直接用来声明数组
void Text()
{
//auto arr[] = { 1, 2, 3 };//错误写法,请勿模仿
int arr[] = {1, 2, 3}//这才是正确写法
}
小Tips:auto在实际中常被用在:基于范围的for循环中、还有lambda表达式中、其次就是一些非常非常长的类型,也会用auto进行替换。
三、基于范围的for循环
🪆C++98中遍历一个数组:
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)//通过下标访问
array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)//通过指针访问
cout << *p << endl;
}
🪆C++98中遍历一个数组:
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还容易犯错误。因此C++11中引入了基于范围for循环。for循环后的括号由冒号“ : ”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)//加引用可以对后面的值修改
e *= 2;
for(auto e : array)
cout << e << " ";
return 0;
}
3.1 范围for的使用条件
- for循环迭代的范围必须是确定的
- 迭代的对象要实现++和==的操作
对数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end方法,begin和end就是for循环的迭代范围。范围for本质上是迭代器,支持迭代器就支持范围for。
void Text(int arr[])//arr本质上只是一个地址,没有范围
{
for (auto a : arr)//错误
{
cout << a << endl;
}
}
四、指针空值nullptr
良好的编程习惯要求我们,在声明一个变量时最好给该变量一个合适的初始值,否则可能出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们一般会把它置空。
🪆回顾NULL:
void f(int)
{
cout << "f(int)" << endl;
}
void f(int*)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(NULL);
return 0;
}
上述代码的本意是:希望通过f(NULL);
去调用void f(int*)
,但是通过执行结果可以看出,f(NULL);
调用的是void f(int)
。这是因为NULL
被定义成了0,且C++98中规定,字面常量0,既可以是一个整型数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成一个整型常量,如果要将其按照指针的方式来使用,必须对其进行强制类型转换(void*)0
。
🪆认识nullptr:
nullptr
用来表示指针空值,因为nullptr是C++11作为新关键字引入的,所以在使用的时候不需要包头文件。C++11中,sizeof(nullptr)
和sizeof((void*)0)
所占字节数相同。
int main()
{
cout << sizeof(nullptr) << endl;
cout << sizeof((void*)0) << endl;
return 0;
}
今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是春人前进的动力!
- 点赞
- 收藏
- 关注作者
评论(0)