关于C++11的统一初始化语法示例详解

 更新时间:2020年4月25日 17:29  点击:1672

前言

本文主要给大家介绍了C++11统一初始化语法的相关内容,关于在当前新标准C++11的语法看来,变量合法的初始化器有如下形式:

X a1 {v};
X a2 = {v};
X a3 = v;
X a4(v);

其实,上面第一种和第二种初始化方式在本质上没有任何差别,添加=则是一种习惯上的行为。使用花括号进行的列表初始化语法,其实早在C++98时代就有了,只不过历史上他们只是被用来对数组元素进行初始化操作,以及初始化自定义POD类型的数据(简单理解就是可以memcpy复制对象的类型)。比如:

int v1[] = {1, 2, 3, 4};
int v2[5] = {1,2,3};
char msg = "hello, world!";

在使用列表来初始化数组的时候,如果声明数组的时候没有指定数组尺寸大小,则编译器就使用其列表包含的元素个数自动计算数组的尺寸;如果提供了数组尺寸,但是列表的元素数目小于数组尺寸,则系统会将剩余的元素全部赋值为0。如果是字符数组的话,C++还支持使用字符串常亮来进行初始化。

一、C++11的统一初始化器

在新标准C++11中这个东西使用范围和特性被大大的扩展了,而且已经成为了一个基础而又重要的利器,几乎可以执行任何的初始化操作,所以也被称为”Uniform initialization”,尽管国内还是习惯上称为列表初始化。因为他可以避免传统初始化中的诸多问题和缺陷,所以从Bjarne Stroustrup爷爷的《C++ 程序设计语言》描述口吻看来,列表初始化是被大力推荐使用的,即便用惯旧式初始化的C++程序员初看起来会很不习惯,但C++强烈建议使用上述第一种方式进行统一初始化操作。

C++11还引入了atomic原子类型,这种类型的变量(比如std::atomic)是无法使用传统=方式进行初始化的,只能使用{}或者()方式进行初始化;对于自定义类,如果其非静态成员变量具有默认值,则这个默认值只能用{}或者=进行初始化。总之也只有{}相比于其他类型可以用于任何位置,所以称为统一初始化器也不足为怪了。

防止类型收窄这是列表初始化的一个非常重要的特性,因为C++有很多隐式转换操作的发生,比如:浮点类型隐式转换为整形、长整型转换为短整型导致数据丢失,高精度的数据转换为低精度的数据,但凡是数据转换一次后再向回转换而不能得到原有表示的情况下,都可以称之为类型收窄。类型收窄常常会导致数据精度丢失,甚至潜在有意或无意错误的发生,尤其是那些不喜欢看编译警告的程序员常常会被忽略掉这些提示,而通过列表初始化的语法,编译器在编译期间进行这方面的强制检查,如果发生类型收窄则强制编译失败,从而能够杜绝相关问题的发生。

除了上面的优势之外,列表初始化语法还可以杜绝C++重构造语法的阴暗面。C++秉承的一个观念就是任何可以被解释为声明语法的语句都会被解释为声明语句,这会导致调用默认构造函数创建对象的时候被用错。

Widget w(); // 被解释为函数声明
Widget w{}; // OK

另外一种情况就是在容器使用的时候,也比较容易产生混淆的语义,这个时候使用列表初始初始化语法可以表明我们提供的列表是实际的元素。因为容器类的构造函数具有使用std::initializer_list作为重载的版本,所以如果要显式调用其某个版本的构造函数,就需要使用()来规避std::initializer_list的版本,称之为ctor-resort。

vector<int> v1{99};   // 一个元素,值为99
vector<int> v2(99);   // 实际是调用构造函数,共99个元素,默认值都是0
vector<string> v2("hello");  // Error,无匹配的构造函数

二、统一初始化器的阴暗面

使用列表初始化语法在绝大多数情况都能胜任,而且工作的很好,但是一旦同std::initializer_list结合起来,它的使用就会让人感觉混淆不清。在auto进行类型自动推导的时候,{}会默认被推导为std::initializer_list,如果这种结果不是你想要的,就需要进行规避以使用其他方式进行初始化操作。

auto z1 {99}; // initializer_list<int>
auto z2 = 99; // int

如果你认为避免上面那个坑就结束了,呵呵……统一初始化器最大的麻烦还在于其和构造函数的结合。如果某个类的构造函数,其提供了一个接收std::initializer_list作为参数类型的重载版本,那么使用统一初始化句法进行构造对象的时候,编译器将会强烈优先使用具有初始化列表的重载版本。

我们知道,以std::initializer_list作为形参的话,其实参列表中的元素不要求和T完全匹配,而只需要能转换成T即可,此时只要转换后满足要求,编译器都会优先使用std::initializer_list作为形参的重载版本,即使其他重载的构造函数具有更优的匹配。在转换的过程中,如果类型提升满足要求则会正常调用;如果发生了窄化转换,则调用会失败报错;只有诸如字符串和数字这类无法转换的类型相互重载时候,重载机制才可能正常工作。

struct Widget {
 Widget(int i, bool b) { cout << "1" << endl; }
 Widget(int i, double d) { cout << "2" << endl; }
 Widget(std::initializer_list<bool> il) { cout << "3" << endl; }
};
Widget w1{1, true}; // 3
Widget w2{9, true}; // Error

还有一个极端情况,如果一个自定义类既有默认构造函数,也有std::initializer_list作为参数的构造函数,则使用{}作为初始化值构造对象的话,C++标准显式规定了调用其默认构造函数,如果想要以空列表的语义调用第二个版本,则可以使用({})的方式进行初始化。

三、C++对象的默认初始化行为

列表初始化还允许使用空列表{}作为初始化器,这时候元素都使用默认值进行初始化,或者调用自定义类型的默认构造函数,所以列表初始化的变量其默认行为都是良好的。

对于我们自定义的数据类型,如有必要也可以,在具体调用的时候不需要具体元素类型为T,只要能转化成T即可,在构造函数中使用迭代器访问列表中的每个元素。

C++规定,如果定义的变量没有指定初始化器,则全局变量、名字空间变量、局部static变量、static成员将会执行相应数据类型的空列表{}初始化;而对于局部变量、自由存储区上的变量(堆对象),除非它们定义于用户自定义类型的默认构造函数中,否则不会执行默认初始化,这种情况是需要格外需要注意的,操作未初始化变量可能会造成不确定的行为。

int* p{ new int{} };
char* q{ new char[2014]{} }

呵呵,如果突然看着一大坨C++代码使用{}进行初始化,可能会一时间觉得奇怪,不过习惯也就好啦!

总结

以上就是这篇文章的全部内容了,本文还有许多不足,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对猪先飞的支持。

参考

  • Effective Modern C++
  • 深入应用C++11:代码优化与工程级应用
  • C++ 程序设计语言(原书第 4 版)
  • GotW #1 Solution: Variable Initialization – or Is It?
[!--infotagslink--]

相关文章

  • Vue 3.0中jsx语法的使用

    这篇文章主要介绍了Vue 3.0 中 jsx 语法使用,帮助大家更好的理解和使用vue框架,感兴趣的朋友可以了解下...2020-11-13
  • C语言的基本语法详解

    本篇文章主要讲解C语言 基本语法,这里提供简单的示例和代码来详细讲解C语言的基本语法,开始学习C语言的朋友可以看一下,希望能够给你带来帮助...2021-09-18
  • C#中方括号[]的语法及作用介绍

    C#中方括号[]可用于数组,索引、属性,更重要的是用于外部DLL类库的引用。...2020-06-25
  • 详解JavaScript语言的基本语法要求

    JavaScript语言的基本语法要求有哪些呐?下面将为大家一一解答: 一、区分大小写 JavaScript语言区分字符大小写,两个字符串相同大小写不同,被认为是不同的字符串。JavaScript语言的关键字也区分大小写,按语法要求应...2015-11-24
  • 解决 VSCode 编写 C++11 代码报红问题分析

    今天在写 C++ 代码的时候用上 C++11 的特性,然后发现 VSCode 虽然可以编译通过,但是会在相应位置报红,这是怎么回事呢?下面小编给大家带来了解决方法,一起看看吧...2021-09-27
  • C#语法相比其它语言比较独特的地方(一)

    这篇文章主要介绍了C#语法相比其它语言比较独特的地方(一),本文讲解了switch语句可以用来测试string型的对象、多维数组、foreach语句、索引器和Property等内容,需要的朋友可以参考下...2020-06-25
  • JavaScript的基础语法和数据类型详解

    这篇文章主要介绍了JavaScript的基础语法和数据类型,保姆级的详细教程,万字长文详细的列出了JavaScript的各种语法,建议收藏系列,希望可以有所帮助...2021-09-29
  • php file_exists 函数与 file_exists语法

    其实于php file_exists 函数与 file_exists语法我们早就讲过了,下面我们来看看一下关于它的使用方法与实例吧 bool file_exists ( string filename ) 如果由 filen...2016-11-25
  • C#基础语法:Base关键字学习笔记

    这篇文章主要介绍了C#基础语法:Base关键字学习笔记,本文讲解了它的一些基础知识以及测试代码,需要的朋友可以参考下...2020-06-25
  • PHP基本语法和数据类型学习笔记

    一篇入门级别的PHP基本语法和数据类型文章,希望对各位php初学者能提供一些帮助哦。 PHP基本语法和数据类型: (1)、PHP基本语法: 1、htm 和 php 混编 ...2016-11-25
  • Python3 基础语法详解

    在本篇文章里小编给大家分享的是一篇关于Python3基础语法知识点总结内容,有兴趣的朋友们可以学习下,希望能够给你带来帮助...2021-10-08
  • php switch 语法

    php switch 语法用于执行基于不同条件不同的行动。 -------------------------------------------------- ------------------------------ 开关语句的PHP 使用swit...2016-11-25
  • PHP的一些基本语法(数组,字符串)

    PHP的一些基本语法(数组,字符串) 上篇写了PHP的一些基本语法,以及表单的提交和处理等,这一篇中接着上一篇写了数组的用法以及数组和字符串之间的相互处理,希望对大家有所...2016-11-25
  • c++11中regex正则表达式示例简述

    这篇文章主要给大家介绍了关于c++11中regex正则表达式的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用c++11具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2020-04-25
  • PHP流程控制的替代语法示例

    PHP流程控制的替代语法示例,有需要了解的朋友可进入参考。 了解过wordpress一类博客程序的模板的PHPer都会看到很多奇怪的PHP语法,比如: <?php if(empty($GET_['a']...2016-11-25
  • C语言的语法风格与代码书写规范指南

    这篇文章主要介绍了C语言的语法风格与代码书写规范指南,文中主张了一些诸如变量和结构体的命名规范等细节方面的问题,需要的朋友可以参考下...2020-04-25
  • BAT文件语法和技巧(bat文件的编写及使用)

    批处理文件是一个文本文件,这个文件的每一行都是一条DOS命令(大部分时候就好象我们在DOS提示符下执行的命令行一样),你可以使用DOS下的Edit或者Windows的记事本(notepad)等任何文本文件编辑工具创建和修改批处理文件...2020-06-30
  • C++11中的原子量和内存序详解

    这篇文章主要给大家介绍了关于C++11中原子量和内存序的相关资料,文中通过示例代码介绍地方非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • php && 实例与& 语法

    & 这是按位与运算符。 && 且运算符 来看个 &实例 $a = 11; $b = 7; printf("%b & %b = %b", $a, $b, $a & $b); //Output: //1011 & 111 = 11 //再看&&实例...2016-11-25
  • php入门教程之常用数据类型和基本语法

    php中数据类型有整型、小数型(浮动数)、布尔类型、字符及数组,变量,常量等下面我们一起来看看吧。 一 PHP常用数据类型 1.基本数据类型 1.1整型 1.2小数型(浮动数...2016-11-25