[C++] C++入门第二篇 -- 引用& -- 内联函数inline -- auto+for(下)

简介: [C++] C++入门第二篇 -- 引用& -- 内联函数inline -- auto+for(下)

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;
}

运行结果:

我们可以看到结果,这样写就把数组元素改了。

相关文章
|
1月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
23 0
|
1月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
24 0
|
1月前
|
分布式计算 Java 编译器
【C++入门(下)】—— 我与C++的不解之缘(二)
【C++入门(下)】—— 我与C++的不解之缘(二)
|
1月前
|
编译器 Linux C语言
【C++入门(上)】—— 我与C++的不解之缘(一)
【C++入门(上)】—— 我与C++的不解之缘(一)
|
6天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
29 4
|
7天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
25 4
|
30天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4
|
30天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
23 4
|
30天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)