inline函数及auto关键字以及nullptr【c++】

目录

一、内敛函数

 如何观察函数是否展开

inline的特性

二、auto关键字

auto关键字的使用细则

三、指针空值nullptr(c++11)


一、内敛函数

函数的调用需要建立函数栈,每次调用函数都需要建立栈与销毁栈 ,这样的一个过程的开销虽然不是特别大,但当你在一个程序里需要多次调用一个函数,这就是一个不小的开销了。其实在c语言里面已经有了解决方案:宏函数。如下:

#define ADD(x,y) ((x)+(y))

 c++里面有他自己的解决方案,而不是沿用c语言里面的宏函数,这是为什么呢?

宏函数是有一些缺陷的:

  1. 最重要的一点,容易写错。经常要涉及到优先级的问题
  2. 没有类型安全检查,比如说上面的x,y你都不会去判断它是什么类型的数据就直接使用。
  3. 不能调试(程序在预编译的时候会直接替换,因此无法调试)

c++的解决方案就不会又面的问题,内敛函数:

inline 修饰的函数叫做内联函数, 编译时C++编译器会在 调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

他的具体作用就像将下面的代码:

b2f2830505cc4dd18526cd847cde72c7.png

变成下面这样,不过这只是一个形象的表示

int main()
{
	int x = 10, y = 10;
	int ret = add(x, y)
	{
		return x + y;
	}
	return 0;
}

 如何观察函数是否展开

1.在release和模式下查看,通过编译器生成的汇编语言是否又call add,有的话就说明调用了add函数并生成了栈。

2.在debug模式下查看,需要进行一些设置,因为debug模式是调试的模式,如果函数展开了就无法对内联函数进行调试了。

在vs2022可以通过以下的设置可以使程序在debug模式下也可以进行展开了。

d107524c296c44639463a98f42ba1474.png

以及928acaed0010434c9fe6bf546aceb430.png 

 然后我们观察他的汇编语言可以发现,函数在这里是没有调用的,而是直接在这里执行了指令:

e29248f8be3447f68fbc76f687033123.png

inline的特性

1.inline是一种通过空间换取时间的方式,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用。体现在你将函数展开会增加函数的代码量,这直接影响到可执行文件的大小(.exe)。

这里为什么说编译器将函数当作内联函数处理,因为你申明的这个内敛函数是否展开最终是由你的编译器决定的。因为如果你定义了一个递归函数或者几十几百行的函数,那编译器每遇到一个就展开的话会显得非常不合理,虽然是以空间换时间,但是你这空间消耗的太多也不合理。

2. inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。        

321c9b7312aa4bd08944a26b59464b06.png

 3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了(因为不需要去查找函数的地址了),链接就会找不到。

二、auto关键字

随着程序越来越复杂,程序中用到的数据类型也越来越复杂了,体现在:

1.类型难于拼写

2.含义不明确导致出错

比如:

std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange", "橙子" }, 
   {"pear","梨"} };
 std::map<std::string, std::string>::iterator it = m.begin();

这就是一个名字非常复杂的数据类型,这时候我们可以用auto关键字来解决这一个尴尬的问题。

auto的作用是进行类型的自动识别,用法如下:

int x;
auto e=x;

在这一段代码里面,auto e自动识别了x是int类型的,并给e声明了一个int类型的空间。

auto关键字的使用细则

1.auto与指针和引用结合起来使用

auto在声明指针类型的数据时,用auto 与auto*没有任何区别。但是auto与引用结合起来就不一样了。

int main()
{
    int x = 10;
    auto a = &x;
    auto* b = &x;
    auto& c = x;//这里就相当于int &c=x;
    cout << typeid(a).name() << endl;//这里的作用是将括号内的数据类型打印出来。
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    *a = 20;
    *b = 30;
     c = 40;
    return 0;
}

43450c4116bf4caf9843cf97bdf08630.png

 2.在同一行定义多个变量

在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将进行报错,因为编译器只对第一个变量进行推导,然后对推导出来的类型定义其他的变量。

void TestAuto()
{
    auto a = 1, b = 2; 
    auto c = 3, d = 4.0;  // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

3.auto不能推导的场景

(1).auto不能作为函数的参数

void TestAuto(auto a)
{}

因为在调用函数的时候需要建立一个栈,auto定义的类型无法确定它的大小,有可能导致建立的栈的大小还不足以放这个变量。

(2)auto不能迎来声明数组

void TestAuto()
{
    int a[]={1,2,3};
    auto b[]={4,5,6};
}

三、指针空值nullptr(c++11)

在c/c++98中的指针定义是有缺陷的,c++委员会为了语言兼容在后续c++的版本的兼容,没有改掉这个缺陷,而是给这个语法打了一个补丁加了nullptr。缺陷如下:

NULL实际上是一个宏,在传统的c头文件(stddef.h),可以看到如下代码:

#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif
可以看到, NULL 可能被定义为字面常量 0 ,或者被定义为无类型指针 (void*) 的常量。不论采取何
种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:
void f(int) 
{
 cout<<"f(int)"<<endl; 
}
void f(int*)
{
 cout<<"f(int*)"<<endl; 
}
int main()
{
 f(0);
 f(NULL);
 f((int*)NULL);
 return 0; 
}

虽然有语法构成重载,但是具体要调用哪个函数呢?

aaec4f4dd25f4a69a787929524d3ac69.png

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的
初衷相悖。
注意:
1. 在使用 nullptr 表示指针空值时,不需要包含头文件,因为 nullptr C++11 作为新关键字引入
2. C++11 中, sizeof(nullptr) sizeof((void*)0) 所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用 nullptr

猜你喜欢

转载自blog.csdn.net/qq_64484137/article/details/126960172
今日推荐