读书频道 > 网站 > 网页设计 > 深入应用C++11:代码优化与工程级应用
1.3.3 初始化列表
15-07-07    下载编辑
收藏    我要投稿   
在StackOverflow的最近一次世界性调查中,C++11在所有的编程语言中排名第二, C++11受到程序员的追捧是毫不意外的,因为它就像C++之父Bjarne Stroustrup说的:它看起来就像一门新的语言。C++11新增加了相当多的立即去当当网订购

1.任意长度的初始化列表

读者可能注意到了,C++11中的stl容器拥有和未显示指定长度的数组一样的初始化能力,代码如下:

int arr[] { 1, 2, 3 };

std::map<std::string, int> mm =
{
   { "1", 1 }, { "2", 2 }, { "3", 3 }
};

std::set<int> ss = { 1, 2, 3 };

std::vector<int> arr = { 1, 2, 3, 4, 5 };

这里arr没有显式指定长度,因此,它的初始化列表可以是任意长度。

同样,std::map、std::set、std::vector也可以在初始化时任意书写需要初始化的内容。

前面自定义的Foo却不具备这种能力,只能按部就班地按照构造函数指定的参数列表进行赋值。

实际上,stl中的容器是通过使用std::initializer_list这个轻量级的类模板来完成上述功能支持的。我们只需要为Foo添加一个std::initializer_list构造函数,它也将拥有这种任意长度初始化的能力,代码如下:

class Foo
{
public:
   Foo(std::initializer_list<int>) {}
};

Foo foo = { 1, 2, 3, 4, 5 }; // OK!

那么,知道了使用std::initializer_list来接收{...},如何通过它来给自定义容器赋值呢?来看代码清单1-9中的例子。

代码清单1-9 通过std::initializer_list给自定义容器赋值示例

class FooVector
{
   std::vector<int> content_;

public:
   FooVector(std::initializer_list<int> list)
   {
      for (auto it = list.begin(); it != list.end(); ++it)
      {
         content_.push_back(*it);
      }
   }
};

class FooMap
{
   std::map<int, int> content_;
   using pair_t = std::map<int, int>::value_type;

public:
   FooMap(std::initializer_list<pair_t> list)
   {
      for (auto it = list.begin(); it != list.end(); ++it)
      {
         content_.insert(*it);
      }
   }
};

FooVector foo_1 = { 1, 2, 3, 4, 5 };
FooMap    foo_2 = { { 1, 2 }, { 3, 4 }, { 5, 6 } };

这里定义了两个自定义容器,一个是FooVector,采用std::vector<int>作为内部存储;另一个是FooMap,采用std::map<int, int>作为内部存储。

可以看到,FooVector、FooMap的初始化过程,就和它们使用的内部存储结构一样。

这两个自定义容器的构造函数中,std::initializer_list负责接收初始化列表。并通过我们熟知的for循环过程,把列表中的每个元素取出来,并放入内部的存储空间中。

std::initializer_list不仅可以用来对自定义类型做初始化,还可以用来传递同类型的数据集合,代码如下:

void func(std::initializer_list<int> l)
{
   for (auto it = l.begin(); it != l.end(); ++it)
   {
      std::cout << *it << std::endl;
   }
}

int main(void)
{
   func({});           // 一个空集合
   func({ 1, 2, 3 });  // 传递 { 1, 2, 3 }
   return 0;
}

如上述所示,在任何需要的时候,std::initializer_list都可以当作参数来一次性传递同类型的多个数据。

2.std::initializer_list的一些细节

了解了std::initializer_list之后,再来看看它的一些特点,如下:

它是一个轻量级的容器类型,内部定义了iterator等容器必需的概念。

对于std::initializer_list<T>而言,它可以接收任意长度的初始化列表,但要求元素必须是同种类型T(或可转换为T)。

它有3个成员接口:size()、begin()、end()。

它只能被整体初始化或赋值。

通过前面的例子,已经知道了std::initializer_list的前几个特点。其中没有涉及的接口size()是用来获得std::initializer_list的长度的,比如:

std::initializer_list<int> list = { 1, 2, 3 };
size_t n = list.size(); // n == 3

最后,对std::initializer_list的访问只能通过begin()和end()进行循环遍历,遍历时取得的迭代器是只读的。因此,无法修改std::initializer_list中某一个元素的值,但是可以通过初始化列表的赋值对std::initializer_list做整体修改,代码如下:

std::initializer_list<int> list;
size_t n = list.size(); // n == 0
list = { 1, 2, 3, 4, 5 };
n = list.size();        // n == 5
list = { 3, 1, 2, 4 };
n = list.size();        // n == 4

std::initializer_list拥有一个无参数的构造函数,因此,它可以直接定义实例,此时将得到一个空的std::initializer_list。

之后,我们对std::initializer_list进行赋值操作(注意,它只能通过初始化列表赋值),可以发现std::initializer_list被改写成了{1, 2, 3, 4, 5}。

然后,还可以对它再次赋值,std::initializer_list被修改成了{3, 1, 2, 4}。

看到这里,可能有读者会关心std::initializer_list的传递或赋值效率。

假如std::initializer_list在传递或赋值的时候如同vector之类的容器一样,把每个元素都复制了一遍,那么使用它传递类对象的时候就要斟酌一下了。

实际上,std::initializer_list是非常高效的。它的内部并不负责保存初始化列表中元素的拷贝,仅仅存储了列表中元素的引用而已。

因此,我们不应该像这样使用:

std::initializer_list<int> func(void)
{
   int a = 1, b = 2;
   return { a, b }; // a、b在返回时并没有被拷贝
}

虽然这能够正常通过编译,但却无法传递出我们希望的结果(a、b在函数结束时,生存期也结束了,因此,返回的将是不确定的内容)。

这种情况下最好的做法应该是这样:

std::vector<int> func(void)
{
   int a = 1, b = 2;
   return { a, b };
}

使用真正的容器,或具有转移/拷贝语义的物件来替代std::initializer_list返回需要的结果。

我们应当总是把std::initializer_list看做保存对象的引用,并在它持有对象的生存期结束之前完成传递。

点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:1.3 功能
下一篇:1.5 小结
相关文章
图文推荐
JavaScript网页动画设
1.9 响应式
1.8 登陆页式
1.7 主题式
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站