【C++从练气到飞升】23--内联函数、auto、范围for、nullptr

 🎈个人主页库库的里昂
收录专栏C++从练气到飞升
🎉鸟欲高飞先振翅,人求上进先读书🎉

目录

⛳️推荐

一、内联函数

1.1 定义

1.2 特性

二、auto关键字

2.1 简介

2.2 auto使用细则

2.3 不能使用auto的场景

三、基于范围的for循环

3.1 范围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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

库库的里昂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值