cpp列表初始化

cpp列表初始化是什么

形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
using namespace std;
class A {
public:
A(int num) : a_(num) {
cout << a_ << endl;
}
private:
const int a_;
};

int main(){
A a(12);
}

即,在构造函数后,使用列表的方式来初始化成员变量。

构造函数内部执行顺序

  1. 调用构造函数
  2. 基类构造函数
  3. 进入左括号前,按照成员在类内声明的顺序,调用默认构造函数初始化成员变量。这就是隐藏的列表初始化。也是列表初始化时按声明顺序调用构造函数的原因。
  4. 执行构造函数内部语句
  5. 出构造函数,构造函数完成

其实,列表初始化一直都在,只是我们在代码中忽略了。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
using namespace std;
class Test {
public:
Test() {
cout << "constructor called" << endl;
}
Test(const Test& t) {
cout << "copy constructor called" << endl;
}
Test& operator=(const Test& t) {
cout << "assignment operator called" << endl;
return *this;
}
};

class Base {
public:
Base() {
cout << "call base constructor" << endl;
}
};

class B : public Base {
public:
B(Test ele) : t_(ele) {
cout << "call B constructor" << endl;
}
private:
Test t_;
};

int main(){
Test t;
cout << "==========" << endl;
B b(t);
}

结果:

1
2
3
4
5
6
7
@└────> # ./a.out 
constructor called
==========
copy constructor called // 传参的拷贝构造
call base constructor // 调用基类构造函数
copy constructor called // 列表初始化
call B constructor // 子类构造函数内语句

列表初始化顺序

列表初始化顺序并不取决于在构造函数中列表的顺序,而取决于该成员在类中声明的顺序。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
using namespace std;
class Test1 {
public:
Test1() = default;
Test1(const Test1& t) {
cout << "Test1 copy constructor called" << endl;
}
};

class Test2 {
public:
Test2() = default;
Test2(const Test2& t) {
cout << "Test2 copy constructor called" << endl;
}
};

class Test3 {
public:
Test3() = default;
Test3(const Test3& t) {
cout << "Test3 copy constructor called" << endl;
}
};

class A {
public:
A(Test1 ele1, Test2 ele2, Test3 ele3) : t2_(ele2), t1_(ele1), t3_(ele3) {}
private:
Test3 t3_;
Test1 t1_;
Test2 t2_;
};

int main(){
Test1 t1;
Test2 t2;
Test3 t3;
cout << "==========" << endl;
A a(t1, t2, t3);
}

结果:

1
2
3
4
5
6
7
8
@└────> # ./a.out 
==========
Test3 copy constructor called // A 的构造函数中的参数复制,因为是从右边的参数开始先拷贝,所以先构造 ele3,再 ele2,最后 ele1
Test2 copy constructor called
Test1 copy constructor called
Test3 copy constructor called // 按照类中声明的顺序,先拷贝给 t3_,再 t1_,最后 t2_。
Test1 copy constructor called
Test2 copy constructor called

区别

  1. 如上面所说,在构造函数中使用 :a_(num) 的形式,对成员变量进行初始化。而在大括号中用等号来对成员变量进行赋值,是赋值行为,并非初始化。这个可以使用下面的例子来验证:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
using namespace std;
class Test {
public:
Test() {
cout << "constructor called" << endl;
}
Test(const Test& t) {
cout << "copy constructor called" << endl;
}
Test& operator=(const Test& t) {
cout << "assignment operator called" << endl;
return *this;
}
};

class A {
public:
A(Test ele) {
t_ = ele;
}
private:
Test t_;
};

class B {
public:
B(Test ele) : t_(ele) {}
private:
Test t_;
};

int main(){
Test t;
cout << "==========" << endl;
A a(t);
cout << "==========" << endl;
B b(t);
}

输出:

1
2
3
4
5
6
7
8
9
@└────> # ./a.out 
constructor called
==========
copy constructor called // A 中 ele 的拷贝构造
constructor called // A 中 t_ 的构造函数
assignment operator called // A 中 = ele 的赋值调用
==========
copy constructor called // B 中 ele 的拷贝构造
copy constructor called // B 中 t_ 的初始化(拷贝构造)

可以看到上面对于 A 来说,是先调用构造函数再调用赋值函数,而对于 B 来说就是拷贝构造函数。所以列表的形式是初始化,不同于在大括号内赋值

  1. 列表初始化可以为 const 变量赋初值,但是大括号内赋值不行。