C++基础:内联函数,auto关键字,nullptr

简介: C++基础:内联函数,auto关键字,nullptr

目录

一.内联函数

1.回顾c语言中的“宏函数”

2.内联函数

3.内联函数的特性

二.C++ auto 关键字

1.auto的基本概念

2.auto使用的注意事项

3.auto不能使用的地方

三. C++11中的 nullptr

一.内联函数
1.回顾c语言中的“宏函数”
先给出一段简单的代码:

int Add(int left, int right)
{

return left + right;

}

int main()
{

int ret = 0;
ret = Add(1, 2);
return 0;

}
转到汇编代码:

可见为了实现一个简单的加法,调用Add函数要执行的汇编指令很多,而且为了调用函数还要执行指令跳转(并且要在栈区上为函数开辟栈帧空间),如果Add函数被重复大量地使用,则会消耗很大一部分系统性能。因此C语言中为了提高程序的运行效率,对于类似的简单函数(注意仅限于非递归且简短的函数),我们常使用宏来替代:

define Add(X,Y) ((X)+(Y))

int main()
{

int ret = 0;
ret = Add(1, 2);
return 0;

}
宏的作用相当于代码语句的替换,上面代码段中的宏,是把Add(X,Y)形式的语句替换成((X)+(Y)),这种替换的过程是在预处理的阶段完成的。

使用宏替换后,运行上面的代码段,转到反汇编:

可见使用宏代替那些简短的非递归(且会被大量使用)的函数可以一定程度上提高程序的性能

但是由于宏的本质是代码替换,所以有时候会让代码变得混乱难以维护,而且宏本身的使用容易出错,C++就提供了一种类似的语法机制--内联函数来代替宏。

2.内联函数
inline关键字修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的汇编指令处(call指令)将被调函数展开成一系列汇编指令并在主函数的栈帧空间中实现被调函数的功能(类似于宏替换,但不是在预处理的阶段完成的),系统无需为被调函数建立函数栈帧,从而提升了程序运行的效率。

用之前的例子举例说明,使用inline修饰Add函数前:

使用inline修饰Add函数后:

可见系统并没有为inline Add函数建立函数栈帧,也没有执行任何指令跳转,程序性能有所提升。(但是注意,Add函数的函数体(包含其指令段)依然被原模原样地存放在只读常量区,只是编译器在编译时将函数体中必要的指令“搬”到了主函数的指令段中取代了call指令)。

3.内联函数的特性
(1)内联函数是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用能够完成函数功能的指令段替换调用函数的call指令。

缺陷:可能会使目标文件变大(汇编指令是要占内存的,编译器用一系列指令段替换call指令会使文件的总指令条数增加。)

优势:少了调用函数的系统开销,提高程序运行效率。

(2)inline只能修饰一些功能简单,代码段简短的非递归函数,如果inline用于修饰一个复杂的函数,则编译器在编译时会自动忽略inline关键字,所以inline对于编译器而言只是一个建议性的关键字而不是要强制执行的命令。

(3)内联函数(被inline修饰的函数)不建议将其声明和定义分离(只用定义即可,定义本身也是一种声名),分离会导致链接错误。因为使用inline,调用函数时call指令被替换了,没有call指令,链接器就链接不到函数体的指令段了。(因此内联函数一般和主函数定义在同一个源文件中)

二.C++ auto 关键字
1.auto的基本概念
C++11中,auto用于定义变量,auto定义的变量的类型由变量定义和初始化语句等号的右边的值的类型决定,auto作为一个类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
比如:

int TestAuto()
{

return 10;

}

int main()
{

int a = 10;
auto b = a;                   //变量b c d的类型由编译器根据等号右边的值自动识别
auto c = 'a';                 //typeid(b).name() 是一个可以返回类型名字符串的方法
auto d = TestAuto();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
return 0;

}

在面向对象的复杂编程中, 有时我们很难确定一个表达式的返回值会是什么类型的,这种时候就可以用auto声明的变量来接收表达式的值。还有些时候,C++编程中,表达式的值是一些很复杂的自定义类型值,这时也可以用auto声明的变量来接收表达式的值。

比如如下场景:

include

include <time.h>

include

include

using std::cout;
using std::endl;

int main()
{

std::map<std::string, std::string> m{ { "apple", "苹果" }, 
                                      { "orange","橙子" },
                                      {"pear","梨"} 
                                     };

auto it  = m.begin();         用auto声名的变量it来接收表达式的返回值

while (it != m.end())
{
    //....
}
return 0;

}

上面代码段中的std::map<std::string, std::string>就是一个复杂的类型,m.begin()返回值就可以用auto声名的变量it来接收,十分方便。

typedef(类型重定义)也可以简化上述代码,但是typedef用于重定义指针类型并用于声明变量时,无法用const来保护指针所指向内存空间。

比如:

typedef char* pstring;
int main()
{

const pstring p1=NULL;
return 0;

}

因此typedef的使用也是由局限性,相比之下auto的使用更方便灵活。

2.auto使用的注意事项
(1) 使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型

(2)auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型

(3)用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须
加&
比如:

int main()
{

int x = 10;
auto a = &x;
auto* b = &x;           a,b的类型最终是一样的
auto& c = x;            想定义引用必须在auto后面加上&
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;

}

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

比如:

void TestAuto()
{

auto c = 3, d = 4.0;   该行代码会编译失败,因为c和d的初始化表达式类型不同

}

3.auto不能使用的地方

  1. auto不能作为函数的参数
  2. auto不能直接用来声明数组

比如:

void TestAuto()
{

int a[] = {1,2,3};
auto b[] = {4,5,6};   无法通过编译

}
三. C++11中的 nullptr
在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。在C语言中,如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:

void TestPtr()
{

int* p1 = NULL;
// ……

}
NULL实际是一个宏,在编译时NULL会被替换为整形0,因此使用NULL在类型上并不严谨,在一些极端情形下可能会导致错误。

于是C++11 将nullptr作为新关键字引入,用于表示指针空值。

为了提高代码的健壮性,在表示指针空值时建议最好使用nullptr

void TestPtr()
{

int* p1 = nullptr;
// ……

}

相关文章
|
1月前
|
安全 编译器 C++
C++ `noexcept` 关键字的深入解析
`noexcept` 关键字在 C++ 中用于指示函数不会抛出异常,有助于编译器优化和提高程序的可靠性。它可以减少代码大小、提高执行效率,并增强程序的稳定性和可预测性。`noexcept` 还可以影响函数重载和模板特化的决策。使用时需谨慎,确保函数确实不会抛出异常,否则可能导致程序崩溃。通过合理使用 `noexcept`,开发者可以编写出更高效、更可靠的 C++ 代码。
39 0
|
3月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
3月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
11天前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
51 18
|
11天前
|
存储 编译器 数据安全/隐私保护
【C++面向对象——类与对象】CPU类(头歌实践教学平台习题)【合集】
声明一个CPU类,包含等级(rank)、频率(frequency)、电压(voltage)等属性,以及两个公有成员函数run、stop。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。​ 相关知识 类的声明和使用。 类的声明和对象的声明。 构造函数和析构函数的执行。 一、类的声明和使用 1.类的声明基础 在C++中,类是创建对象的蓝图。类的声明定义了类的成员,包括数据成员(变量)和成员函数(方法)。一个简单的类声明示例如下: classMyClass{ public: int
37 13
|
11天前
|
编译器 数据安全/隐私保护 C++
【C++面向对象——继承与派生】派生类的应用(头歌实践教学平台习题)【合集】
本实验旨在学习类的继承关系、不同继承方式下的访问控制及利用虚基类解决二义性问题。主要内容包括: 1. **类的继承关系基础概念**:介绍继承的定义及声明派生类的语法。 2. **不同继承方式下对基类成员的访问控制**:详细说明`public`、`private`和`protected`继承方式对基类成员的访问权限影响。 3. **利用虚基类解决二义性问题**:解释多继承中可能出现的二义性及其解决方案——虚基类。 实验任务要求从`people`类派生出`student`、`teacher`、`graduate`和`TA`类,添加特定属性并测试这些类的功能。最终通过创建教师和助教实例,验证代码
37 5
|
11天前
|
存储 算法 搜索推荐
【C++面向对象——群体类和群体数据的组织】实现含排序功能的数组类(头歌实践教学平台习题)【合集】
1. **相关排序和查找算法的原理**:介绍直接插入排序、直接选择排序、冒泡排序和顺序查找的基本原理及其实现代码。 2. **C++ 类与成员函数的定义**:讲解如何定义`Array`类,包括类的声明和实现,以及成员函数的定义与调用。 3. **数组作为类的成员变量的处理**:探讨内存管理和正确访问数组元素的方法,确保在类中正确使用动态分配的数组。 4. **函数参数传递与返回值处理**:解释排序和查找函数的参数传递方式及返回值处理,确保函数功能正确实现。 通过掌握这些知识,可以顺利地将排序和查找算法封装到`Array`类中,并进行测试验证。编程要求是在右侧编辑器补充代码以实现三种排序算法
27 5
|
11天前
|
Serverless 编译器 C++
【C++面向对象——类的多态性与虚函数】计算图像面积(头歌实践教学平台习题)【合集】
本任务要求设计一个矩形类、圆形类和图形基类,计算并输出相应图形面积。相关知识点包括纯虚函数和抽象类的使用。 **目录:** - 任务描述 - 相关知识 - 纯虚函数 - 特点 - 使用场景 - 作用 - 注意事项 - 相关概念对比 - 抽象类的使用 - 定义与概念 - 使用场景 - 编程要求 - 测试说明 - 通关代码 - 测试结果 **任务概述:** 1. **图形基类(Shape)**:包含纯虚函数 `void PrintArea()`。 2. **矩形类(Rectangle)**:继承 Shape 类,重写 `Print
33 4
|
11天前
|
设计模式 IDE 编译器
【C++面向对象——类的多态性与虚函数】编写教学游戏:认识动物(头歌实践教学平台习题)【合集】
本项目旨在通过C++编程实现一个教学游戏,帮助小朋友认识动物。程序设计了一个动物园场景,包含Dog、Bird和Frog三种动物。每个动物都有move和shout行为,用于展示其特征。游戏随机挑选10个动物,前5个供学习,后5个用于测试。使用虚函数和多态实现不同动物的行为,确保代码灵活扩展。此外,通过typeid获取对象类型,并利用strstr辅助判断类型。相关头文件如&lt;string&gt;、&lt;cstdlib&gt;等确保程序正常运行。最终,根据小朋友的回答计算得分,提供互动学习体验。 - **任务描述**:编写教学游戏,随机挑选10个动物进行展示与测试。 - **类设计**:基类
26 3
|
2月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
78 2