c++ - C++中列表初始化、Initializer_list及相关问题

标签 c++ list

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

请考虑以下代码片段:

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 和 _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!
}

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

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

最后,想象一下在我的 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构造函数。规则是这样的:

  • If either a default constructor or an initializer-list constructor could be invoked, prefer the default constructor
  • If both an initializer-list contructor and an "ordinary constructor" could be invoked, prefer the initializer-list constructor

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

C c{5,3,4};

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

C c(5,3,4);

将调用第二个构造函数(以 3 个整数作为参数)。我的问题是:如果我也想提供缩小安全性,如何使用第二个构造函数而不是初始化列表创建一个对象? (因为如果我按照本问题的第一个示例执行操作,则会调用初始化列表构造函数!)。

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

最佳答案

因此,无论何时使用大括号,您都在使用 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}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 是一种不常用的容器形式,用于使用 {} 初始化列表中的多个参数。

关于c++ - C++中列表初始化、Initializer_list及相关问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46569038/

相关文章:

c - 代码块中的动态分配

c++ - 在 C++ 中将彩色文本打印到控制台

python-3.x - 在数字范围内对列表进行排序

c++ - 如何将常量值转换为非常量变量?

c++ - 为什么迭代器需要默认构造

python - 对元组列表进行排序

c# - 使用 LinQ 每季度过滤数据

python - 如何将列表列表中的所有数字变成整数

C++ vector 指针数组

c++ - 当我将 C++ 中派生类的指针分配给指针时,地址会发生变化