当前位置: 首页 > news >正文

15 【C++11 新特性】统一的列表初始化和统一的声明

文章目录

  • C++11简介
  • 1. 统一的列表初始化
    • 1.1 统一初始化的原理
      • (1)多参数的隐式类型转换
          • 禁用类型转换
      • (2)std::initializer_list
          • 大概了解initializer_list
          • 容器实现接收initializer_list类型参数的构造函数和赋值重载函数
    • 1.2 统一列表初始化的总结
  • 2. 统一的声明
    • 2.1 auto
    • 2.2 decltype
  • 总结


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增加的语法特性非常篇幅非常多,我们这里没办法一 一讲解,所以本文主要讲解实际中比较实用的语法:

  1. 列表初始化
  2. 变量类型推导
  3. 范围for循环
  4. 智能指针
  5. 新增加容器—静态数组array、forward_list以及unordered系列
  6. 右值引用和移动语义
  7. 新增类功能: 禁止生成默认函数的关键字delete、 final与override
  8. 可变模板参数
  9. lambda表达式
  10. 包装器
  11. 线程库

C++参考手册

小故事:
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。


1. 统一的列表初始化

C++11的第一个新特性:一切皆可用{}初始化,并且可以不用加=

class Point
{
public:Point(int x, int y):_x(x), _y(y){}int _x;int _y;
};int main()
{int x = 1;int y = { 2 };int z{ 3 };int* p1 = new int[3]{1, 2, 3};   //有了统一列表初始化之后就可以这样,下面其他初始化可以不加=的原因,或许是为了和这里统一。//int* pa = new int[3] = {1, 2, 3}; // 错误: “=”: 无法从“initializer list”转换为“int *”int p2[]{ 1, 2, 3 };int p3[] = { 1, 2, 3 };Point p4(4, 4);Point p5{ 5,5 };Point p6 = { 6, 6 };vector<int> v1{ 1,2,3,4,5 };vector<int> v2 = { 1,2,3,4,5 };return 0;
}

1.1 统一初始化的原理

(1)多参数的隐式类型转换

我们上面对Point类对象的构造:

Point p4(4, 4);
Point p5{ 5,5 };
Point p6 = { 6, 6 };

这三种Point初始化,本质都是调用了构造函数,也就是这三个是等价的。

我们知道单参数隐式类型转换:string s = "xxxx";

对于对象p6,它们其实本质是一个多参数的隐式类型转换:将{ 6, 6 },隐式转换成了构造函数刚好接收两个参数的类Point,构造出了一个临时对象,然后将临时对象赋值给了p6

由于编译器的优化行为,可能将p6的构造优化的和p5一样直接调用了Point的构造函数。

证明:

int main()
{//Point& p = { 1,2 };  //报错:“初始化”: 无法从“initializer list”转换为“Point &”const Point& p1 = { 1,2 };  //正确return 0;
}

因为临时变量有常性,所以我们无法直接将其赋给一个非常性的引用,而加了const就可以正常引用。
侧面证明了这是多参数的隐式类型转换。

至于报错中的initializer list是什么,暂且不管。

在堆上new对象也是同样原理:

//堆上new对象:
Point* po1 = new Point<int>{1, 2};                //new类对象,隐式类型转换
std::vector<int>* vec3 = new std::vector<int>{1, 2, 3, 4};    //new类对象,调用initializer_list参数构造函数
禁用类型转换

和单参数的隐式类型转换一样,如果我们不想隐式类型转换发生,我们可以在类的构造函数前加上explicit禁用类型装换:

class Point
{
public:explicit Point(int x, int y):_x(x), _y(y){}int _x;int _y;
};int main()
{Point p4(4, 4);Point p5{ 5,5 };//Point p6 = { 6, 6 };  //报错: "Point" 的复制列表初始化不能使用显式构造函数return 0;
}

(2)std::initializer_list

我们来看看上面v2和p6的初始化:

Point p6 = { 6, 6 };
//Point p6 = { 6, 6 ,6}; // 报错。
vector<int> v2 = { 1,2,3,4,5 };

它们都是利用{}初始化,两个有什么不同?他们用的是同一个语法吗?

很明显不是,因为我们清除vector内部的实现规则,{}中的值明显是给了vector中一个数组成员(实际是指针实现)初始化了。而我们Point后面的{}是给了两个成员初始化。
我们Point调的是构造为成员初始化,它的构造只需要两个参数,超过就会报错。但是我们vector可以用很多值,因为最终它是放到一个数组中的。

我们的p6是多参数隐式类型转换,那么v2是什么呢?

它其实也是调用构造函数,是这样实现的:
C++11中新增了一个类型initializer_list,编译器可以将直接写的常量列表{}识别成initializer_list类型。
然后我们的容器如vector,再去实现对应使用initializer_list初始化的构造函数,就实现了以上使用{}给容器初始化的现象。

注意,正常的给数组使用{}初始化并不使用initializer_list转换,而是 C++ 核心语言特性的一部分,换句话说就是编译器的行为:

Point p6 = { 6, 6 };
//栈上的数组:
Point p2[2]{ {1,1}, {2,2} };
//堆上new数组:
Point* pt = new Point[2]{ p6, p6 };    //initializer_list给数组一一初始化

至于为什么可以不加=,这是编译器的行为。

大概了解initializer_list
int main()
{auto il = { 10, 20 ,30 };cout << typeid(il).name() << endl;  //输出: class std::initializer_list<int>cout << sizeof(il) << endl;         //输出: 8return 0;
}

我们可以认为,initializer_list内部是用两个指针实现的,因为32位下地址大小为4字节,所以两个指针成员就是8字节。
在这里插入图片描述
常量数组识别成initializer_list,那么:编译器会想办法取到数组的开始地址和结尾地址给到initializer_list的成员(本质还是调用initializer_list的构造函数),这里是编译器特殊处理的,因为我们常量列表返回的并不是指针。

所以像我们上面出现的错误,就是因为编译器隐式类型转换不成功,从而尝试了转换成initializer_list而产生的报错。
而且由于数组列表被识别成了initializer_list,这样初始化不可以:
const int* p1 = { 1,2,3 }; //“初始化”: 无法从“initializer list”转换为“const int *”

容器实现接收initializer_list类型参数的构造函数和赋值重载函数

我们很多容器构造函数,C++11都支持了用initializer_list做参数的构造函数和赋值重载,就是为了支持我们的列表初始化。
在这里插入图片描述
如果我们给我们自己的类加上可以接收initializer_list参数类型的构造函数和赋值重载,那么我们的自己的类,就也可以使用{}初始化了。

template<class T>
class myVector {
public:typedef T* iterator;myVector(initializer_list<T> l){_start = new T[l.size()];_finish = _start + l.size();_endofstorage = _start + l.size();iterator vit = _start;typename initializer_list<T>::iterator lit = l.begin();while (lit != l.end()){*vit++ = *lit++;}//for (auto e : l)//   *vit++ = e;}myVector<T>& operator=(initializer_list<T> l) {myVector<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;
};

大括号给容器初始化元素的过程:编译器自动将常量列表转换为initializer_list类对象,然后根据去调用对应容器接收initializer_list参数的构造或者赋值重载。


1.2 统一列表初始化的总结

统一列表初始化,可以给类初始化,也可以给容器元素初始化,也可以适用于new表达式中,也可以用于数组的元素初始化(不过不使用initializer_list)。
给类初始化是多参数的隐式类型转换;给容器初始化元素是编译器自动将常量列表转换成了initializer_list,各个容器再去支持使用initializer_list初始化或者赋值;
而用于原生数组的元素初始化,是 C++ 核心语言特性的一部分,并不转换为initializer_list。

有了统一列表初始化,就可以做到这种事:

map<string, string> dict = { {"sort","排序"},{"left","左边"}};

内层括号:是多参数隐式类型装换而成的pair类型。
外层括号:是一个pair列表转换的initializer_list,map有对应接收initializer_list类型参数的构造函数。
在这里插入图片描述


2. 统一的声明

我们介绍的C++11中的第二个特性:统一的声明
C++11提供了多种简化声明的方式,尤其是在使用模板时。

2.1 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", "插入"} };//map<string, string>::iterator it = dict.begin();auto it = dict.begin();return 0;
}

2.2 decltype

关键字decltype将变量的类型声明为表达式指定的类型。

// decltype的一些使用使用场景
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 * y) ret; // ret的类型是doubledecltype(&x) p;      // p的类型是int*cout << typeid(ret).name() << endl;cout << typeid(p).name() << endl;F(1, 'a');return 0;
}

decltype和auto区别就是,我们的auto一定要用一个类型推导并且必须为我们定义的新变量初始化,而我们的decltype,也可以推导一个类型去定义一个变量,并且可以不为我们的新变量初始化。

如有一些特殊场景:

我们需要只是单纯的定义一个变量

class A
{decltype(malloc) ptr;
};

类模板需要的类型

template<class T>
class B
{T _num;
};int main()
{int x = 1, y = 2;B<decltype(x * y)> b;return 0;
}

总结

  1. 统一列表初始化:

可以给类初始化,也可以给容器元素初始化,也可以适用于new表达式中,也可以用于数组的元素初始化(不过不使用initializer_list)。
给类初始化是多参数的隐式类型转换;给容器初始化元素是编译器自动将常量列表转换成了initializer_list,各个容器再去支持使用initializer_list初始化或者赋值;
而用于原生数组的元素初始化,是 C++ 核心语言特性的一部分,并不转换为initializer_list。

  1. 统一的声明变量方式:

auto自动类型推导去定义变量
decltype自动推导表达式的类型去定义变量
都是编译器的行为。


本文章为作者的笔记和心得记录,顺便进行知识分享,有任何错误请评论指点 😃。

http://www.dtcms.com/a/478211.html

相关文章:

  • OpenCV计算机视觉实战(26)——OpenCV与机器学习
  • ACE会成为下一代上下文工程技术吗?
  • GitHub 热榜项目 - 日榜(2025-10-13)
  • 网站首页关键如何优化网络促销的方法有哪些
  • CTFSHOW WEB 2
  • 学术研究者的AI记录工具对比评测
  • mianf网站换友网站
  • 旧电脑变废为宝:Puter+cpolar打造你的专属云盘
  • springboot考试管理系统-计算机毕业设计源码84001
  • 手写MyBatis第106弹:#{}预编译安全机制 vs ${}字符串替换风险 - 源码级深度解析
  • 【Pytorch】数学运算
  • 金泉网站建设开发手机网站开发注意
  • 当Excel遇上大语言模型:ExcelAgentTemplate架构深度剖析与实战指南
  • 新农村建设在哪个网站申请vi设计包含的内容
  • 达梦数据库相关术语及管理操作
  • 百度网站推广公司济南网络优化推广
  • 【SpringBoot从初学者到专家的成长14】SpringBoot项目结构介绍
  • mongodb一个服务器部署多个节点
  • 基金网站制作工程承包公司
  • 成都企业网站建设价格搜索引擎收录
  • 第9章:两条道路的风景:技术与管理的真实世界(4)
  • 基于frenet坐标系的规划与避障
  • 从本地到云端:Fiora+cpolar打造真正的私密社交通讯站
  • Vue Router 导航守卫
  • 技术评测丨RPA主流平台稳定性、安全与信创适配能力对比
  • 简约淘宝网站模板免费下载建立 wiki 网站
  • 【Unity】uNet游戏服务端框架(三)心跳机制
  • 二叉树的深搜
  • C++设计模式之行为型模式:模板方法模式(Template Method)
  • 做3dh春丽网站叫什么重庆十大软件公司