C++11的历史和统一的初始化列表
c++11的历史背景
c++历史
C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(object oriented programming:面向对象)思想,支持面向对象的程序设计语言应运而生。
1982年,Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此:C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。
c++标准发展
1979年,贝尔实验室的本贾尼等人试图分析unix内核的时候,试图将内核模块化,于是在C语言的基础上进行扩展,增加了类的机制,完成了一个可以运行的预处理程序,称之为
C with classes。语言的发展就像是练功打怪升级一样,也是逐步递进,由浅入深的过程。
阶段 | 内容 |
---|---|
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表达式,autot的返回值类型推导,二进制字面常量等 |
C++17 | 在C++11上做了一些小幅改进,增加了19个新特性,比如:static_assert()的文本信息可选,Fold表达式用于可变的模板,if和switch语句中的初始化器等 |
C++20 | 自C++11以来最大的发行版,引入了许多新的特性,比如:模块(Modules)、协程(Coroutines)、范围(Ranges)、概念(Constraints)等重大特性,还有对已有特性的更新;比如Lambda支持模板、范围for支持初始化等 |
C++23 | 允许在编译时检测当前是否在常量求值上下文中执行,operator[] 支持多参数,简化条件编译的语法(#elifdef 和 #elifndef),允许在 constexpr 函数中使用 union 和 try-catch(但 catch 必须在编译时无实际副作用),增强标准库例如支持print(没错,就是Python的print)、支持网络库等 |
即使是这样,现在很多公司主流使用还是C++98和C++11(标准更新需要编译器支持,但很多公司一旦支持新标准可能导致旧代码出现问题,重构代码需要经济、时间),所以不用刻意追求最新,重点将C++98和C++11掌握好,随着对C++理解不断加深,有时间可以去琢磨下更新的特性。
即使没有官方库,也有很多民间自研的第3方库,但官方做的东西口碑至少还有,所以质量能有保证。
c++11的历史
在2003年c++标准委员会曾经提交了一份技术勘误表(简称TC1),使得c++03这个名字已经取代了c++98称为c++11之前的最新c++标准名称。
不过由于c++03(TC1)主要是对c++98标准中的漏洞进行修复,语言的核心部分则没有改动,因此人们习惯性的把两个标准合并称为c++98/03标准。
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++0x到c++11,c++标准10年磨一剑,第二个真正意义上的标准珊珊来迟。相比于c++98/03,c++11则带来了数量可观的变化,其中包含了约140个新特性,以及对c++03标准中约600个缺陷的修正,这使得c++11更像是从c++98/03中孕育出的一种新语言。
相比较而言,c++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率,公司实际项目开发中也用得比较多,所以c++11要作为一个重点去学习。
c++11的英文文档,点击即可学习,后续内容大多参考这个链接。
c++11相对于c++98,多的东西:
新容器。即
forward_list
、array
、unordered_map
和unordered_set
。array
虽然是静态数组,但很多功能vector
都有,而且vector
的功能还比array
多,forward_list
作为单链表在很多场景也可以用list
代替,也就unordered_map
和unordered_set
好用,能在部分情况代替map
和set
。新增返回带
const
的迭代器cbegin
和cend
。但是实际意义不大,属于锦上添花的操作。因为begin
和end
也可以返回const
迭代器。新的构造函数(
initializer_list
),使容器和内置类型数组一样可通过{}
初始化成为可能。新增右值引用和基于右值引用的移动构造、移动赋值和右值引用版本插入。例如右值引用版本插入比如
vector::emplace_back
、vector::push_back
、map::insert
和map::emplace
都是新引入的插入接口函数的右值版本。改进可变参数。
改进和新增部分关键字。例如
auto
、decltype
、default
、delete
、final
、override
。新增委托构造函数。
新增lambda函数和包装器。
当然不止这些,还有智能指针和线程库等。
这里就简单介绍c++11新增的initializer_list
类。
统一的初始化列表
{}初始化
在c语言,标准允许使用花括号{}
对内置类型数组或者结构体元素进行统一的列表初始值设定。
#include<stdio.h>
struct Point {
int _x;
int _y;
};
int main() {
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
struct Point p = { 1, 2 };
return 0;
}
在c++中,struct
延续了这种设定,将这种通过{}
进行初始化的行为叫列表初始化。class
只要成员的访问权限是public
也可以这样使用。
但c++的class
和struct
都被改造成了类,若类内部的成员访问是私有,或类中有别的构造函数,则不能通过{}
进行初始化。
#include<stdio.h>
struct Point {
int _x;
int _y;
};
struct Point2{
int _x;
int _y;
};
struct Point3{
Point3(int x,int y)
:_x(x),_y(y){ }
int _x;
int _y;
};
class Point4{
public:
int _x;
int _y;
};
struct Point5{
private:
int _x;
int _y;
};
class Point6{
int _x;
int _y;
};
int main() {
struct Point p = { 1, 2 };//一般的结构体
Point2 p2 = { 3, 2 };//没有构造函数的类
//Point3 p3 = { 3, 2 };//有构造函数的类不能在c++98这么玩
Point4 p4 = { 3, 2 };//class定义的类只要权限许可就行
//Point5 p5 = { 3, 2 };//私有成员不可访问
//Point6 p6={1,2};//私有成员不可访问
return 0;
}
c++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号=
,也可不添加。但成员中有私有的就不能使用初始化列表。
int main() {
int x1 = 1;
int x2{ 2 };
int array1[]{ 1, 2, 3, 4, 5 };
int array2[5]{ 0 };
struct Point p = { 1, 2 };//见上个代码
Point2 p2 = { 3, 2 };
Point3 p3 = { 3, 2 };//c++11允许
Point4 p4 = { 3, 2 };
//Point5 p5 = { 3, 2 };//私有成员不可访问
//Point6 p6={1,2};//私有成员不可访问
// c++11中列表初始化也可以适用于new表达式中
int* pa = new int[4]{ 0, 1 };
return 0;
}
且{}
内的参数相对于类的成员数,可以少,但不能多。
#include<iostream>
using namespace std;
class Array{
public:
int a;
int b;
int c;
};
int main() {
//Array a={1,2,3,4};//参数多了不允许
Array a={1,2};
cout<<a.a<<' '<<a.b<<' '<<a.c<<endl;
return 0;
}
initializer_list
c++支持类的对象使用初始化列表,但和c语言的结构体一样,仅用于给每个成员赋初值。
#include<iostream>
using namespace std;
class Array{
public:
int arr[10];
};
int main() {
Array a={1,2,3};
for(int i=0;i<10;i++)
cout<<a.arr[i]<<' ';
return 0;
}
c++11新增了initializer_list
类模板。
initializer_list - C++ Reference
#include<iostream>
using std::cout;
using std::endl;
int main() {
auto li = { 1,2,3 };
cout << typeid(li).name() << endl;
//输出class std::initializer_list<int>
return 0;
}
它可以让容器和普通的内置类型数组一样支持初始化列表。和构造+拷贝构造的{}
初始化不同,initializer_list
不限制参数个数。
initializer_list
是新增的类,它能将用户提供的{数据,数据,...}
作为initializer_list
的数据,一般是作为构造函数的参数,C++11对STL中的不少容器就增加了initializer_list
作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator=
的参数,这样就可以用大括号赋值。
通过vector
的构造函数来演示初始化列表的实质:
#include<iostream>
#include<initializer_list>
using std::cout;
using std::endl;
using std::initializer_list;//可以展开namespace std但没必要
namespace mystd
{
template<class T>
class vector {
public:
typedef T* iterator;
vector(initializer_list<T> l) {
_start = new T[l.size()];//申请属于vector的新空间
_finish = _start + l.size();
_endofstorage = _start + l.size();
iterator vit = _start;
typename initializer_list<T>::iterator
lit = l.begin();//这里推荐用auto减少长度
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;
}
T& operator[](size_t pos) {
return _start[pos];
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
}
int main() {
mystd::vector<int>arr = { 1,2,3,4,5 };
for (int i = 0; i < 5; i++)
cout << arr[i]<<' ';
return 0;
}
可以这样理解:编译器识别出{}
时会先找到一个常量存储区(即只读数据段 .rodata ,有的编译器是静态存储区),将{}
内的数据存下来,然后将这些内容调用initializer_list
的拷贝构造,隐式转换成initializer_list
。在vector
这个容器内部的形参为
initializer_list
的构造函数,通过遍历这个initializer_list
来初始化容器。
不只是vector
,其他容器也都添加了initializer_list
作为自身的构造函数。
例如map
:
#include<iostream>
#include<map>
using std::cout;
using std::endl;
using std::map;
int main() {
//pair也支持用{x,y}初始化
map<int, int>mp = { {1,2},{2,3},{3,5} };
for (auto& x: mp)
cout << x.first << ' ' << x.second << endl;
return 0;
}
关于成员的初始化,实际就是给成员赋予缺省值,当程序调用构造函数时,初始化列表会通过缺省值来初始化对象。详细见类的默认成员函数——构造函数-CSDN博客。