ModernC新特性
Modern C++ 新特性--- initializer_list
我们先从一个例子开始进入今天的话题,如下图所示:
上图所示的例子中,我们希望初始化一个vector,除了v9的初始化之外,我们发现,如果我们希望一次性初始化一个vector拥有多个不同数值的元素,并不方便。要么采用上图中初始化v1的方法,要么采用上图中初始化v4的方法,但是这个必须的有一个已经满足我们需要的vector如上图中的v1。还有就是采用初始化v7的方法,逐个push_back操作。这些操作对于程序员来说,都不够直接也不够方便。上图中v9的初始化方法就是c++11之后的新特性,初始化数值被逗号分开,用大括号括起来直接跟在变量后面加分号结尾就完成了初始化。再看看上图所示的v9初始化例子,对比一下,是不是方便了很多。实际上不过vector可以这样初始化,普通数据类型也可以这样初始化,如下图所示的例子:
在上图所示的例子中,我们看到基本数据类型也可以使用这种新的初始化方式,其中unsigned long 类型的 ul初始化时只有一对{},其中并未写任何值,这表示将ul初始化为该类型的默认值,对于unsigned long类型来说,默认值是0,因此ul的初值被初始化为0。同时上图中也展示了如何使用新式初始化方式初始化STL库的map数据类型。我们看到这样的初始化方式确实方便了不少,这正是c++11之后所追求的统一初始化 (Uniform Initialization)。而实现统一初始化背后的技术支撑就是采用了initializer_list。下面我们就来解释一下,c++编译器是如何使用initializer_list来完成统一初始化的。
std::initializer_list是C++11引入的,定义在头文件中。initializer_list是一个模板类,它的背后是由std::array 来实现的。编译器在编译代码时看到变量后面的{},就会根据{}之间元素的个数对其首先构造一个array,T在具体环境中就是变量的类型或者是容器存储的类型。然后用构造好的array和array中元素的个数来构造一个initializer_list对象。使用该对象来初始化变量。你可能会有疑问,编译器如何用构造的initializer_list对象来初始化变量呢?我们通过下图所示的例子说明。
运行上图所示的例子,可以得到如下输出:
可见编译器在初始化class A的对象a时调用了其构造函数,上文不是说编译器已经构造了一个initializer_list对象准备来初始化对应的变量吗? 上图的例子中没有构造函数接收一个initializer_list对象啊? 是的,编译器构造完成initializer_list对象后就会去对应的类定义中查找是否有接收参数为initializer_list对象的构造函数,如果有就调用该函数,如果没有,就会将构造好的initializer_list对象中的数据拆开,由于编译器知道数据的类型和个数,因此就会查找有没有接收对应类型参数和个数匹配的构造函数,如果有就将拆开的数据传给找到的构造函数,完成构造。如上图中所示的例子,最终是调用了class A的一个参数的构造函数。为了验证上面的说法,我们给class A 添加一个带有initializer_list类型参数的构造函数,再运行试试看。如下图所示:
我们运行上图所示的代码,得到如下输出。
可以看到带有initializer_list类型参数的构造函数被调用了。实际上,一个函数如果以initializer_list类型作为参数的话,那么这个函数就可以接收变长参数了,因为initializer_list类型是可以处理变长参数的。想想如果我们初始化vector的时候,我们想初始化几个元素是可以自由写的,因此initializer_list类型必须支持长度可变。实际上其原理我们在之前已经提到了。但是initializer_list支持的数据类型必须一样。从这一点上来说可变参数模板(不了解可变参数模板的可以看我之前的文章<< C++2.0新特性之变参模板>>)更强悍一些。下面演示一个使用initializer_list类型作为函数参数来支持可变参数长度的例子,如下图所示:
运行上图所示的代码得到下面输出:
正式因为initializer_list支持变长数据处理,我们可以根据方便的使用标准库里提供的函数了,如下图所示的求多个数据的最大值和最小值。
这比之前只能两个数进行比较方便多了吧。
本文介绍了Modern c++统一初始化以及initializer_list的用法和其背后的基本原理,希望对大家有所帮助。