文章目录
- 一、统一的列表初始化
- 1.用{}来初始化元素
- 2.initializer_list
- 二、自动类型推断
- 3.auto
- 4.decltype
- 三、指针
- 5.nullptr
- 6.范围for
- 四、STL中的一些新变化
- 1.新增加的容器
- 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中,我们都可以使用{}来对我们的元素进行初始化
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <array>
using namespace std;
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()
{
int x1 = 1;
// 要能看懂,但是不建议使用
int x2 = { 2 };
int x3 { 2 };
// 都是在调用构造函数
Date d1(2022, 11, 22);
// C++11 要能看懂,但是不建议使用
Date d2 = {2022, 11, 11}; // ->调用构造函数
Date d3{ 2022, 11, 11 };
return 0;
}
2.initializer_list
C++11中有了一种新的类型,initializer_list,语法上原生支持通过大括号的方式初始化给它。它就像一个顺序表一样,支持迭代器,但是不支持插入数据。
int main()
{
// 调用支持list (initializer_list<value_type> il)类似这样的构造函数
vector<int> v1 = { 1, 2, 3, 4, 5, 6 };
vector<int> v2 { 1, 2, 3, 4, 5, 6 };
list<int> lt1 = { 1, 2, 3, 4, 5, 6 };
list<int> lt2{ 1, 2, 3, 4, 5, 6 };
//c++11这里新增了一个类型initializer_list类型来实现,是这里默认需要的一个容器
auto x = { 1, 2, 3, 4, 5, 6 };
cout << typeid(x).name() << endl;
return 0;
}
那我们的vector等容器又是如何支持大括号的初始化的呢?
因为我们的C++11同时也对我们的库函数进行了更新,让我们的库函数都支持通过initializer_list来进行构造。
这里我们查看一下vector的构造函数中多了一个构造方法,也就是我们的initializer
那如何让我们自己之前写过的vector支持通过initializer来初始化
vector(initializer_list<T> il)
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
reserve(il.size());
iterator it=begin();
for(auto&e:il)
{
push_back(e);
}
}
测试代码
void myvector_test18()
{
vector<int> v1={1,2,3,4,5,6,7,8,9,10};
vector<int> v2{1,2,3,4,5,6,7,8,9,10};
for(auto e:v2)
{
cout<<e<<" ";
}
cout<<endl;
}
当然,我们的map,set,pair等容器也可以通过这种方式进行构造
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <set>
#include <array>
using namespace std;
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(2022, 11, 22);
Date d2 = {2022, 11, 11};
Date d3{ 2022, 11, 11 };
vector<Date> v3 = {d1, d2, d3};
vector<Date> v4 = { { 2022, 1, 1 }, {2022, 11, 11} };
string s1 = "11111";
// 构造
//这相当于就是隐式类型转换
//构造一个pair我们也可以通过{}的方式构造
map<string, string> dict = { { "sort", "排序" }, { "insert", "插入" } };
return 0;
}
当然在我们的C++11的库中,还将许多赋值也进行了重载,让其能够支持initializer进行赋值
下面是我们的测试代码
#include <iostream>
#include <map>
using namespace std;
int main()
{
// 构造
//这相当于就是隐式类型转换
//构造一个pair我们也可以通过{}的方式构造
map<string, string> dict = { { "sort", "排序" }, { "insert", "插入" } };
// 赋值重载
//这里不写auto让编译器自动去推断,我们的编译器是没有办法匹配出来的。
initializer_list<pair<const string, string>> kvil = { { "left", "左边" }, { "left", "左边" } };
dict = kvil;
return 0;
}
总结:
C++11之后,一切对象都可以用列表初始化。但是我们建议普通对象还是用以前的方式初始化,容器可以采用花括号进行初始化。
二、自动类型推断
3.auto
在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃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", "插入"} };
//如果我们这里要自己写的话,需要写一长串,但是使用auto的话就非常方便
//map<string, string>::iterator it = dict.begin();
auto it = dict.begin();
return 0;
}
如果我们这里使用auto的话,我们上面代码中的迭代器的类型等等,我们都不用自己手写,就非常方便,但是我们如果使用了auto进行自动类型推断,我们代码的可读性就会变差,但是有些编译器会给你标识出来(比方说clion)。
4.decltype
typename可以推导对象的类型,但是我们不能通过这个推导出来的类型来定义我们的对象,只是单纯地拿到这个类型的字符串。
但是如果我们想要用推导出来的类型重新定义一个新的对象呢?
int main()
{
int x = 10;
// typeid拿到只是类型的字符串,不能用这个再去定义对象什么的
// 下面这样写会报错的,没有下面这样的用法
// typeid(x).name() y = 20;
decltype(x) y1 = 20.22;
//auto和decltype是不一样的,我们的auto这里推导出来的是double,也就是我们右边的赋值的元素的类型是什么,我们auto推导出来也就是什么
//但是我们的deltype推导出来的类型是x的类型,也就是int
auto y2 = 20.22;
cout << y1 << endl;
cout << y2 << endl;
return 0;
}
三、指针
5.nullptr
由于C++中NULL被定义成字面量0,这样就可能会带来一些问题,因为0既能指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
6.范围for
这里的范围for和我们java中的增强for有些相似,底层是一个迭代器。
可以查看我们之前的博客中的迭代器和范围for相关的部分
int main()
{
vector<int> tmp{1,2,3,4,5,6,7,8,9};
for(auto i:tmp)
{
cout<<i<<" ";
}
cout<<endl;
}
四、STL中的一些新变化
1.新增加的容器
< array >和< forward_list >显得有些鸡肋
因为< array >是固定大小的数组容器,不支持尾插和尾删,支持[]迭代器。
C++11增加这个的初衷是为了替代c语言中的数组
int main()
{
const size_t N = 100;
int a1[N];
// C语言数组越界检查,越界读基本检查不出来,越界写是抽查
a1[N];
//a1[N] = 1;
a1[N+5] = 1;
// 越界读写都可以被检查出来
// 实际情况:array用得很少,一方面大家用c数组用惯了
// 用array不如用vector + resize去替代c数组
array<int, N> a2;
a2[N];
a2[N] = 1;
a2[N + 5] = 1;
return 0;
}
< forward_list >是一个单链表,我们的< list >是双向链表,在使用的时候其实< forward_list >插入的是在我们当前指定位置的后一个位置插入,然后erase并不是擦除当前的位置,而是擦除当前位置的下一个位置。
2.容器内部的变化
1.都支持了initializer_list构造,用来支持列表初始化
2.比较鸡肋的接口,比如cbegin,cend系列
3.移动构造和移动赋值,用来对标拷贝构造和拷贝赋值,在某些场景下可以提高效率
(set&&x);
(set& operator=(set&&x)
4.右值引用的参数的插入