Bootstrap

【C++】构造函数冒号后面的初始化列表使用小括号( )和大括号{ }的区别(回子的四种写法)

1、创建对象时,使用小括号( )和大括号{ }的区别

1)内置类型的初始值,以下三种方法没有区别

int x(0); 
int y = 0;
int z{0}; 

2)自定义类型的赋值

Widget w1;       调用默认构造函数
Widget w2 = w1;  调用拷贝构造函数,不是赋值操作
w1 = w2;         调用operator=函数,是赋值操作

3)类内成员的默认初始值
类内成员的默认初始值:可以使用大括号、等号;不可以使用小括号

class Widget {
  ...
private:
  int x{ 0 };   x的默认初始值为0
  int y = 0;    同上
  int z( 0 );   报错
}

注意:当大括号用于类内成员的默认初始值时,如果初始值存在丢失信息的风险,则编译器将报错:

doubel ld = 3.14;
int a {ld};    报错,存在信息丢失风险
int b (ld);    正确

4)声明对象还是创建对象
调用带参构造函数,可以使用如下方法:

Widget w1(10); 

如果想调用午餐构造函数时,不可以使用下面的方法,因为这会被编译器理解为:声明了个函数,而不是创建对象

Widget w2()

正确的方法:

Widget w2; 

或者使用大括号

Widget w2{}; 

2、大括号和std::initializer_list

大括号这么牛逼,统一都使用大括号不就得了。实际情况不是这么理想。这很C++……

1)先看使用 std::initializer_list 的情况(小括号和大括号行为一致)
大括号初始化的缺点是它有时会显现令人惊讶的的行为。这些行为的出现是因为与std::initializer_list混淆了。在构造函数中,只

class Widget {
public:
  Widget(int i, bool b);
  Widget(int i, double d);
  ...
};

Widget w1(10, true);   调用第一个构造函数
Widget w2{10, true};   调用第一个构造函数
Widget w3(10, 5.0);    调用第二个构造函数
Widget w4{10, 5.0};    调用第二个构造函数

2)形参带有std::initializer_list
如果构造函数的形参带有std::initializer_list,调用构造函数时大括号初始化语法会强制使用带 std::initializer_list 参数的重载构造函数:

class Widget {
public:
  Widget(int i, bool b);
  Widget(int i, double d);
  Widget(std::initializer_list<long double> il);
  ...
};
Widget w1(10, true);    使用圆括号,调用第一个构造函数
Widget w2{10, true};    使用大括号,强制调用第三个构造函数,10true被转换为long double                    
Widget w3(10, 5.0);     使用圆括号,调用第二个构造函数
Widget w4{10, 5.0};     使用大括号,强制调用第三个构造函数,105.0被转换为long double

3)拷贝构造和赋值构造也受带有std::initializer_list的构造函数的影响

class Widget {
public:
  Widget(int i, bool b);
  Widget(int i, double d);
  Widget(std::initializer_list<long double> il);
  operator float() const;   // 支持隐式转换为float类型
  ...
};

Widget w5(w4);    使用圆括号,调用拷贝构造函数
Widget w6{w4};    使用大括号,调用第三个构造函数,原因是先把w4转换为float,再把float转换为long dobule
Widget w7(std::move(m4));  使用圆括号,调用移动构造函数
Widget w8{std::move(m4)};  使用大括号,调用第三个构造函数,理由同w6

4)即使参数数量不匹配,照样优先使用 std::initializer_list 的构造函数,

class Widget {
public:
  Widget(int i, bool b);
  Widget(int i, double d);
  Widget(std::initializer_list<bool> il);  // long double 改为 bool
  ...
};

Widget w{10, 5.0}; 

报错,因为参数个数不匹配,编译器会忽略另外两个构造函数(第二个还是参数精确匹配的!)

5)只有当大括号内的值无法转换为std::initializer_list元素的类型时,编译器才会使用正常的重载选择方法:

class Widget {
public:
  Widget(int i, bool b);
  Widget(int i, double d);
  Widget(std::initializer_list<std::string> il);  // bool 改为 std::string
  ...
};

Widget w1(10, true);   使用圆括号,调用第一个构造函数
Widget w2{10, true};   使用大括号,不过调用第一个构造函数,因为无法转换为string
Widget w3(10, 5.0);    使用圆括号,调用第二个构造函数
Widget w4{10, 5.0};    使用大括号, 不过调用第二个构造函数,因为无法转换为string

6)只有空括号可以不强制匹配 std::initializer_list 构造函数

class Widget {
public:
  Widget();
  Widget(std::initializer_list<int> il);
  ...
};

Widget w1;     调用默认构造函数
Widget w2{};   调用默认构造函数

7)小括号( )和大括号{ }初始化容器时的却

std::vector v1(10, 20); // 使用不带std::initializer_list的构造函数,创建10个元素的vector,每个元素的初始值为20
std::vector v2{10, 20}; // 使用带std::initializer_list的构造函数, 创建2个元素的vector,元素值为10和20

原因是:
std::vector中有一个可以指定容器的大小和容器内元素的初始值的不带std::initializer_list构造函数,但它也有一个可以指定容器中元素值的带std::initializer_list函数。

;