文章目录
- 一、前言
- 二、C++11诞生简介
- 三、列表初始化
- (一){}初始化
- (二)initializer_list容器
- 1. initializer_list 概念
- 2. initializer_list的使用场景
- 3. initializer_list接口函数模拟实现
- 四、关键字
- (一)auto
- (二)decltype
- (三)nullptr
- 五、范围for
- 六、STL的更新
- (一)新容器
- 1. array容器
- 2.forward_list容器
- 3.unordered_map和unordered_set容器
- (二)新方法
- (三)新函数
- 1.to_string
一、前言
经过了漫长了C++语法学习还有高阶数据结构学习
差不多来到了C++学习的收尾部分C++11
这部分主要是一些零碎的知识点
中间还可能涉及到一些linux相关知识
二、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的小故事。
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。
三、列表初始化
(一){}初始化
我们在前面的C语言中我们一般习惯使用这样子的代码来初始化数组。
int arr[] = { 1,2,3,4,5 };
int arr2[4] = { 0 };
在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
int arr[] = { 1,2,3,4,5 };
point p{ 1,2};
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
// 对于内置类型进行初始化
int a = { 3 };
int a{ 3 };
// 对于数组进行初始化
int arr[5] = { 1,2,3,4,5 };
int arr2[5]{ 1,2,3,4,5 };
// 对于结构体元素进行初始化
point p = { 1,3 };
point p{ 1,3 };
// new表达式
int* p1 = new int[4] {0};
// int* p1 = new int[4] = {0};
// new表达式初始化的时候不可以加等号
这里特别要注意的一点是,使用new表达式初始化不能使用等于号了。
创建自定义对象的时候也可以使用列表初始化。
class Date
{
public:
Date(int year,int month , int day)
:_year(year)
,_month(month)
,_day(day)
{
;
}
private:
int _year;
int _month;
int _day;
};
void test4()
{
Date d1 = { 2023,1,23 };
Date d2{ 2023,1,23 };
}
(二)initializer_list容器
1. initializer_list 概念
C++11中新增了initializer_list容器。
C++11中能够使用{}初始化就是因为这个容器的功劳。
- begin和end迭代器 用于支持迭代器遍历
- size函数支持获取容器中的元素个数
它的本质就是一个用大括号括起来的列表
我们可以使用这样子的代码来确认它的类型
auto l = { 1,2,3,4,5 };
cout << typeid(l).name() << endl;
2. initializer_list的使用场景
initializer_list容器并没有提供增删查改相关接口函数,这是因为这个容器提供出来了并不是让你储存数据,而是为了能够让其他容器进行列表初始化的。
vector<int> v = { 1,2,3,4,5 };
set<int> s = { 1,2,3,4,5 };
map<int, int> m = { make_pair(1,2),make_pair(3,4) };
这些容器之所以能够支持这样子的列表初始化,实际上是因为容器的成员函数当中增加了一个以initializer_list为参数的构造函数。
那么底层原理实际上就是,先通过initializer_list构造出来一个列表,然后将这个列表中的所有元素拷贝到我们要构造的容器当中。
3. initializer_list接口函数模拟实现
这里总结下:
- 构造函数中遍历initializer_list时,可以使用范围for遍历,也是使用迭代器遍历,二者本质上没有差别,因为范围for遍历的底层就是使用迭代器实现的。
- 假如我们使用迭代器遍历,由于使用了模板参数,所以我们在声明类型的时候要加上typename 告知编译器这是一个类型。
- 如果不增加一个以initializer_list为参数的赋值运算符重载函数,括号初始化也可以运行。
四、关键字
(一)auto
在C++11中 auto关键字的作用是自动推导类型。
map<int, string> m;
map<int, string>::iterator it = m.begin();
auto it = m.begin();
我们可以发现,使用auto关键字之后代码变得简洁了很多。
在某些情况下,使用auto自动推导类型是十分有用的,比如说下面的情况。
short a = 32670;
short b = 32670;
short c = a + b;
cout << c << endl;
但是如果我们使用auto推导的话情况就会不一样了。
short a = 32670;
short b = 32670;
auto c = a + b;
cout << c << endl;
(二)decltype
关键字decltype可以将变量类型声明为表达式指定的类型。
const int x = 1;
double y = 2.2;
decltype(x * y) ret;
decltype (&x) p;
cout << typeid(ret).name() << endl;
cout << typeid(p).name() << endl;
F(1, 'a');
F(1, 2.2);
decltype除了能够推演表达式的类型,它还能够推演返回值的类型
void* GetMemory(size_t size)
{
return malloc(size);
}
void test11()
{
cout << typeid(decltype(GetMemory)).name() << endl;
cout << typeid(decltype(GetMemory(0))).name() << endl;
}
- 如果我们不带参数,则推导的是函数的类型
- 如果我们带参数,则推导的是返回值的类型
(三)nullptr
在C语言中NULL 即空指针,被定义成了0,但是这在一些情况下可能会带来一些问题。
比如说下面的代码。
void f(int arg)
{
cout << "void f(int arg)" << endl;
}
void f(int* arg)
{
cout << "void f(int* arg)" << endl;
}
int main()
{
f(NULL);
return 0;
}
当我们像函数参数中传递一个空指针的时候实际上我们是想要调用第二个函数,可是结果缺不符合我们的预期。
于是C++11的标准中引入了nullptr来代表空指针,使得我们写的代码更加的严谨!
void f(int arg)
{
cout << "void f(int arg)" << endl;
}
void f(int* arg)
{
cout << "void f(int* arg)" << endl;
}
int main()
{
f(nullptr);
return 0;
}
五、范围for
这个语法我们从刚刚学习C++就开始使用了,语法格式如下。
vector<int> v;
v.push_back(2);
v.push_back(5);
v.push_back(4);
v.push_back(7);
v.push_back(1);
for (auto x : v)
{
cout << x << endl;
}
主体是这四行代码。
for (auto x : v)
{
cout << x << endl;
}
- auto关键字的目的是为了自动推导类型,当然如果你知晓类型是什么格式也可以直接声明之来替换auto
- x为变量名,它代表的是容器中的每个数据,你也可以修改它的名字使之更好理解
- 变量名和容器名之间要用分号隔开
- 最后的就是容器名字,输入你想要遍历的容器即可
范围for的底层原理并不复杂,其实就是迭代器遍历,只要一个容器可以被迭代器遍历,那么它就可以被范围for遍历!
和普通for循环类似 范围for也可以被continue和break关键字打断!
范围for的两个使用条件
- 它的范围要是确定的 在begin和end迭代器之间
- 它的迭代器要支持++和==操作,如果它的迭代器不支持此操作,那么范围for也是不支持的。
六、STL的更新
(一)新容器
1. array容器
它的本质是一个静态数组
我们创建一个array对象需要提供两个参数
一个是数据的类型 ,一个是大小,具体示例代码如下
array<int,10> a1;
array<double, 12> a2;
array和普通数组的对比:
相同点:
- 它和普通数组一样支持通过下标访问操作符【】来访问数据
- 支持范围for遍历
- 创建后的数组大小不可改变。
不同点:
- 它使用一个类对于array容器进行了封装,对于数组边界的检查更加严格了,如果使用下标访问操作符越界会出现断言检查,如果使用 at 成员函数访问会抛出异常
- 普通数组对于越界的检查只有在写操作的时候才会很严格,如果只是进行读操作并不会那么严格。
因为它的对象是在栈上创建的,而栈的空间一般都很小,所以说我们不宜使用array创建过大的对象。
2.forward_list容器
forward_list容器的本质上是一个单链表!
相比于双链表来说单链表的缺点太多了,所以说我们平时不常使用forward_list容器来存储数据。
这里简单介绍下forward_list容器的几个特点:
- forward_list容器只支持头插头删 因为单链表的尾插尾删,都要先找到尾,时间复杂度是O(N) 而头插头删的时间复杂度则是O(1)。
- forward_list容器只支持头插头删,因为单链表的尾插尾删,都要先找到尾,时间复杂度是O(N) 而头插头删的时间复杂度则是O(1)。
- forward_list容器只支持头插头删 因为单链表的尾插尾删,都要先找到尾,时间复杂度是O(N) 而头插头删的时间复杂度则是O(1)。
3.unordered_map和unordered_set容器
关于这两个容器,它们的底层是用哈希表实现的,具体的使用可以参考我之前的博客!
(二)新方法
C++容器中提供了一些新方法
- 提供了一个以initializer_list作为参数的构造函数,用于支持列表初始化。
- 提供了cbegin和cend方法,用于返回const迭代器。
- 提供了emplace系列方法,并在容器原有插入方法的基础上重载了一个右值引用版本的插入函数,用于提高向容器中插入元素的效率。
(三)新函数
1.to_string
在C++11中提供了一个函数可以将别的类型转化为字符串类型!
to_string()
它的底层实现原理是函数重载!
string类型转化为其他类型!
如果要将string类型转化为其他类型 这里我们只需要调用对应的函数即可
最后的数字代表转化后的类型:
i 代表 int
l 代表 long
ll 代表 long long 依次类推