C ++中的列表初始化,Initializer_list和相关问题

时间:2017-10-04 15:46:30

标签: c++ list

我知道这是一个在stackoverflow上已经讨论得非常广泛的主题,但是我很难找到彻底的解答来消除我在C ++中列表初始化和initializer_lists的所有困惑,所以我只想试一试并问我自己的问题。

请考虑以下代码段:

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时,我期待得到有关缩小的编译器错误。但是,不会生成错误,_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};

将调用iniatializer-list构造函数。但是,如果我像这样创建我的对象:

C c(5,3,4);

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

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

1 个答案:

答案 0 :(得分: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
}

此处,{3, 700}旁边的p与代码中使用的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是一个不常用的容器形式,用于{}初始化列表中的多个参数。