C++ 巩固进阶之二

大纲

C++ 对象深入探索

对象的构造与析构

案例代码一

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <iostream>

using namespace std;

class Test {

public:
Test(int a = 10) : _a(a) {
cout << "Test(int)" << endl;
}

~Test() {
cout << "~Test()" << endl;
}

Test(const Test &t) : _a(t._a) {
cout << "Test(const Test& t)" << endl;
}

Test &operator=(const Test &t) {
cout << "operator=(const Test& t)" << endl;
this->_a = t._a;
return *this;
}

private:
int _a;
};

void test01() {
cout << "============ test01() ============" << endl;

// 调用普通构造函数
Test t1;

// 调用拷贝构造函数
Test t2(t1);

// 调用拷贝构造函数
Test t3 = t1;

// 调用普通构造函数
// 执行效果相当于 `Test t4(30);`
// Test(30) 通常会显式生成临时对象,临时对象的生存周期是所在语句
// 但是,如果用临时对象去拷贝构造新对象,那么临时对象就不会产生,也就是直接构造新对象就行,这是任意 C++ 编译器都会做的优化
Test t4 = Test(30);

cout << "--------- 1 ---------" << endl;

// 调用赋值运算符重载函数
t4 = t2;

// 先调用普通构造函数,然后再调用赋值运算符重载函数
// Test(20) 会显式生成临时对象,临时对象的生存周期是所在语句
t4 = Test(20);

cout << "--------- 2 ---------" << endl;

// 先调用普通构造函数,然后再调用赋值运算符重载函数
// (Test)20 会显式生成临时对象,临时对象的生存周期是所在语句
// 执行效果相当于 `t4 = Test(20);`,这里要求 Test 类拥有 int 类型的构造函数
t4 = (Test) 20;

cout << "--------- 3 ---------" << endl;

// 先调用普通构造函数,然后再调用赋值运算符重载函数
// 会隐式生成临时对象,临时对象的生存周期是所在语句
// 执行效果相当于 `t4 = Test(20);`,这里要求 Test 类拥有 int 类型的构造函数
t4 = 20;

cout << "--------- 4 ---------" << endl;
}

void test02() {
cout << "\n============ test02() ============" << endl;

// 调用普通构造函数
// (Test)20 会显式生成临时对象,临时对象的生存周期是所在语句
// 当临时对象出了所在语句,会立刻被析构,同时指针 p 会成为野指针
// 结论:使用指针变量指向临时对象是不安全的
Test *p = &Test(20);

// 调用普通构造函数
// (Test)20 会显式生成临时对象,临时对象的生存周期是所在函数
// 但是,这里的临时对象出了所在语句,不会立刻被析构,而是直到出了函数作用域才会被析构
// 在函数作用域内,ref 引用会一直有效
// 结论:使用引用变量指向临时对象是安全的
Test &ref = Test(20);

cout << "--------- 1 ---------" << endl;
}

int main() {
test01();
test02();
return 0;
}

程序运行输出的结果如下:

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
--------- 1 ---------
operator=(const Test& t)
Test(int)
operator=(const Test& t)
~Test()
--------- 2 ---------
Test(int)
operator=(const Test& t)
~Test()
--------- 3 ---------
Test(int)
operator=(const Test& t)
~Test()
--------- 4 ---------
~Test()
~Test()
~Test()
~Test()

============ test02() ============
Test(int)
~Test()
Test(int)
--------- 1 ---------
~Test()

案例代码二

提示

在 C++ 中,最先析构的对象,往往都是最后析构的,即符合 "先构造后析构" 的规则(类似栈的 "先进后出"),这规则同样适用于类成员属性的构造和析构。

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#include <iostream>

using namespace std;

class Test {

public:
Test(int a = 5, int b = 5) : _a(a), _b(b) {
cout << "Test(" << a << ", " << b << ")" << endl;
}

~Test() {
cout << "~Test(" << this->_a << ", " << this->_b << ")" << endl;
}

Test(const Test &t) : _a(t._a), _b(t._b) {
cout << "Test(const Test& t)" << endl;
}

Test &operator=(const Test &t) {
cout << "operator=(const Test& t)" << endl;
this->_a = t._a;
this->_b = t._b;
return *this;
}

private:
int _a;
int _b;
};

void test01() {
cout << "\n============ test01() ============" << endl;

// 调用普通构造函数
Test t2(20, 20);

// 调用拷贝构造函数
Test t3 = t2;

cout << "--------- 1 ---------" << endl;

// 调用普通构造函数
// 执行效果相当于 `Test t4(30, 30);`
// Test(30, 30) 通常会显式生成临时对象,临时对象的生存周期是所在语句
// 但是,如果用临时对象去拷贝构造新对象,那么临时对象就不会产生,也就是直接构造新对象就行,这是任意 C++ 编译器都会做的优化
// 这里定义的局部静态变量,只在程序结束的时候才会被析构
static Test t4 = Test(30, 30);

cout << "--------- 2 ---------" << endl;

// 先调用普通构造函数,然后再调用赋值运算符重载函数
// Test(40, 40) 会显式生成临时对象,临时对象的生存周期是所在语句
t2 = Test(40, 40);

// 先调用普通构造函数,然后再调用赋值运算符重载函数
// (Test)(50, 50) 会显式生成临时对象,临时对象的生存周期是所在语句
// 执行效果相当于 `t2 = Test(50);`,这里要求 Test 类拥有对应的构造函数
t2 = (Test) (50, 50);

// 先调用普通构造函数,然后再调用赋值运算符重载函数
// 60 会显式生成临时对象,临时对象的生存周期是所在语句
// 执行效果相当于 `t2 = Test(60);`,这里要求 Test 类拥有对应的构造函数
t2 = 60;

cout << "--------- 3 ---------" << endl;
}

void test02() {
cout << "\n============ test02() ============" << endl;

// 调用普通构造函数
Test *p1 = new Test(70, 70);

// 调用(两次)普通构造函数
Test *p2 = new Test[2];

cout << "--------- 4 ---------" << endl;

// 调用普通构造函数
// Test(80, 80) 会显式生成临时对象,临时对象的生存周期是所在语句
// 当临时对象出了所在语句,会立刻被析构,同时指针 p 会成为野指针
// 结论:使用指针变量指向临时对象是不安全的
Test *p3 = &Test(80, 80);

// 调用普通构造函数
// Test(90, 90) 会显式生成临时对象,临时对象的生存周期是所在函数
// 但是,这里的临时对象出了所在语句,不会立刻被析构,而是直到出了函数作用域才会被析构
// 在函数作用域内,ref 引用会一直有效
// 结论:使用引用变量指向临时对象是安全的
Test &p4 = Test(90, 90);

cout << "--------- 5 ---------" << endl;

// 调用析构函数
delete p1;

// 调用(两次)析构函数
delete[] p2;

cout << "--------- 6 ---------" << endl;
}

// 第一个执行(t1 是最先构造的,而且又是最后析构)
Test t1(10, 10);

// 第三个执行
int main() {
cout << "\n============ start main()============" << endl;
test01();
test02();
cout << "\n============ end main()============" << endl;
return 0;
}

// 第二个执行
Test t5(100, 100);

程序运行输出的结果如下:

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
43
Test(10, 10)
Test(100, 100)

============ start main()============

============ test01() ============
Test(20, 20)
Test(const Test& t)
--------- 1 ---------
Test(30, 30)
--------- 2 ---------
Test(40, 40)
operator=(const Test& t)
~Test(40, 40)
Test(50, 5)
operator=(const Test& t)
~Test(50, 5)
Test(60, 5)
operator=(const Test& t)
~Test(60, 5)
--------- 3 ---------
~Test(20, 20)
~Test(60, 5)

============ test02() ============
Test(70, 70)
Test(5, 5)
Test(5, 5)
--------- 4 ---------
Test(80, 80)
~Test(80, 80)
Test(90, 90)
--------- 5 ---------
~Test(70, 70)
~Test(5, 5)
~Test(5, 5)
--------- 6 ---------
~Test(90, 90)

============ end main()============
~Test(30, 30)
~Test(100, 100)
~Test(10, 10)

C++ 函数调用深入理解