大家好,我是店小二。今天,我们将深入探讨 C++11 中新增的特性。在之前的学习过程中,大家或许已经接触或掌握了一些相关特性。让我们继续前进,探索更多 C++11 的精彩内容吧!
🌈个人主页:是店小二呀
🌈C语言专栏:C语言
🌈C++专栏: C++
🌈初阶数据结构专栏: 初阶数据结构
🌈高阶数据结构专栏: 高阶数据结构
🌈Linux专栏: Linux
🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅
文章目录
- 一、C++简介
-
- [1.1 C++11简介](#1.1 C++11简介)
- [1.2 C++发展小故事](#1.2 C++发展小故事)
- [1.3 C++历史版本](#1.3 C++历史版本)
- 二、列表初始化
-
- [2.1 类使用"花括号"初始化](#2.1 类使用"花括号"初始化)
-
- [2.1.1 构造函数及其隐式类型转化](#2.1.1 构造函数及其隐式类型转化)
- [2.1.2 列表初始化介绍](#2.1.2 列表初始化介绍)
- [2.1.3 列表初始化取消等号](#2.1.3 列表初始化取消等号)
- [2.2 容器统一使用"花括号"初始化](#2.2 容器统一使用"花括号"初始化)
-
- [2.2.1 "花括号"隐式类型转化不足](#2.2.1 "花括号"隐式类型转化不足)
- [2.2.1 花括号不全是intitailizer_list](#2.2.1 花括号不全是intitailizer_list)
- 三、简化声明
-
- [3.1 auto](#3.1 auto)
- [3.2 decltype](#3.2 decltype)
-
- [3.2.1 单纯使用auto不行吗?](#3.2.1 单纯使用auto不行吗?)
- 四、nullptr
- 五、范围for循环
- 六、STL中一些变化
一、C++简介
1.1 C++11简介
在2003年C++标准委员会曾经提交了一份技术勘误表(简称TC1),使得C++03这个名字已经取代了C++98称为C++11之前的最新C++标准名称。不过由于C++03(TC1)主要是对C++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为C++98/03标准。从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。
相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习。
C++11增加的语法特性非常篇幅非常多,我们这里没办法一 一讲解,如果想要深入研究的话,可以点击该链接进行深入研究https://en.cppreference.com/w/cpp/11
1.2 C++发展小故事
1979年,贝尔实验室的本贾尼等人试图分析unix内核的时候,试图将内核模块化,于是在C语言的基础上进行扩展,增加了类的机制,完成了一个可以运行的预处理程序,称之为C with classes。
1998年是C++标准委员会成立的第一年,本来计划以后每5年视实际需要更新一次标准,C++国际标准委员会在研究C++ 03的下一个版本的时候,一开始计划是2007年发布,所以最初这个标准叫C++ 07。但是到06年的时候,官方觉得2007年肯定完不成C++07,而且官方觉得2008年可能也完不成。最后干脆叫C++ 0x。x的意思是不知道到底能在07还是08还是09年完成。结果2010年的时候也没完成,最后在2011年终于完成了C++标准。所以最终定名为C++11
1.3 C++历史版本
语言的发展就像是练功打怪升级一样,也是逐步递进,由浅入深的过程。我们先来看下C++的历史版本。
阶段 | 内容 |
---|---|
C with classes | 类及派生类、公有和私有成员、类的构造和析构、友元、内联函数、赋值运算符 重载等 |
C++1.0 | 添加虚函数概念,函数和运算符重载,引用、常量等 |
C++2.0 | 更加完善支持面向对象,新增保护成员、多重继承、对象的初始化、抽象类、静 态成员以及const成员函数 |
C++3.0 | 进一步完善,引入模板,解决多重继承产生的二义性问题和相应构造和析构的处 理 |
C++98 | C++标准第一个版本,绝大多数编译器都支持,得到了国际标准化组织(ISO)和美 国标准化协会认可,以模板方式重写C++标准库,引入了STL(标准模板库) |
C++03 | C++标准第二个版本,语言特性无大改变,主要:修订错误、减少多异性 |
C++05 | C++标准委员会发布了一份计数报告(Technical Report,TR1),正式更名 C++0x,即:计划在本世纪第一个10年的某个时间发布 |
C++11 | 增加了许多特性,使得C++更像一种新语言,比如:正则表达式、基于范围for循 环、auto关键字、新容器、列表初始化、标准线程库等 |
C++14 | 对C++11的扩展,主要是修复C++11中漏洞以及改进,比如:泛型的lambda表 达式,auto的返回值类型推导,二进制字面常量等 |
C++17 | 在C++11上做了一些小幅改进,增加了19个新特性,比如:static_assert()的文 本信息可选,Fold表达式用于可变的模板,if和switch语句中的初始化器等 |
C++20 | 自C++11以来最大的发行版,引入了许多新的特性,比如:模块(Modules)、协 程(Coroutines)、范围(Ranges)、概念(Constraints)等重大特性,还有对已有 特性的更新:比如Lambda支持模板、范围for支持初始化等 |
C++23 | 制定ing |
C++还在不断的向后发展。但是:现在公司主流使用还是C++98和C++11,所有大家不用追求最新,重点将C++98和C++11掌握好,等工作后,随着对C++理解不断加深,有时间可以去琢磨下更新的特性。
二、列表初始化
在C++98中,标准允许使用花括号{}对数组或者结构体元素 进行统一的列表值初始化设定(注意:这里跟初始化列表不是一个东西)
cpp
struct Point
{
int _X;
int _y;
};
int main()
{
//c
int array1[] = { 1,2,3,4,5 };
int array2[5] = { 0 };
Point p = { 1,2 };
return 0;
}
2.1 类使用"花括号"初始化
2.1.1 构造函数及其隐式类型转化
如果类对象想使用花括号{}进行"类似"数组与结构体那样进行列表初始化操作,可以考虑隐式类型转化。
cpp
class A
{
public:
A(int x, int y)
:_x(x)
, _y(y)
{}
A(int x)
:_x(x)
,_y(x)
{}
private:
int _x;
int _y;
};
int main()
{
//单参数的隐式类型转化
A aa2 = 1;
//多参数的隐式类型转化
A aa3 = { 1, 2 };
return 0;
}
**关于这里的隐式类型转化,会通过类中构造函数进行构造,返回A类型的临时变量(隐式类型转化)。**A类型对象以该临时变量进行拷贝构造。虽然使用了花括号 {}
来初始化 aa3
,但是这只是初始化列表的一种形式,不涉及 std::initializer_list
列表初始化
如果是通过类中构造函数返回该类型的临时对象,那么临时变量具有常性 。对于A& aa3 = {3, 2}
这里权限被放大,那么想使用引用接收,就要添加const减低权限接收const A& aa3 = {3, 2}
。
2.1.2 列表初始化介绍
关于initializer_list本质是常量数组,实际上没有为自己开辟空间,而是内部存在两个指针,一个first一个last类似first指向第一个元素,last指向最后一个元素的后一个位置,去访问传递给它的初始化列表中的元素。
std::initializer_list一般是作为构造函数的参数,C++11对STL中的不少容器就增加std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=的参数,这样就可以用大括号赋值 。
如果这个内容感兴趣的,可以点击这个链接进行进一步的学习std::initializer_list相关文档链接
2.1.3 列表初始化取消等号
**C++11扩大了用大括号括起的列表(列表初始化)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用列表初始化时,可添加等号(=),也可不添加。**但是推荐添加=,提高可读性,也是方便我们看代码。
cpp
int main()
{
// C++11支持的列表初始化,这里会调用构造函数初始化
A a1{ 1,2 };
A a1 = { 1,2 };
int i{1};
return 0;
}
2.2 容器统一使用"花括号"初始化
2.2.1 "花括号"隐式类型转化不足
cpp
//vector(const T & x1)
//vector(const T& x1, const T& x2)
//vector(const T& x1, const T& x2, const T& x3)
//vector(const T& x1, const T& x2, const T& x3, const T& x4)
// ...
int main()
{
vector<int> v1;
vector<int> v2(10,1);
vector<int> v3 = { 1,2,3 };
vector<int> v4 = { 10,30,40 };
return 0;
}
通过上面的例子,X自定义 = Y类型->隐式类型转化 X(Y mm),X支持Y为参数类型构造就可用,但是导致了参数个数和类型是固定的,如果想要实现不同参数个数和类型,需要提前定义好,则具有很大的局限性
这做法是很麻烦的,容器想用不固定的{}数据个数初始化,对此C++11对STL中的不少容器就增加std::initializer_list作为参数的构造函数,这个构造一劳永逸的解决了问题,不用想上面那样麻烦的方式解决初始化问题。
cpp
vector(intitailizer__list<T> il);
vector<int> v = {1,2,3,4,5};//使用列表初始化。
2.2.1 花括号不全是intitailizer_list
关于花括号不一定都是intitailizer_list,比如pair和map
cpp
int main()
{
pair<string, string> kv1("sort", "排序");
pair<string, string> kv2("insert", "插入");
map<string, string> dict = { {"sort", "排序"},{"insert", "插入"} };
return 0;
}
- pair多参数隐式类型转化
- initializer_list 的构造
三、简化声明
c++11提供了多种简化声明的方式,尤其是在使用模板时 ,就有所体现
3.1 auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型腿断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初
始化值的类型。
问题 :请问auto y = j;
这里auto推出的类型是int&还是int呢?
cpp
int main()
{
int i = 0;
auto x = i;
//auto& x = i;
x++;
int& j = i;
auto y = j;
return 0;
}
通过观察,我们看成i、j公用一块空间,y有独自空间,所以auto推出类型是int而不是int&。auto
推断出的类型是基于变量实际存储的空间的类型(即变量的值的类型),而不是变量本身的类型或引用类型。
3.2 decltype
关键字decltype将变量的类型声明为表达式指定的类型
我们如果想要知道某个变量的类型,可用通过 cout << typeid(x).name << endl
进行打印查看,但是typeid(x).name
得到是表示类型的字符串,并不能定义变量
cpp
int main()
{
list<int>::iterator it1;
//typeif推出时一个单纯的字符串
cout << typeid(it1).name() << endl;
//不能用来定义对象
typeid(it1).name() it2;
return 0;
}
3.2.1 单纯使用auto不行吗?
如果使用decltype将变量的类型声明为表达式指定的类型,就可用到达上面想要实现的效果,但是有auto可以自动推导出该变量的类型,为什么还要这么麻烦使用decltype得到类型呢?
cpp
int main()
{
list<int>::iterator it1;
decltype(it1) it2;
cout << typeid(it2).name() << endl;
auto it3 = it1;
cout << typeid(it3).name() << endl;
return 0;
}
虽然使用auto和decltype实现的效果在这种情况是一样的,但是如果场景更加复杂,单纯地使用auto是无法解决问题的,比如类模板实例化配合auto做返回值得到场景。
cpp
template<class T>
class A
{
public:
T* New(int n)
{
return new T[n];
}
};
auto func()
{
list<int> it;
auto ret = it.begin();
return ret;
}
int main()
{
auto ret = func();
A<decltype(ret)> aa;
return 0;
}
这里主要想表达的意思就是auto不是万能的,在某些场景下需要配合decltype解决问题。这里就是需要填充类型,decltype将变量的类型声明为表达式指定的类型就行使用,而auto是自动推导类型给被定义变量使用。
auto和decltype有些地方增加代码读起来有难度,比如auto做返回值就是大坑,想要知道这个函数返回的类型是什么,还有到该函数内部去检查,如果该函数多重嵌套auto做返回值的函数,效率会很低。对于auto和decltype的建议是慎用,写代码的人爽了,让读代码的人哭了
四、nullptr
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
cpp
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
五、范围for循环
在C++11及以后的版本中引入了范围for循环(Range-based for loop),它提供了一种更加简洁和直观的方式来遍历容器中的元素或者其他支持迭代器的数据结构。
cpp
for (declaration : range_expression)
{
// loop body
}
- declaration:用来声明一个新的变量,该变量在每次迭代中将被赋予当前范围内的元素值。这可以是一个新的变量声明,也可以是现有变量的引用。
- range_expression:表示要遍历的范围,可以是一个数组、容器、字符串等支持迭代器的数据结构。
六、STL中一些变化
用橘色圈起来是C++11中的一些几个新容器,但是实际最有用的是unordered_map和unordered_set(哈希)
容器中的一些新方法:
- 如果我们再细细去看会发现基本每个容器中都增加了一些C++11的方法,但是其实很多都是用得比较少的。
- 比如提供了cbegin和cend方法返回const迭代器等等,但是实际意义不大,因为begin和end也是可以返回const迭代器的,这些都是属于锦上添花的操作。
- 实际上C++11更新后,容器中增加的新方法最后用的插入接口函数的右值
以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!