文章目录
- 1.C++11简介
- 2.统一的列表初始化
- 2.1{}初始化
- 2.2 std::initializer_list
- 3.声明
- 3.1 auto
- 3.2 decltype
- 3.3 nullptr
- 4.范围for
- 5.STL中一些变化
- 5.1 array
- 5.2 forward_list容器
- 5.3 unordered_map和unordered_set容器
1.C++11简介
相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以我们要作为一个重点去学习。C++11增加的语法特性非常篇幅非常多,我们这里没办法一 一讲解,所以本篇文章主要讲解实际中比较实用的语法。
想要深入了解C++11的可以到C++11官网
2.统一的列表初始化
2.1{}初始化
- 在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
- C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
- 创建对象时也可以使用列表初始化方式调用构造函数初始化。
有如下使用方法:
#include<iostream>
#include<vector>
#include<list>
#include<map>
using namespace std;
struct Point
{
int _x;
int _y;
};
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{ 3 };
int arr1[] = { 1,2 };
int array1[]{ 1, 2, 3, 4, 5 };
int arr2[5] = { 0 };
int array2[5]{ 0 };
Point p1 = { 1, 2 };
Point P2{ 1,2 };
// C++11中列表初始化也可以适用于new表达式中
int* pa = new int[4]{ 0 };
int* pb = new int[10]{ 1, 2, 3 };
Point* p3 = new Point[2]{ { 1,1 },{ 2,2 } };
//一般调用构造函数创建对象的方式
Date d1(2022, 8, 29);
//创建对象时也可以使用列表初始化方式调用构造函数初始化
Date d2 = { 2022, 8, 30 }; //可添加等号
Date d3{ 2022, 8, 31 }; //可不添加等号
return 0;
}
2.2 std::initializer_list
C++11中新增了initializer_list容器,并且该容器没有提供过多的成员函数。std::initializer_list的官方详细文档:
https://cplusplus.com/reference/initializer_list/initializer_list/
那么std::initializer_list是什么类型呢?从下图的运行结果可以看出可以看出编译器会将大括号识别为std::itializer_list类型。
int main()
{
auto il = { 10, 20, 30 };
cout << typeid(il).name() << endl;//typeid是用于获取变量类型的函数
return 0;
}
initializer_list是C++11提供的新类型,定义在头文件中。
用于表示某种特定类型的值的数组,和vector一样,initializer_list也是一种模板类型。能够处理不同数量实参(但是类型相同),与vector不同的是initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。在进行函数调用的时候需要使用花括号将所有的参数括起来。
std::initializer_list使用场景:
std::initializer_list一般是作为构造函数的参数,没有提供对应的增删查改等接口,因为initializer_list并不是专门用于存储数据的,而是为了让其他容器支持列表初始化的。C++11对STL中的不少容器就增加std::initializer_list作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=的参数,这样就可以用大括号赋值。
#include<iostream>
#include<vector>
#include<list>
#include<map>
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()
{
initializer_list<int> i3 = { 10,20,30 };
initializer_list<int>::iterator it3 = i3.begin();
cout << it3 << endl;
vector<int> v1 = { 1,2,3,4,5 };
vector<int> v2 = { 10,20,30 };
list<int> lt1 = { 1,2,3,4,5 };
list<int> lt2 = { 10,20,30 };
//auto自动识别initializer_list类型
auto i1 = { 10,20,30,1,1,2,2,2,2,2,2,1,1,1,1,1,1,1,1,2,1,1,2 };
auto i2 = { 10,20,30 };
cout << typeid(i1).name() << endl;
cout << typeid(i2).name() << endl;
initializer_list<int>::iterator it1 = i1.begin();
initializer_list<int>::iterator it2 = i2.begin();
cout << it1 << endl;
cout << it2 << endl;
map<string, string> dict1 = { {"sort", "排序"},{"string", "字符串"},{"Date", "日期"} };
map<string, string> dict2{ make_pair("sort", "排序"), { "insert", "插入" } };//也可以省略赋值符号
pair<string, string> kv1 = { "Date", "日期" };
Date d1(2023, 5, 20);
Date d2(2023,5,21);
// initializer_list<Date>
vector<Date> vd1 = {d1, d2};
vector<Date> vd2 = { Date(2023,5,20), Date(2023,5,21) };
vector<Date> vd3 = { {2023,5,20}, {2023,5,20} };
return 0;
}
让模拟实现的vector也支持{}初始化和赋值
namespace bit
{
template<class T>
class vector {
public:
typedef T* iterator;
vector(initializer_list<T> l)
{
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
iterator vit = _start;
//必须加typename,声明iterator是个类型,不然编译器不知道它是类型还是
//静态变量
typename initializer_list<T>::iterator lit = l.begin();
//将已经装载数据的initializer_list<T> l
//中的每个元素通过迭代器搞到vector中
while (lit != l.end())
{
*vit++ = *lit++;
}
//for (auto e : l)
// *vit++ = e;
}
//赋值运算函数的现代写法
vector<T>& operator=(initializer_list<T> l) {
vector<T> tmp(l);
std::swap(_start, tmp._start);
std::swap(_finish, tmp._finish);
std::swap(_endofstorage, tmp._endofstorage);
return *this;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
3.声明
c++11提供了多种简化声明的方式,尤其是在使用模板时。
3.1 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;
}
3.2 decltype
decltype,在C++中,作为操作符,用于查询表达式的数据类型。decltype在C++11标准制定时引入,主要是为泛型编程而设计,以解决泛型编程中,由于有些类型由模板参数决定,而难以(甚至不可能)表示之的问题。
decltype的使用场景
#include<iostream>
#include<vector>
using namespace std;
template<class T1,class T2>
void func(T1 t1, T2 t2)
{
decltype(t1*t2) ret; //也可以推导模板参数的类型
cout << typeid(ret).name() << endl;
}
void* GetMemory(size_t size)
{
return malloc(size);
}
int main()
{
const int x = 1;
double y = 2.2;
//decltype可以推演表达式的类型
decltype(x*y) ret; //ret的类型是double
ret = 2.2;
decltype(&x) p; //p的类型是int*
cout << typeid(ret).name() << endl;
cout << typeid(p).name() << endl;
func(1, 'a');
//vector存储的类型跟x*y表达式返回值类型一致
//decltype推导表达式类型,用这个类型实例化模板参数或者定义对象
vector<decltype(x*y)> v;
//decltype还可以推演函数返回值的类型
//1.如果没有带参数,则推导函数类型
cout << typeid(decltype(GetMemory)).name() << endl;
//2.如果没有带参数,则推导函数类型
cout << typeid(decltype(GetMemory(0))).name() << endl;
return 0;
}
运行结果:
- 虽然通过typeid(变量名).name()的方式可以获取一个变量类型,但无法用这个函数获取到变量的类型取去定义变量。
- auto可以自动推导类型,但是auto的使用前提是必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。
- decltype不仅可以将变量的类型声明为表达式指定的类型,还可以推导函数类型或函数返回值的类型。
3.3 nullptr
由于C++中NULL被定义成字面量0,这样可能会带来一些问题,因为0既能表示整型常量,又能表示指针常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。
#ifndef NULL
#ifndef __cpluscplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
#endif
有关空指针,野指针的博客:空指针 野指针
4.范围for
传统for语句
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v;
for (int i = 0; i < 10; i++)
{
v.push_back(i);
}
//遍历打印
for (auto it = v.begin(); it != v.end(); it++)
{
cout << *it << " ";
}
cout << endl;
//或
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//将数组元素值全部乘以2
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
arr[i] *= 2;
}
//打印数组中的所有元素
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
范围for的使用方式
int main()
{
string strs[] = { "苹果", "香蕉", "草莓", "橘子" };
for (const auto& e : strs)
{
cout << e << " ";
}
cout << endl;
system("pause");
return 0;
}
auto :自动推演出范围变量的数据类型。
关于引用&:范围for在遍历的时候,把数组的每一个元素的值拷贝给e,拷贝需要开辟空间,一定程度上降低了效率,加引用后可以节省空间,提高效率;而且若想要修改数组的内容,不加引用就无法修改,因为拷贝的值只是数组的一个副本,而加了引用之后,就是变量的别名,也就是变量本身,就可以进行修改操作。
- 可以使用continue或break;
- 范围for的底层实际上是一个迭代器,编译器会自动去调用迭代器从而实现范围for。所以使用范围for,那么这个对象必须要支持++和减减操作;
- 范围for循环迭代的范围必须是确定的。对应普通数组,就是第一个元素到最后一个元素;对于STL的容器就是begin()到end()。
5.STL中一些变化
新增容器
C++11中新增了四个容器,分别是array、forward_list、unordered_map和unordered_set。
5.1 array
数组array是一个大小固定的序列容器,本质是一个静态数组,容器中保存着特定数量的元素,元素按照严格的线性序列保存。它是一个封装了固定数量元素的数组的聚合类型。因此,它不能动态的添加或删除元素(类似的大小可扩展的容器参见vector)。
int main()
{
array<int, 10> a1; //定义一个可存储10个int类型元素的array容器
array<double, 5> a2; //定义一个可存储5个double类型元素的array容器
return 0;
}
- array容器支持通过
[]
访问指定下标的元素,同样也支持使用范围for遍历数组元素,并且创建后数组的大小不可改变。 - array与普通数组的不同之处主要在于:array会严格的进行越界检查,用下标访问操作符[]访问元素采用断言检查,调用at成员函数采用抛异常检查。
5.2 forward_list容器
forward_list容器本质是一个单链表,只支持头插头删,不支持尾插尾删。因为单链表在进行尾插尾删需要先找尾,时间复杂度为O(N),尾插尾删的效率不高。
比起list而言,forward_list每个结点可以节省一个指针的空间,头删头插效率不错,但是日常中我们一般不缺内存,还是使用list容器更香。
5.3 unordered_map和unordered_set容器
unordered_map和unordered_set容器底层采用的都是哈希表。这也是C++11中新增的最有价值的容器。关于这两个容器,我之前的博客由详细介绍,可以点击以下链接进行跳转学习:
【C++】unordered_map与unordered_set(系列关联式容器)
底层哈希:【C++】哈希/散列详细解析
容器中的一些新方法
如果我们再细细去看会发现基本每个容器中都增加了一些C++11的方法。
比如提供了cbegin和cend方法返回const迭代器,将内置类型转换成string类型统一调用的to_string函数,将string类型转换成内置类型的函数,一个以initializer_list作为参数的构造函数,用于支持列表初始化。提供了emplace系列方法,并在容器原有插入方法的基础上重载了一个右值引用版本的插入函数,用于提高向容器中插入元素的效率。
有关C++11新增的:1.右值引用和移动语义;2.lambda表达式;3.包装器;4.线程库5.可变参数模板等重要知识博主会将后序继续更新详细解析的文章。