【问题标题】:List initialization, Initializer_list and related questions in C++C++中列表初始化、Initializer_list及相关问题
【发布时间】:2017-10-04 15:46:29
【问题描述】:

我知道这是一个已在 stackoverflow 上广泛讨论的主题,但我很难找到彻底的答案来消除我对 C++ 中的列表初始化和 initializer_lists 的所有困惑,所以我将尝试一下并提出我自己的问题。

请考虑以下 sn-p 代码:

class C
{
public :
    C(int a, int b, int c) : _a (a), _b(b), _c(c) {}; //initialization_list with ()
    //C(int a, int b, int c) : _a{ a }, _b{ b }, _c{ c } {}; //initialization list with {}
private :
    int _a, _b, _c;
};

int main()
{
    C a(5.3,3.3,4.3); // no list
    C b{5.3,3.3,4.3}; // list {}
    C c({5.3,3.3,4.3}); // list {}
}

我不明白为什么这两个初始化列表的行为相似?我期待,当尝试使用_a{a}, _b{b}, _c{c} 类型的初始化列表创建 C 类型的对象时,会出现关于缩小的编译器错误。但是,不会产生错误,_a, _b and _c 只存储整数值。

只有在使用列表“{}”创建对象 b 或 c 时,编译器才会生成缩小错误消息。这是为什么?使用我不知道的 {} 或 () 编写初始化列表之间是否有任何区别,或者行为是否相同?

来我的下一个问题:

class C
{
public :

//private :
    int _a, _b, _c;
};

int main()
{
    C a(5,3,4); //obviously doesn't work as no such constructor
    C b{5,3,4}; //work only if _a, _b and _c are not private nor protected!
}

为什么第二个语句(带大括号)只有在变量是公共的情况下才有效?所涉及的机制是什么?

所以我想更好地理解,除了通过创建具有列表 {} 的对象所提供的“缩小安全性”之外,此列表还有哪些其他“功能”机制提供?因为在第二次调用中,它仍然是被调用的默认构造函数(因此,不是以 initializer_list 作为参数的默认构造函数),对吧?

最后,想象一下在我的class C 中,我有另一个构造函数将初始化列表作为参数。

class C
    {
    public :
        C() = default; //default constructor
        C(int a, int b, int c) : _a (a), _b(b), _c(c) {};
        C(std::initializer_list<int> a) { //do some stuffs with the list};
    private :
        int _a, _b, _c;
    };

很明显,如果尝试创建一个接受除 3(或实际上是 0)以外的任何整数的对象,将调用采用 initializer_list 的构造函数。但是,如果创建这样的对象:

C c();

C c{};

将调用默认构造函数。但是,如果创建一个恰好具有 3 个整数的对象:

C c(5,2,3);

C c{5,2,3};

initializer_list 构造函数将被调用。规则是这样的:

  • 如果可以调用默认构造函数或初始化列表构造函数,则首选默认构造函数
  • 如果可以调用初始化列表构造函数和“普通构造函数”,则首选初始化列表构造函数

因此(如果我错了,请纠正我),如果我这样创建我的对象:

C c{5,3,4};

初始化器列表构造函数将被调用。但是,如果我这样创建对象:

C c(5,3,4);

将调用第二个构造函数(以 3 个整数作为参数)。我的问题是:如果我还想提供缩小安全性,如何使用第二个构造函数而不是初始化器列表创建一个对象? (因为如果我像这个问题的第一个例子那样做,初始化列表构造函数将被调用!)。

不要犹豫,举例说明你的回复,并讨论我在这个问题中没有谈到的与列表相关的概念。我想很好地掌握这些。谢谢。

【问题讨论】:

标签: c++ list


【解决方案1】:

因此,无论何时使用花括号,您都在使用aggregate initialization,这是一种按顺序初始化的结构或类的初始化方法。或通过指示符。例如,

#include <iostream>

struct Foo
{
  int a;
  char b;
};

class Doo
{
public: 
  double h;
  char ch;
};

int main() {
  Foo first = {3, 't'};
  std::cout << first.b << "\n";
  //t
  Doo second = {'3', 50};
  std::cout << second.ch << "\n";
  //2 interpreted as char
}

在这里,当我们使用{} 来初始化一个类或结构时,它们总是被解释为按照类中列出的顺序。这就是打印 '2' 的原因,因为 ASCII 中的 50 对应于字符 '2'。

构造函数初始化

所以你也可以对构造函数初始化列表使用相同的逻辑,

#include <iostream>

struct Pair
{
  int first;
  long second;
};

class Couple
{
public:
  Pair p;
  int g;
public:
 Couple(): p{3, 700}, g{3}
 {}
};

int main() {
  Couple test;
  std::cout << test.p.first << "\n";
  //3
}

这里,p 旁边的 {3, 700} 与代码中 else where 使用的 Pair p = {3, 700}; 相同。您基本上使用顺序聚合初始化。现在,如果我们将 Pair 字段的花括号更改为括号会发生什么?

我们收到此错误

main.cpp: In constructor 'Couple::Couple()':
main.cpp:15:26: error: no matching function for call to 'Pair::Pair(int, int)'
  Couple(): p(3, 700), g{3}

那是因为我们没有接受两个数字的 Pair 构造函数。因此,聚合初始化和括号之间的主要区别在于,您需要为使用括号创建的任何特定参数集实现构造函数,但使用花括号,您可以使用编译器提供给您的默认值。

std::initializer_list 是一种不常用的容器形式,用于包含{} 的初始化列表中的多个参数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-22
    • 2016-12-22
    • 1970-01-01
    • 2015-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多