目录
1. C++11简介
🎄从C++0x到C++11,C++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习。C++11增加的语法特性非常篇幅非常多,我们这里没办法一 一讲解,所以主要讲解实际中比较实用的语法。
语言的发展就像是练功打怪升级一样,也是逐步递进,由浅入深的过程。我们先来看下C++的历史版本
C++还在不断的向后发展。但是:现在公司主流使用还是C++98和C++11,所有大家不用追求最新,重点将C++98和C++11掌握好,等工作后,随着对C++理解不断加深,有时间可以去琢磨下更新的特性。
✨小故事:
2. 统一的列表初始化
2.1 {}初始化
- 在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。比如:
struct Point
{
int _x;
int _y;
};
int main()
{
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
Point p = { 1, 2 };
return 0;
}
- C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
struct Point
{
int _x;
int _y;
};
int main()
{
int x1 = 1; //给x1赋值为1
int x2{ 2 }; //给x2赋值为2
int x3 = {3}; //给x3赋值为3
int array[5] = { 1, 2, 3, 4, 5 }; //原版本
int array1[]{ 1, 2, 3, 4, 5 };
int array2[5]{ 0 };
Point p{ 1, 2 };
// C++11中列表初始化也可以适用于new表达式中
int* pa = new int[4]{ 0 };
return 0;
}
- 创建对象时也可以使用列表初始化方式调用构造函数初始化
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2024, 1, 1); // 原版本C++98的构造
// 以下是C++11支持的列表初始化,这里会调用构造函数初始化
Date d2{ 2024, 1, 2 };
Date d3 = { 2024, 1, 3 };
return 0;
}
这三种列表初始化都可以进行支持
2.2 std::initializer_list
🎄Date类型的实例构造就是调用其内部的构造函数
🎄vector容器的实例构造则是使用了initializer_list
//我们经常使用这种方式初始化vector
vector<int> v = {1, 2, 3}
//这里右边其实就是使用了initializer_list
std::initializer_list的介绍文档:
std::initializer_list是什么类型:
int main()
{
// the type of il is an initializer_list
auto il = { 10, 20, 30 };
initializer_list<int> il2 = { 1 ,2 ,3 }; //这两个初始化是一样的
cout << typeid(il).name() << endl;
return 0;
}
int main()
{
// the type of il is an initializer_list
auto il = { 1.1 ,1.2 ,1.3 };
cout << typeid(il).name() << endl;
return 0;
}
🍁可以理解为initializer_list为C++11新增的一个容器,但是这个容器和我们以前讲述的容器不一样,这个容器不实际存储数据
- 但请注意,该模板类不是隐式定义的,即使隐式使用了类型,也应包括头<initializer_list>来访问它。
- initializer_list对象是自动构建的,就好像分配了一个T类型的元素数组一样,使用任何必要的非收缩隐式转换,将列表中的每个元素复制初始化为数组中的相应元素。
- initializer_list对象引用该数组的元素而不包含这些元素:复制initializer_list对象会生成另一个引用相同底层元素的对象,而不是引用它们的新副本(引用语义)。
- 此临时数组的生存期与initializer_list对象的生存期相同。
std::initializer_list的应用场景:
- 初始化容器:std::initializer_list用于初始化各种容器,如std::vector、std::list、std::set等。通过使用花括号{}来创建一个std::initializer_list对象,并将其作为容器的构造函数参数,可以方便地初始化容器中的元素。
具体可以看文档:
-
函数参数:std::initializer_list可以作为函数的参数,用于接收可变数量的参数。这样可以方便地传递一组值给函数,并在函数内部进行处理。例如,可以使用std::initializer_list来实现一个可变参数的打印函数。
-
初始化自定义对象:如果一个类具有接受std::initializer_list作为构造函数参数的构造函数,那么可以使用花括号{}来初始化该类的对象。这样可以方便地为自定义对象提供一组初始值。
3. 声明
c++11提供了多种简化声明的方式,尤其是在使用模板时。
3.1 auto
int main()
{
int i = 10;
auto p = &i;
auto pf = strcpy;
cout << typeid(p).name() << endl;
cout << typeid(pf).name() << endl;
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
//map<string, string>::iterator it = dict.begin();
auto it = dict.begin();
return 0;
}
3.2 decltype
🍁decltype是C++11引入的一个关键字,用于获取表达式的类型。它可以在编译时推导出表达式的类型,并将其作为返回值类型或变量的类型。decltype的语法如下:
🎄其中,expression是一个表达式,可以是变量、函数调用、运算符等。
使用decltype可以方便地获取表达式的类型,特别是在模板编程中非常有用。它可以避免手动指定类型,减少代码冗余和错误。
例如,假设有一个变量x,我们可以使用decltype来获取x的类型:
int x = 10;
decltype(x) y; // y的类型为int
另外,decltype还可以用于推导函数返回值类型,例如:
总结一下,decltype是C++11引入的关键字,用于获取表达式的类型。它可以在编译时推导出表达式的类型,并将其作为返回值类型或变量的类型。
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
decltype(t1 * t2) ret;
cout << typeid(ret).name() << endl;
}
int main()
{
const int x = 1;
double y = 2.2;
decltype(x) z = 1; // ret的类型是int
decltype(x * y) ret; // ret的类型是double
decltype(&x) p; // p的类型是int*
cout << typeid(z).name() << endl;
cout << typeid(ret).name() << endl;
cout << typeid(p).name() << endl;
F(1, 'a');
return 0;
}
注意:
3.3 nullptr
🍁在C++中,nullptr是一个特殊的关键字,用于表示空指针。它的设计意义在于解决了C语言中NULL的一些问题。
-
首先,nullptr的引入消除了C语言中NULL的二义性。在C语言中,NULL被定义为0,但0也可以表示整数的零值。这就导致了在某些情况下,编译器无法确定NULL到底是指针类型还是整数类型。而nullptr则明确表示一个空指针,消除了这种二义性。
-
其次,nullptr可以提高代码的可读性和安全性。使用nullptr可以明确地表示一个空指针,使得代码更加清晰易懂。同时,使用nullptr还可以避免一些潜在的错误,比如将整数值错误地赋给指针变量。
-
最后,nullptr还可以与函数重载一起使用,用于区分不同的函数重载。在C++中,函数重载是指在同一个作用域内定义多个同名函数,但它们的参数类型或参数个数不同。当我们调用这些重载函数时,如果传入一个空指针作为参数,编译器可以根据nullptr的类型来确定调用哪个重载函数。
由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
4.STL中一些变化
容器中的一些新方法
- 如果我们再细细去看会发现基本每个容器中都增加了一些C++11的方法,但是其实很多都是用得
比较少的。 - 比如提供了cbegin和cend方法返回const迭代器等等,但是实际意义不大,因为begin和end也是
可以返回const迭代器的,这些都是属于锦上添花的操作。 - 实际上C++11更新后,容器中增加的新方法最后用的插入接口函数的右值引用版本:
但是这些接口到底意义在哪?网上都说他们能提高效率,他们是如何提高效率的?
请看下面的右值引用和移动语义章节的讲解。另外emplace还涉及模板的可变参数,也需要再继
续深入学习后面章节的知识。
总结:
STL的一些变化:
- 新容器
- 新接口