C++ 异常处理详解

本文档基于 exceptions.cpp 文件,详细介绍 C++ 中的异常处理机制,包括自定义异常、标准异常、异常捕获与处理等内容。

异常处理基础

异常处理是 C++ 中一种处理错误的机制,它允许程序在遇到错误时跳转到特定的错误处理代码。

基本异常处理结构

1
2
3
4
5
try {
// 可能抛出异常的代码
} catch (异常类型 e) {
// 异常处理代码
}

自定义异常类

1. 自定义异常基类

1
2
3
4
5
6
7
8
9
10
11
class MyException : public std::exception {
private:
std::string message;

public:
MyException(const std::string& msg) : message(msg) {}

const char* what() const noexcept override {
return message.c_str();
}
};

2. 特定异常类

1
2
3
4
5
6
7
8
9
10
11
// 除零异常
class DivisionByZeroException : public std::runtime_error {
public:
DivisionByZeroException() : std::runtime_error("Division by zero!") {}
};

// 负值异常
class NegativeValueException : public std::runtime_error {
public:
NegativeValueException(const std::string& msg) : std::runtime_error(msg) {}
};

异常抛出与捕获

1. 除法操作异常

1
2
3
4
5
6
double divide(double a, double b) {
if (b == 0) {
throw DivisionByZeroException(); // 抛出除零异常
}
return a / b;
}

2. 平方根计算异常

1
2
3
4
5
6
double squareRoot(double x) {
if (x < 0) {
throw NegativeValueException("Cannot calculate square root of negative number"); // 抛出负值异常
}
return std::sqrt(x);
}

3. 值处理异常

1
2
3
4
5
6
7
8
9
void processValue(int value) {
if (value < 0) {
throw MyException("Value cannot be negative"); // 抛出自定义异常
}
if (value > 100) {
throw std::out_of_range("Value exceeds maximum limit of 100"); // 抛出标准异常
}
std::cout << "Processing value: " << value << std::endl;
}

4. 向量元素访问异常

1
2
3
4
5
6
int getElement(const std::vector<int>& vec, int index) {
if (index < 0 || index >= vec.size()) {
throw std::out_of_range("Index out of range"); // 抛出越界异常
}
return vec[index];
}

异常处理示例

1. 基本异常处理

1
2
3
4
5
6
7
8
std::cout << "=== Basic Exception Handling ===" << std::endl;
try {
int divisor = 0;
int result = 10 / divisor; // 这里会触发除零异常
std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl; // 捕获并处理异常
}

2. 自定义异常处理

1
2
3
4
5
6
7
8
9
10
11
12
std::cout << "\n=== Custom Exception ===" << std::endl;
try {
processValue(-5); // 会抛出负值异常
} catch (const MyException& e) {
std::cout << "Caught MyException: " << e.what() << std::endl;
}

try {
processValue(150); // 会抛出超出范围异常
} catch (const std::out_of_range& e) {
std::cout << "Caught out_of_range: " << e.what() << std::endl;
}

3. 除法异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::cout << "\n=== Division Exception ===" << std::endl;
try {
double result = divide(10.0, 0.0); // 会抛出除零异常
std::cout << "Result: " << result << std::endl;
} catch (const DivisionByZeroException& e) {
std::cout << "Caught DivisionByZeroException: " << e.what() << std::endl;
}

try {
double result = divide(10.0, 2.0); // 正常执行
std::cout << "Result: " << result << std::endl;
} catch (const DivisionByZeroException& e) {
std::cout << "Caught DivisionByZeroException: " << e.what() << std::endl;
}

4. 平方根异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::cout << "\n=== Square Root Exception ===" << std::endl;
try {
double result = squareRoot(-4); // 会抛出负值异常
std::cout << "Result: " << result << std::endl;
} catch (const NegativeValueException& e) {
std::cout << "Caught NegativeValueException: " << e.what() << std::endl;
}

try {
double result = squareRoot(16); // 正常执行
std::cout << "Result: " << result << std::endl;
} catch (const NegativeValueException& e) {
std::cout << "Caught NegativeValueException: " << e.what() << std::endl;
}

5. 多个 catch 块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
std::cout << "\n=== Multiple Catch Blocks ===" << std::endl;
try {
int choice = 1;
if (choice == 1) {
throw std::runtime_error("Runtime error occurred");
} else if (choice == 2) {
throw std::logic_error("Logic error occurred");
} else {
throw std::exception();
}
} catch (const std::runtime_error& e) {
std::cout << "Caught runtime_error: " << e.what() << std::endl;
} catch (const std::logic_error& e) {
std::cout << "Caught logic_error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "Caught generic exception: " << e.what() << std::endl;
}

6. 嵌套 try-catch

1
2
3
4
5
6
7
8
9
10
11
std::cout << "\n=== Nested Try-Catch ===" << std::endl;
try {
try {
throw std::runtime_error("Inner exception");
} catch (const std::exception& e) {
std::cout << "Inner catch: " << e.what() << std::endl;
throw std::runtime_error("Outer exception"); // 重新抛出异常
}
} catch (const std::exception& e) {
std::cout << "Outer catch: " << e.what() << std::endl;
}

7. 向量索引异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::cout << "\n=== Vector Index Exception ===" << std::endl;
std::vector<int> numbers = {10, 20, 30, 40, 50};

try {
int value = getElement(numbers, 2); // 正常访问
std::cout << "Element at index 2: " << value << std::endl;
} catch (const std::out_of_range& e) {
std::cout << "Caught out_of_range: " << e.what() << std::endl;
}

try {
int value = getElement(numbers, 10); // 越界访问
std::cout << "Element at index 10: " << value << std::endl;
} catch (const std::out_of_range& e) {
std::cout << "Caught out_of_range: " << e.what() << std::endl;
}

8. 捕获所有异常

1
2
3
4
5
6
7
8
std::cout << "\n=== Catch All Exceptions ===" << std::endl;
try {
riskyFunction(); // 会抛出异常
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
} catch (...) { // 捕获所有其他类型的异常
std::cout << "Caught unknown exception" << std::endl;
}

9. 无异常保证

1
2
3
4
5
6
7
std::cout << "\n=== No-throw Guarantee ===" << std::endl;
auto safeDivide = [](double a, double b) noexcept { // noexcept 表示该函数不会抛出异常
return (b != 0) ? a / b : 0;
};

double result = safeDivide(10.0, 0.0);
std::cout << "Safe division result: " << result << std::endl;

10. 异常规格说明

1
2
3
4
5
6
7
8
9
10
std::cout << "\n=== Exception Specification ===" << std::endl;
auto functionWithExceptionSpec = []() throw(std::runtime_error) { // 指明可能抛出的异常类型
throw std::runtime_error("Specified exception");
};

try {
functionWithExceptionSpec();
} catch (const std::runtime_error& e) {
std::cout << "Caught specified exception: " << e.what() << std::endl;
}

11. 重新抛出异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::cout << "\n=== Rethrowing Exception ===" << std::endl;
auto processAndRethrow = []() {
try {
throw std::runtime_error("Original exception");
} catch (const std::exception& e) {
std::cout << "Caught in inner handler: " << e.what() << std::endl;
throw; // 重新抛出异常
}
};

try {
processAndRethrow();
} catch (const std::exception& e) {
std::cout << "Caught in outer handler: " << e.what() << std::endl;
}

12. 标准异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::cout << "\n=== Standard Exceptions ===" << std::endl;
try {
throw std::invalid_argument("Invalid argument provided");
} catch (const std::invalid_argument& e) {
std::cout << "Caught invalid_argument: " << e.what() << std::endl;
}

try {
throw std::length_error("Length error occurred");
} catch (const std::length_error& e) {
std::cout << "Caught length_error: " << e.what() << std::endl;
}

try {
throw std::bad_alloc();
} catch (const std::bad_alloc& e) {
std::cout << "Caught bad_alloc: " << e.what() << std::endl;
}

13. 构造函数中的异常

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
std::cout << "\n=== Exception in Constructor ===" << std::endl;
class Resource {
public:
Resource(int size) {
if (size <= 0) {
throw std::invalid_argument("Size must be positive");
}
std::cout << "Resource created with size: " << size << std::endl;
}

~Resource() {
std::cout << "Resource destroyed" << std::endl;
}
};

try {
Resource res(10); // 正常创建
} catch (const std::exception& e) {
std::cout << "Exception in constructor: " << e.what() << std::endl;
}

try {
Resource res(-5); // 构造函数抛出异常
} catch (const std::exception& e) {
std::cout << "Exception in constructor: " << e.what() << std::endl;
}

异常处理最佳实践

  1. 只在真正异常的情况下使用异常:不要将异常用于常规控制流
  2. 抛出有意义的异常:异常信息应该清晰地描述错误原因
  3. 捕获合适的异常类型:尽量捕获具体的异常类型,而不是所有异常
  4. 使用 RAII 管理资源:确保即使发生异常,资源也能被正确释放
  5. 考虑异常安全性:编写代码时考虑异常可能带来的影响

编译说明

使用以下命令编译本文档中的示例代码,确保中文输出正常:

1
g++ -fexec-charset=GBK exceptions.cpp -o exceptions.exe

-fexec-charset=GBK 选项确保编译后的程序能够正确输出中文字符。

完整示例代码

以下是完整的示例代码,包含本文档中所有的异常处理示例:

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include <cmath>

// 自定义异常基类
class MyException : public std::exception {
private:
std::string message;

public:
MyException(const std::string& msg) : message(msg) {}

const char* what() const noexcept override {
return message.c_str();
}
};

// 除零异常
class DivisionByZeroException : public std::runtime_error {
public:
DivisionByZeroException() : std::runtime_error("Division by zero!") {}
};

// 负值异常
class NegativeValueException : public std::runtime_error {
public:
NegativeValueException(const std::string& msg) : std::runtime_error(msg) {}
};

// 除法函数,可能抛出除零异常
double divide(double a, double b) {
if (b == 0) {
throw DivisionByZeroException();
}
return a / b;
}

// 平方根函数,可能抛出负值异常
double squareRoot(double x) {
if (x < 0) {
throw NegativeValueException("Cannot calculate square root of negative number");
}
return std::sqrt(x);
}

// 处理值的函数,可能抛出自定义异常或标准异常
void processValue(int value) {
if (value < 0) {
throw MyException("Value cannot be negative");
}
if (value > 100) {
throw std::out_of_range("Value exceeds maximum limit of 100");
}
std::cout << "Processing value: " << value << std::endl;
}

// 获取向量元素的函数,可能抛出越界异常
int getElement(const std::vector<int>& vec, int index) {
if (index < 0 || index >= vec.size()) {
throw std::out_of_range("Index out of range");
}
return vec[index];
}

// 可能抛出异常的函数
void riskyFunction() {
throw std::runtime_error("Something went wrong in riskyFunction!");
}

int main() {
// 基本异常处理示例
std::cout << "=== Basic Exception Handling ===" << std::endl;
try {
int divisor = 0;
int result = 10 / divisor;
std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}

// 自定义异常示例
std::cout << "\n=== Custom Exception ===" << std::endl;
try {
processValue(-5);
} catch (const MyException& e) {
std::cout << "Caught MyException: " << e.what() << std::endl;
}

try {
processValue(150);
} catch (const std::out_of_range& e) {
std::cout << "Caught out_of_range: " << e.what() << std::endl;
}

// 除法异常示例
std::cout << "\n=== Division Exception ===" << std::endl;
try {
double result = divide(10.0, 0.0);
std::cout << "Result: " << result << std::endl;
} catch (const DivisionByZeroException& e) {
std::cout << "Caught DivisionByZeroException: " << e.what() << std::endl;
}

try {
double result = divide(10.0, 2.0);
std::cout << "Result: " << result << std::endl;
} catch (const DivisionByZeroException& e) {
std::cout << "Caught DivisionByZeroException: " << e.what() << std::endl;
}

// 平方根异常示例
std::cout << "\n=== Square Root Exception ===" << std::endl;
try {
double result = squareRoot(-4);
std::cout << "Result: " << result << std::endl;
} catch (const NegativeValueException& e) {
std::cout << "Caught NegativeValueException: " << e.what() << std::endl;
}

try {
double result = squareRoot(16);
std::cout << "Result: " << result << std::endl;
} catch (const NegativeValueException& e) {
std::cout << "Caught NegativeValueException: " << e.what() << std::endl;
}

// 多个 catch 块示例
std::cout << "\n=== Multiple Catch Blocks ===" << std::endl;
try {
int choice = 1;
if (choice == 1) {
throw std::runtime_error("Runtime error occurred");
} else if (choice == 2) {
throw std::logic_error("Logic error occurred");
} else {
throw std::exception();
}
} catch (const std::runtime_error& e) {
std::cout << "Caught runtime_error: " << e.what() << std::endl;
} catch (const std::logic_error& e) {
std::cout << "Caught logic_error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "Caught generic exception: " << e.what() << std::endl;
}

// 嵌套 try-catch 示例
std::cout << "\n=== Nested Try-Catch ===" << std::endl;
try {
try {
throw std::runtime_error("Inner exception");
} catch (const std::exception& e) {
std::cout << "Inner catch: " << e.what() << std::endl;
throw std::runtime_error("Outer exception");
}
} catch (const std::exception& e) {
std::cout << "Outer catch: " << e.what() << std::endl;
}

// 向量索引异常示例
std::cout << "\n=== Vector Index Exception ===" << std::endl;
std::vector<int> numbers = {10, 20, 30, 40, 50};

try {
int value = getElement(numbers, 2);
std::cout << "Element at index 2: " << value << std::endl;
} catch (const std::out_of_range& e) {
std::cout << "Caught out_of_range: " << e.what() << std::endl;
}

try {
int value = getElement(numbers, 10);
std::cout << "Element at index 10: " << value << std::endl;
} catch (const std::out_of_range& e) {
std::cout << "Caught out_of_range: " << e.what() << std::endl;
}

// 捕获所有异常示例
std::cout << "\n=== Catch All Exceptions ===" << std::endl;
try {
riskyFunction();
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
} catch (...) {
std::cout << "Caught unknown exception" << std::endl;
}

// 无异常保证示例
std::cout << "\n=== No-throw Guarantee ===" << std::endl;
auto safeDivide = [](double a, double b) noexcept {
return (b != 0) ? a / b : 0;
};

double result = safeDivide(10.0, 0.0);
std::cout << "Safe division result: " << result << std::endl;

// 异常规格说明示例
std::cout << "\n=== Exception Specification ===" << std::endl;
auto functionWithExceptionSpec = []() throw(std::runtime_error) {
throw std::runtime_error("Specified exception");
};

try {
functionWithExceptionSpec();
} catch (const std::runtime_error& e) {
std::cout << "Caught specified exception: " << e.what() << std::endl;
}

// 重新抛出异常示例
std::cout << "\n=== Rethrowing Exception ===" << std::endl;
auto processAndRethrow = []() {
try {
throw std::runtime_error("Original exception");
} catch (const std::exception& e) {
std::cout << "Caught in inner handler: " << e.what() << std::endl;
throw;
}
};

try {
processAndRethrow();
} catch (const std::exception& e) {
std::cout << "Caught in outer handler: " << e.what() << std::endl;
}

// 标准异常示例
std::cout << "\n=== Standard Exceptions ===" << std::endl;
try {
throw std::invalid_argument("Invalid argument provided");
} catch (const std::invalid_argument& e) {
std::cout << "Caught invalid_argument: " << e.what() << std::endl;
}

try {
throw std::length_error("Length error occurred");
} catch (const std::length_error& e) {
std::cout << "Caught length_error: " << e.what() << std::endl;
}

try {
throw std::bad_alloc();
} catch (const std::bad_alloc& e) {
std::cout << "Caught bad_alloc: " << e.what() << std::endl;
}

// 构造函数中的异常示例
std::cout << "\n=== Exception in Constructor ===" << std::endl;
class Resource {
public:
Resource(int size) {
if (size <= 0) {
throw std::invalid_argument("Size must be positive");
}
std::cout << "Resource created with size: " << size << std::endl;
}

~Resource() {
std::cout << "Resource destroyed" << std::endl;
}
};

try {
Resource res(10);
} catch (const std::exception& e) {
std::cout << "Exception in constructor: " << e.what() << std::endl;
}

try {
Resource res(-5);
} catch (const std::exception& e) {
std::cout << "Exception in constructor: " << e.what() << std::endl;
}

return 0;
}

总结

异常处理是 C++ 中一种强大的错误处理机制,它允许程序在遇到错误时能够优雅地处理并恢复。通过本文档的学习,您应该已经掌握了以下内容:

  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
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260

## exceptions.cpp

```c++
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include <cmath>

class MyException : public std::exception {
private:
std::string message;

public:
MyException(const std::string& msg) : message(msg) {}

const char* what() const noexcept override {
return message.c_str();
}
};

class DivisionByZeroException : public std::runtime_error {
public:
DivisionByZeroException() : std::runtime_error("Division by zero!") {}
};

class NegativeValueException : public std::runtime_error {
public:
NegativeValueException(const std::string& msg) : std::runtime_error(msg) {}
};

double divide(double a, double b) {
if (b == 0) {
throw DivisionByZeroException();
}
return a / b;
}

double squareRoot(double x) {
if (x < 0) {
throw NegativeValueException("Cannot calculate square root of negative number");
}
return std::sqrt(x);
}

void processValue(int value) {
if (value < 0) {
throw MyException("Value cannot be negative");
}
if (value > 100) {
throw std::out_of_range("Value exceeds maximum limit of 100");
}
std::cout << "Processing value: " << value << std::endl;
}

int getElement(const std::vector<int>& vec, int index) {
if (index < 0 || index >= vec.size()) {
throw std::out_of_range("Index out of range");
}
return vec[index];
}

void riskyFunction() {
throw std::runtime_error("Something went wrong in riskyFunction!");
}

int main() {
std::cout << "=== Basic Exception Handling ===" << std::endl;
try {
double divisor = 0.0;
double result = divide(10.0, divisor);
std::cout << "Result: " << result << std::endl;
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
}

std::cout << "\n=== Custom Exception ===" << std::endl;
try {
processValue(-5);
} catch (const MyException& e) {
std::cout << "Caught MyException: " << e.what() << std::endl;
}

try {
processValue(150);
} catch (const std::out_of_range& e) {
std::cout << "Caught out_of_range: " << e.what() << std::endl;
}

std::cout << "\n=== Division Exception ===" << std::endl;
try {
double result = divide(10.0, 0.0);
std::cout << "Result: " << result << std::endl;
} catch (const DivisionByZeroException& e) {
std::cout << "Caught DivisionByZeroException: " << e.what() << std::endl;
}

try {
double result = divide(10.0, 2.0);
std::cout << "Result: " << result << std::endl;
} catch (const DivisionByZeroException& e) {
std::cout << "Caught DivisionByZeroException: " << e.what() << std::endl;
}

std::cout << "\n=== Square Root Exception ===" << std::endl;
try {
double result = squareRoot(-4);
std::cout << "Result: " << result << std::endl;
} catch (const NegativeValueException& e) {
std::cout << "Caught NegativeValueException: " << e.what() << std::endl;
}

try {
double result = squareRoot(16);
std::cout << "Result: " << result << std::endl;
} catch (const NegativeValueException& e) {
std::cout << "Caught NegativeValueException: " << e.what() << std::endl;
}

std::cout << "\n=== Multiple Catch Blocks ===" << std::endl;
try {
int choice = 1;
if (choice == 1) {
throw std::runtime_error("Runtime error occurred");
} else if (choice == 2) {
throw std::logic_error("Logic error occurred");
} else {
throw std::exception();
}
} catch (const std::runtime_error& e) {
std::cout << "Caught runtime_error: " << e.what() << std::endl;
} catch (const std::logic_error& e) {
std::cout << "Caught logic_error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "Caught generic exception: " << e.what() << std::endl;
}

std::cout << "\n=== Nested Try-Catch ===" << std::endl;
try {
try {
throw std::runtime_error("Inner exception");
} catch (const std::exception& e) {
std::cout << "Inner catch: " << e.what() << std::endl;
throw std::runtime_error("Outer exception");
}
} catch (const std::exception& e) {
std::cout << "Outer catch: " << e.what() << std::endl;
}

std::cout << "\n=== Vector Index Exception ===" << std::endl;
std::vector<int> numbers = {10, 20, 30, 40, 50};

try {
int value = getElement(numbers, 2);
std::cout << "Element at index 2: " << value << std::endl;
} catch (const std::out_of_range& e) {
std::cout << "Caught out_of_range: " << e.what() << std::endl;
}

try {
int value = getElement(numbers, 10);
std::cout << "Element at index 10: " << value << std::endl;
} catch (const std::out_of_range& e) {
std::cout << "Caught out_of_range: " << e.what() << std::endl;
}

std::cout << "\n=== Catch All Exceptions ===" << std::endl;
try {
riskyFunction();
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
} catch (...) {
std::cout << "Caught unknown exception" << std::endl;
}

std::cout << "\n=== No-throw Guarantee ===" << std::endl;
auto safeDivide = [](double a, double b) noexcept {
return (b != 0) ? a / b : 0;
};

double result = safeDivide(10.0, 0.0);
std::cout << "Safe division result: " << result << std::endl;

std::cout << "\n=== Exception Specification ===" << std::endl;
auto functionWithExceptionSpec = []() {
throw std::runtime_error("Specified exception");
};

try {
functionWithExceptionSpec();
} catch (const std::runtime_error& e) {
std::cout << "Caught specified exception: " << e.what() << std::endl;
}

std::cout << "\n=== Rethrowing Exception ===" << std::endl;
auto processAndRethrow = []() {
try {
throw std::runtime_error("Original exception");
} catch (const std::exception& e) {
std::cout << "Caught in inner handler: " << e.what() << std::endl;
throw;
}
};

try {
processAndRethrow();
} catch (const std::exception& e) {
std::cout << "Caught in outer handler: " << e.what() << std::endl;
}

std::cout << "\n=== Standard Exceptions ===" << std::endl;
try {
throw std::invalid_argument("Invalid argument provided");
} catch (const std::invalid_argument& e) {
std::cout << "Caught invalid_argument: " << e.what() << std::endl;
}

try {
throw std::length_error("Length error occurred");
} catch (const std::length_error& e) {
std::cout << "Caught length_error: " << e.what() << std::endl;
}

try {
throw std::bad_alloc();
} catch (const std::bad_alloc& e) {
std::cout << "Caught bad_alloc: " << e.what() << std::endl;
}

std::cout << "\n=== Exception in Constructor ===" << std::endl;
class Resource {
public:
Resource(int size) {
if (size <= 0) {
throw std::invalid_argument("Size must be positive");
}
std::cout << "Resource created with size: " << size << std::endl;
}

~Resource() {
std::cout << "Resource destroyed" << std::endl;
}
};

try {
Resource res(10);
} catch (const std::exception& e) {
std::cout << "Exception in constructor: " << e.what() << std::endl;
}

try {
Resource res(-5);
} catch (const std::exception& e) {
std::cout << "Exception in constructor: " << e.what() << std::endl;
}

return 0;
}

C++ 多线程编程指南

本文档基于 threads.cpp 文件,详细介绍 C++ 多线程编程的各种特性和用法。所有示例代码均包含中文注释,并确保可以使用 -fexec-charset=GBK 编译选项正常编译。

目录

基本线程创建

最基本的线程创建方式是使用 std::thread 类,并传入一个可调用对象(如函数)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <thread>

// 简单的线程函数
void printHello() {
std::cout << "Hello from thread!" << std::endl;
}

int main() {
// 创建线程并执行 printHello 函数
std::thread t1(printHello);

// 等待线程完成
t1.join();

std::cout << "Thread finished" << std::endl;

return 0;
}

带参数的线程

线程函数可以接收参数,参数会被复制到线程的内部存储空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <thread>
#include <string>

// 带参数的线程函数
void printMessage(const std::string& msg) {
std::cout << msg << std::endl;
}

int main() {
// 创建线程并传递参数
std::thread t2(printMessage, "Hello from parameterized thread!");

// 等待线程完成
t2.join();

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
#include <iostream>
#include <thread>
#include <chrono>

// 计数函数
void countTo(int n) {
for (int i = 1; i <= n; ++i) {
std::cout << "Count: " << i << std::endl;
// 休眠 100 毫秒
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}

int main() {
// 创建两个线程,都执行 countTo 函数
std::thread t3(countTo, 5);
std::thread t4(countTo, 5);

// 等待两个线程完成
t3.join();
t4.join();

return 0;
}

互斥锁与线程安全

当多个线程访问共享资源时,需要使用互斥锁(mutex)来保证线程安全。

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
#include <iostream>
#include <thread>
#include <mutex>

int main() {
// 创建互斥锁
std::mutex mtx;
int counter = 0;

// 使用 lambda 表达式创建递增函数
auto increment = [&mtx, &counter]() {
for (int i = 0; i < 1000; ++i) {
// 使用 lock_guard 自动管理锁的生命周期
std::lock_guard<std::mutex> lock(mtx);
counter++;
}
};

// 创建两个线程执行递增操作
std::thread t5(increment);
std::thread t6(increment);

t5.join();
t6.join();

std::cout << "Final counter value: " << counter << std::endl;

return 0;
}

Unique Lock

std::unique_lock 提供了比 std::lock_guard 更灵活的锁管理,可以手动控制锁的获取和释放。

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
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>

int main() {
std::mutex mtx2;
int sharedValue = 0;

// 修改共享值的函数
auto modifyValue = [&mtx2, &sharedValue](int amount) {
// 创建 unique_lock
std::unique_lock<std::mutex> lock(mtx2);
sharedValue += amount;
std::cout << "Modified value to: " << sharedValue << std::endl;

// 手动释放锁
lock.unlock();

// 执行不需要锁的操作
std::this_thread::sleep_for(std::chrono::milliseconds(50));
};

std::thread t7(modifyValue, 10);
std::thread t8(modifyValue, 20);

t7.join();
t8.join();

return 0;
}

条件变量

条件变量(condition_variable)用于线程间的通知机制,允许线程等待某个条件满足。

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
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>

int main() {
std::mutex mtx3;
std::condition_variable cv;
bool ready = false;

// 工作线程函数
auto worker = [&mtx3, &cv, &ready]() {
std::unique_lock<std::mutex> lock(mtx3);
// 等待 ready 变为 true
cv.wait(lock, [&ready] { return ready; });
std::cout << "Worker thread is processing" << std::endl;
};

std::thread t9(worker);

// 主线程休眠 1 秒后设置 ready 为 true 并通知工作线程
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx3);
ready = true;
std::cout << "Signaling worker thread" << std::endl;
}
cv.notify_one();

t9.join();

return 0;
}

原子操作

原子操作(atomic)提供了无锁的线程安全操作,适用于简单的计数器等场景。

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
#include <iostream>
#include <thread>
#include <atomic>

int main() {
// 创建原子变量
std::atomic<int> atomicCounter(0);

// 原子递增函数
auto atomicIncrement = [&atomicCounter]() {
for (int i = 0; i < 1000; ++i) {
atomicCounter++;
}
};

std::thread t10(atomicIncrement);
std::thread t11(atomicIncrement);

t10.join();
t11.join();

std::cout << "Atomic counter value: " << atomicCounter << std::endl;

return 0;
}

Future 和 Promise

std::futurestd::promise 用于线程间的结果传递。

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
#include <iostream>
#include <thread>
#include <future>
#include <chrono>

int main() {
// 创建 promise 和 future
std::promise<int> promise;
std::future<int> future = promise.get_future();

// 设置值的函数
auto setValue = [&promise]() {
std::this_thread::sleep_for(std::chrono::seconds(1));
promise.set_value(42);
};

std::thread t12(setValue);

std::cout << "Waiting for value..." << std::endl;
// 阻塞等待获取值
int value = future.get();
std::cout << "Received value: " << value << std::endl;

t12.join();

return 0;
}

Async 异步任务

std::async 提供了更高级的异步任务管理方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <future>
#include <chrono>

int main() {
// 创建异步任务
auto asyncResult = std::async(std::launch::async, []() {
std::this_thread::sleep_for(std::chrono::seconds(2));
return 100;
});

std::cout << "Doing other work while async task runs..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));

// 获取异步任务结果
int asyncValue = asyncResult.get();
std::cout << "Async result: " << asyncValue << std::endl;

return 0;
}

线程 ID

每个线程都有一个唯一的 ID,可以通过 std::this_thread::get_id() 获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <thread>

int main() {
std::thread t13([]() {
std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;
});

std::cout << "Main Thread ID: " << std::this_thread::get_id() << std::endl;
t13.join();

return 0;
}

硬件并发支持

std::thread::hardware_concurrency() 可以获取系统支持的并发线程数。

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <thread>

int main() {
unsigned int numThreads = std::thread::hardware_concurrency();
std::cout << "Number of concurrent threads supported: " << numThreads << std::endl;

return 0;
}

线程睡眠

std::this_thread::sleep_for() 可以让当前线程休眠指定的时间。

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <thread>
#include <chrono>

int main() {
std::cout << "Sleeping for 2 seconds..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Woke up!" << std::endl;

return 0;
}

分离线程

分离线程(detached thread)不需要主线程等待其完成,会在后台运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <thread>
#include <chrono>

int main() {
std::thread t14([]() {
for (int i = 0; i < 5; ++i) {
std::cout << "Detached thread: " << i << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
});

// 分离线程
t14.detach();

std::cout << "Main thread continues while detached thread runs" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));

return 0;
}

Packaged Task

std::packaged_task 用于包装可调用对象,以便异步执行并获取结果。

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
#include <iostream>
#include <thread>
#include <future>
#include <chrono>

int main() {
// 创建 packaged_task
std::packaged_task<int(int, int)> task([](int a, int b) {
std::this_thread::sleep_for(std::chrono::seconds(1));
return a + b;
});

// 获取 future
std::future<int> taskFuture = task.get_future();

// 在新线程中执行任务
std::thread t15(std::move(task), 10, 20);

std::cout << "Waiting for packaged task result..." << std::endl;
// 获取任务结果
int taskResult = taskFuture.get();
std::cout << "Task result: " << taskResult << std::endl;

t15.join();

return 0;
}

Shared Future

std::shared_future 允许多个线程共享同一个 future 的结果。

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
#include <iostream>
#include <thread>
#include <future>
#include <chrono>
#include <string>

int main() {
std::promise<int> sharedPromise;
// 创建 shared_future
std::shared_future<int> sharedFuture = sharedPromise.get_future().share();

// 等待并打印结果的函数
auto waitAndPrint = [&sharedFuture](const std::string& name) {
int result = sharedFuture.get();
std::cout << name << " got result: " << result << std::endl;
};

// 创建两个线程共享同一个 future
std::thread t16(waitAndPrint, "Thread 1");
std::thread t17(waitAndPrint, "Thread 2");

// 1 秒后设置值
std::this_thread::sleep_for(std::chrono::seconds(1));
sharedPromise.set_value(999);

t16.join();
t17.join();

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
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <future>
#include <chrono>
#include <vector>
#include <string>

// 简单的线程函数
void printHello() {
std::cout << "Hello from thread!" << std::endl;
}

// 带参数的线程函数
void printMessage(const std::string& msg) {
std::cout << msg << std::endl;
}

// 计数函数
void countTo(int n) {
for (int i = 1; i <= n; ++i) {
std::cout << "Count: " << i << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}

int main() {
std::cout << "=== 基本线程 ===" << std::endl;
std::thread t1(printHello);
t1.join();
std::cout << "Thread finished" << std::endl;

std::cout << "\n=== 带参数的线程 ===" << std::endl;
std::thread t2(printMessage, "Hello from parameterized thread!");
t2.join();

std::cout << "\n=== 多线程 ===" << std::endl;
std::thread t3(countTo, 5);
std::thread t4(countTo, 5);

t3.join();
t4.join();

std::cout << "\n=== 互斥锁线程安全 ===" << std::endl;
std::mutex mtx;
int counter = 0;

auto increment = [&mtx, &counter]() {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
counter++;
}
};

std::thread t5(increment);
std::thread t6(increment);

t5.join();
t6.join();
std::cout << "Final counter value: " << counter << std::endl;

std::cout << "\n=== Unique Lock ===" << std::endl;
std::mutex mtx2;
int sharedValue = 0;

auto modifyValue = [&mtx2, &sharedValue](int amount) {
std::unique_lock<std::mutex> lock(mtx2);
sharedValue += amount;
std::cout << "Modified value to: " << sharedValue << std::endl;
lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
};

std::thread t7(modifyValue, 10);
std::thread t8(modifyValue, 20);

t7.join();
t8.join();

std::cout << "\n=== 条件变量 ===" << std::endl;
std::mutex mtx3;
std::condition_variable cv;
bool ready = false;

auto worker = [&mtx3, &cv, &ready]() {
std::unique_lock<std::mutex> lock(mtx3);
cv.wait(lock, [&ready] { return ready; });
std::cout << "Worker thread is processing" << std::endl;
};

std::thread t9(worker);

std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx3);
ready = true;
std::cout << "Signaling worker thread" << std::endl;
}
cv.notify_one();

t9.join();

std::cout << "\n=== 原子操作 ===" << std::endl;
std::atomic<int> atomicCounter(0);

auto atomicIncrement = [&atomicCounter]() {
for (int i = 0; i < 1000; ++i) {
atomicCounter++;
}
};

std::thread t10(atomicIncrement);
std::thread t11(atomicIncrement);

t10.join();
t11.join();
std::cout << "Atomic counter value: " << atomicCounter << std::endl;

std::cout << "\n=== Future 和 Promise ===" << std::endl;
std::promise<int> promise;
std::future<int> future = promise.get_future();

auto setValue = [&promise]() {
std::this_thread::sleep_for(std::chrono::seconds(1));
promise.set_value(42);
};

std::thread t12(setValue);

std::cout << "Waiting for value..." << std::endl;
int value = future.get();
std::cout << "Received value: " << value << std::endl;

t12.join();

std::cout << "\n=== Async with Future ===" << std::endl;
auto asyncResult = std::async(std::launch::async, []() {
std::this_thread::sleep_for(std::chrono::seconds(2));
return 100;
});

std::cout << "Doing other work while async task runs..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));

int asyncValue = asyncResult.get();
std::cout << "Async result: " << asyncValue << std::endl;

std::cout << "\n=== 线程 ID ===" << std::endl;
std::thread t13([]() {
std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;
});

std::cout << "Main Thread ID: " << std::this_thread::get_id() << std::endl;
t13.join();

std::cout << "\n=== 线程硬件并发 ===" << std::endl;
unsigned int numThreads = std::thread::hardware_concurrency();
std::cout << "Number of concurrent threads supported: " << numThreads << std::endl;

std::cout << "\n=== 线程睡眠 ===" << std::endl;
std::cout << "Sleeping for 2 seconds..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Woke up!" << std::endl;

std::cout << "\n=== 分离线程 ===" << std::endl;
std::thread t14([]() {
for (int i = 0; i < 5; ++i) {
std::cout << "Detached thread: " << i << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
});
t14.detach();
std::cout << "Main thread continues while detached thread runs" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));

std::cout << "\n=== Packaged Task ===" << std::endl;
std::packaged_task<int(int, int)> task([](int a, int b) {
std::this_thread::sleep_for(std::chrono::seconds(1));
return a + b;
});

std::future<int> taskFuture = task.get_future();
std::thread t15(std::move(task), 10, 20);

std::cout << "Waiting for packaged task result..." << std::endl;
int taskResult = taskFuture.get();
std::cout << "Task result: " << taskResult << std::endl;

t15.join();

std::cout << "\n=== Shared Future ===" << std::endl;
std::promise<int> sharedPromise;
std::shared_future<int> sharedFuture = sharedPromise.get_future().share();

auto waitAndPrint = [&sharedFuture](const std::string& name) {
int result = sharedFuture.get();
std::cout << name << " got result: " << result << std::endl;
};

std::thread t16(waitAndPrint, "Thread 1");
std::thread t17(waitAndPrint, "Thread 2");

std::this_thread::sleep_for(std::chrono::seconds(1));
sharedPromise.set_value(999);

t16.join();
t17.join();

return 0;
}

编译与运行

编译命令

使用 -fexec-charset=GBK 选项编译代码,以确保中文输出正常:

1
g++ -std=c++11 -fexec-charset=GBK threads.cpp -o threads.exe

运行结果

运行编译生成的可执行文件,将看到各种线程操作的输出:

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
=== 基本线程 ===
Hello from thread!
Thread finished

=== 带参数的线程 ===
Hello from parameterized thread!

=== 多线程 ===
Count: 1
Count: 1
Count: 2
Count: 2
Count: 3
Count: 3
Count: 4
Count: 4
Count: 5
Count: 5

=== 互斥锁线程安全 ===
Final counter value: 2000

=== Unique Lock ===
Modified value to: 10
Modified value to: 30

=== 条件变量 ===
Signaling worker thread
Worker thread is processing

=== 原子操作 ===
Atomic counter value: 2000

=== Future 和 Promise ===
Waiting for value...
Received value: 42

=== Async with Future ===
Doing other work while async task runs...
Async result: 100

=== 线程 ID ===
Main Thread ID: 1
Thread ID: 2

=== 线程硬件并发 ===
Number of concurrent threads supported: 4

=== 线程睡眠 ===
Sleeping for 2 seconds...
Woke up!

=== 分离线程 ===
Main thread continues while detached thread runs
Detached thread: 0
Detached thread: 1
Detached thread: 2
Detached thread: 3
Detached thread: 4

=== Packaged Task ===
Waiting for packaged task result...
Task result: 30

=== Shared Future ===
Thread 1 got result: 999
Thread 2 got result: 999

总结

本文档介绍了 C++ 中多线程编程的各种特性和用法,包括:

  • 基本线程创建和管理
  • 线程参数传递
  • 多线程并发执行
  • 互斥锁和线程安全
  • 条件变量和线程通知
  • 原子操作
  • Future、Promise 和异步任务
  • 线程 ID 和硬件并发
  • 线程睡眠和分离线程
  • Packaged Task 和 Shared Future

通过这些工具和技术,可以编写出高效、安全的多线程应用程序,充分利用现代计算机的多核处理能力。

threads.cpp

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <atomic>
#include <future>
#include <chrono>
#include <vector>
#include <string>

void printHello() {
std::cout << "Hello from thread!" << std::endl;
}

void printMessage(const std::string& msg) {
std::cout << msg << std::endl;
}

void countTo(int n) {
for (int i = 1; i <= n; ++i) {
std::cout << "Count: " << i << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}

int main() {
std::cout << "=== Basic Thread ===" << std::endl;
std::thread t1(printHello);
t1.join();
std::cout << "Thread finished" << std::endl;

std::cout << "\n=== Thread with Parameters ===" << std::endl;
std::thread t2(printMessage, "Hello from parameterized thread!");
t2.join();

std::cout << "\n=== Multiple Threads ===" << std::endl;
std::thread t3(countTo, 5);
std::thread t4(countTo, 5);

t3.join();
t4.join();

std::cout << "\n=== Mutex for Thread Safety ===" << std::endl;
std::mutex mtx;
int counter = 0;

auto increment = [&mtx, &counter]() {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
counter++;
}
};

std::thread t5(increment);
std::thread t6(increment);

t5.join();
t6.join();
std::cout << "Final counter value: " << counter << std::endl;

std::cout << "\n=== Unique Lock ===" << std::endl;
std::mutex mtx2;
int sharedValue = 0;

auto modifyValue = [&mtx2, &sharedValue](int amount) {
std::unique_lock<std::mutex> lock(mtx2);
sharedValue += amount;
std::cout << "Modified value to: " << sharedValue << std::endl;
lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
};

std::thread t7(modifyValue, 10);
std::thread t8(modifyValue, 20);

t7.join();
t8.join();

std::cout << "\n=== Condition Variable ===" << std::endl;
std::mutex mtx3;
std::condition_variable cv;
bool ready = false;

auto worker = [&mtx3, &cv, &ready]() {
std::unique_lock<std::mutex> lock(mtx3);
cv.wait(lock, [&ready] { return ready; });
std::cout << "Worker thread is processing" << std::endl;
};

std::thread t9(worker);

std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lock(mtx3);
ready = true;
std::cout << "Signaling worker thread" << std::endl;
}
cv.notify_one();

t9.join();

std::cout << "\n=== Atomic Operations ===" << std::endl;
std::atomic<int> atomicCounter(0);

auto atomicIncrement = [&atomicCounter]() {
for (int i = 0; i < 1000; ++i) {
atomicCounter++;
}
};

std::thread t10(atomicIncrement);
std::thread t11(atomicIncrement);

t10.join();
t11.join();
std::cout << "Atomic counter value: " << atomicCounter << std::endl;

std::cout << "\n=== Future and Promise ===" << std::endl;
std::promise<int> promise;
std::future<int> future = promise.get_future();

auto setValue = [&promise]() {
std::this_thread::sleep_for(std::chrono::seconds(1));
promise.set_value(42);
};

std::thread t12(setValue);

std::cout << "Waiting for value..." << std::endl;
int value = future.get();
std::cout << "Received value: " << value << std::endl;

t12.join();

std::cout << "\n=== Async with Future ===" << std::endl;
auto asyncResult = std::async(std::launch::async, []() {
std::this_thread::sleep_for(std::chrono::seconds(2));
return 100;
});

std::cout << "Doing other work while async task runs..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));

int asyncValue = asyncResult.get();
std::cout << "Async result: " << asyncValue << std::endl;

std::cout << "\n=== Thread ID ===" << std::endl;
std::thread t13([]() {
std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl;
});

std::cout << "Main Thread ID: " << std::this_thread::get_id() << std::endl;
t13.join();

std::cout << "\n=== Thread Hardware Concurrency ===" << std::endl;
unsigned int numThreads = std::thread::hardware_concurrency();
std::cout << "Number of concurrent threads supported: " << numThreads << std::endl;

std::cout << "\n=== Thread Sleep ===" << std::endl;
std::cout << "Sleeping for 2 seconds..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Woke up!" << std::endl;

std::cout << "\n=== Detached Thread ===" << std::endl;
std::thread t14([]() {
for (int i = 0; i < 5; ++i) {
std::cout << "Detached thread: " << i << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
});
t14.detach();
std::cout << "Main thread continues while detached thread runs" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));

std::cout << "\n=== Packaged Task ===" << std::endl;
std::packaged_task<int(int, int)> task([](int a, int b) {
std::this_thread::sleep_for(std::chrono::seconds(1));
return a + b;
});

std::future<int> taskFuture = task.get_future();
std::thread t15(std::move(task), 10, 20);

std::cout << "Waiting for packaged task result..." << std::endl;
int taskResult = taskFuture.get();
std::cout << "Task result: " << taskResult << std::endl;

t15.join();

std::cout << "\n=== Shared Future ===" << std::endl;
std::promise<int> sharedPromise;
std::shared_future<int> sharedFuture = sharedPromise.get_future().share();

auto waitAndPrint = [&sharedFuture](const std::string& name) {
int result = sharedFuture.get();
std::cout << name << " got result: " << result << std::endl;
};

std::thread t16(waitAndPrint, "Thread 1");
std::thread t17(waitAndPrint, "Thread 2");

std::this_thread::sleep_for(std::chrono::seconds(1));
sharedPromise.set_value(999);

t16.join();
t17.join();

return 0;
}

C++ Lambda 表达式详解

Lambda 表达式是 C++11 引入的一种便捷的匿名函数对象创建方式,它允许我们在需要函数的地方直接定义函数,而不需要单独声明函数。本文档基于 lambda.cpp 文件,详细介绍 Lambda 表达式的各种用法。

基本语法

Lambda 表达式的基本语法如下:

1
2
3
[capture list](parameters) -> return_type {
// 函数体
}
  • capture list:捕获列表,指定哪些外部变量可以在 Lambda 内部使用,以及如何捕获(值捕获或引用捕获)
  • parameters:参数列表,与普通函数的参数列表类似
  • return_type:返回类型,可以省略,由编译器自动推导
  • function body:函数体,包含 Lambda 表达式的执行代码

示例代码与说明

1. 基本 Lambda 表达式

1
2
3
4
5
// 基本 Lambda 表达式
auto greet = []() {
std::cout << "Hello, Lambda!" << std::endl;
};
greet(); // 调用 Lambda 表达式

这是一个最简单的 Lambda 表达式,没有参数,也没有捕获任何变量。

2. 带参数的 Lambda 表达式

1
2
3
4
5
// 带参数的 Lambda 表达式
auto add = [](int a, int b) {
return a + b;
};
std::cout << "5 + 3 = " << add(5, 3) << std::endl;

Lambda 表达式可以像普通函数一样接受参数。

3. 显式指定返回类型的 Lambda 表达式

1
2
3
4
5
// 显式指定返回类型的 Lambda 表达式
auto multiply = [](int a, int b) -> int {
return a * b;
};
std::cout << "4 * 7 = " << multiply(4, 7) << std::endl;

当 Lambda 表达式的返回类型比较复杂或编译器无法自动推导时,可以显式指定返回类型。

4. 值捕获的 Lambda 表达式

1
2
3
4
5
6
7
8
9
// 值捕获的 Lambda 表达式
int x = 10;
auto captureByValue = [x]() {
std::cout << "Captured value: " << x << std::endl;
};
captureByValue(); // 输出: Captured value: 10

x = 20;
captureByValue(); // 仍然输出: Captured value: 10,因为是值捕获

值捕获会在 Lambda 创建时复制变量的值,之后外部变量的修改不会影响 Lambda 内部的值。

5. 引用捕获的 Lambda 表达式

1
2
3
4
5
6
7
8
9
// 引用捕获的 Lambda 表达式
int y = 10;
auto captureByReference = [&y]() {
std::cout << "Captured reference: " << y << std::endl;
};
captureByReference(); // 输出: Captured reference: 10

y = 20;
captureByReference(); // 输出: Captured reference: 20,因为是引用捕获

引用捕获不会复制变量的值,而是捕获变量的引用,因此外部变量的修改会影响 Lambda 内部的值。

6. 混合捕获的 Lambda 表达式

1
2
3
4
5
6
7
8
9
10
// 混合捕获的 Lambda 表达式
int a = 5, b = 10;
auto mixedCapture = [a, &b]() {
std::cout << "a (by value): " << a << ", b (by reference): " << b << std::endl;
};
mixedCapture(); // 输出: a (by value): 5, b (by reference): 10

a = 15;
b = 20;
mixedCapture(); // 输出: a (by value): 5, b (by reference): 20

Lambda 表达式可以同时使用值捕获和引用捕获,分别指定不同的变量。

7. 默认捕获的 Lambda 表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 默认值捕获的 Lambda 表达式
int m = 1, n = 2;
auto defaultCaptureValue = [=]() {
std::cout << "m: " << m << ", n: " << n << std::endl;
};
defaultCaptureValue(); // 输出: m: 1, n: 2

// 默认引用捕获的 Lambda 表达式
auto defaultCaptureRef = [&]() {
std::cout << "m: " << m << ", n: " << n << std::endl;
};
defaultCaptureRef(); // 输出: m: 1, n: 2

m = 10;
n = 20;
defaultCaptureValue(); // 输出: m: 1, n: 2(值捕获)
defaultCaptureRef(); // 输出: m: 10, n: 20(引用捕获)
  • [=]:默认值捕获,捕获所有在 Lambda 内部使用的外部变量
  • [&]:默认引用捕获,捕获所有在 Lambda 内部使用的外部变量的引用

8. mutable Lambda 表达式

1
2
3
4
5
6
7
8
9
10
// mutable Lambda 表达式
int counter = 0;
auto increment = [counter]() mutable {
counter++;
std::cout << "Counter: " << counter << std::endl;
};
increment(); // 输出: Counter: 1
increment(); // 输出: Counter: 2
increment(); // 输出: Counter: 3
std::cout << "Original counter: " << counter << std::endl; // 输出: Original counter: 0

默认情况下,值捕获的变量在 Lambda 内部是不可修改的。使用 mutable 关键字可以允许修改值捕获的变量,但这不会影响外部的原始变量。

9. Lambda 表达式与 STL 算法

9.1 for_each 算法

1
2
3
4
5
// 使用 Lambda 表达式的 for_each 算法
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::for_each(numbers.begin(), numbers.end(), [](int& n) {
n *= 2;
});

9.2 count_if 算法

1
2
3
4
5
// 使用 Lambda 表达式的 count_if 算法
auto evenNumbers = std::count_if(numbers.begin(), numbers.end(), [](int n) {
return n % 2 == 0;
});
std::cout << "Count of even numbers: " << evenNumbers << std::endl;

9.3 find_if 算法

1
2
3
4
5
6
7
// 使用 Lambda 表达式的 find_if 算法
auto greaterThanTen = std::find_if(numbers.begin(), numbers.end(), [](int n) {
return n > 10;
});
if (greaterThanTen != numbers.end()) {
std::cout << "First number > 10: " << *greaterThanTen << std::endl;
}

9.4 sort 算法

1
2
3
4
5
6
7
8
9
10
11
12
// 使用 Lambda 表达式的 sort 算法
std::vector<int> unsorted = {5, 2, 8, 1, 9, 3, 7, 4, 6};

// 升序排序
std::sort(unsorted.begin(), unsorted.end(), [](int a, int b) {
return a < b;
});

// 降序排序
std::sort(unsorted.begin(), unsorted.end(), [](int a, int b) {
return a > b;
});

9.5 transform 算法

1
2
3
4
5
6
// 使用 Lambda 表达式的 transform 算法
std::vector<int> original = {1, 2, 3, 4, 5};
std::vector<int> squared(original.size());

std::transform(original.begin(), original.end(), squared.begin(),
[](int n) { return n * n; });

9.6 accumulate 算法

1
2
3
4
5
6
7
8
9
10
11
// 使用 Lambda 表达式的 accumulate 算法
std::vector<int> values = {1, 2, 3, 4, 5};
int sum = std::accumulate(values.begin(), values.end(), 0, [](int acc, int n) {
return acc + n;
});
std::cout << "Sum: " << sum << std::endl;

int product = std::accumulate(values.begin(), values.end(), 1, [](int acc, int n) {
return acc * n;
});
std::cout << "Product: " << product << std::endl;

9.7 remove_if 算法

1
2
3
4
5
6
// 使用 Lambda 表达式的 remove_if 算法
std::vector<int> toRemove = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto newEnd = std::remove_if(toRemove.begin(), toRemove.end(), [](int n) {
return n % 2 == 0;
});
toRemove.erase(newEnd, toRemove.end()); // 真正删除元素

9.8 copy_if 算法

1
2
3
4
5
6
7
// 使用 Lambda 表达式的 copy_if 算法
std::vector<int> source = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> dest;

std::copy_if(source.begin(), source.end(), std::back_inserter(dest), [](int n) {
return n % 2 != 0;
});

10. Lambda 表达式与 std::function

1
2
3
4
5
6
7
8
9
10
11
// Lambda 表达式与 std::function
std::function<int(int, int)> operation = [](int a, int b) {
return a + b;
};
std::cout << "Operation(3, 4) = " << operation(3, 4) << std::endl;

// 可以重新赋值不同的 Lambda 表达式
operation = [](int a, int b) {
return a * b;
};
std::cout << "Operation(3, 4) = " << operation(3, 4) << std::endl;

使用 std::function 可以存储 Lambda 表达式,方便在不同地方使用。

11. Lambda 表达式作为函数参数

1
2
3
4
5
6
7
8
9
// Lambda 表达式作为函数参数
auto applyOperation = [](std::vector<int>& vec, std::function<int(int)> op) {
for (int& n : vec) {
n = op(n);
}
};

std::vector<int> nums = {1, 2, 3, 4, 5};
applyOperation(nums, [](int n) { return n * n; }); // 传递 Lambda 作为参数

Lambda 表达式可以作为参数传递给其他函数,特别是接受函数对象的 STL 算法。

12. Lambda 表达式与字符串

1
2
3
4
5
6
7
8
9
10
11
12
// Lambda 表达式与字符串
std::vector<std::string> words = {"apple", "banana", "cherry", "date", "elderberry"};

// 按字符串长度排序
std::sort(words.begin(), words.end(), [](const std::string& a, const std::string& b) {
return a.length() < b.length();
});

// 统计长度大于 5 的单词数量
auto longWords = std::count_if(words.begin(), words.end(), [](const std::string& w) {
return w.length() > 5;
});

Lambda 表达式同样适用于处理字符串等复杂类型。

额外示例

1. 嵌套 Lambda 表达式

1
2
3
4
5
6
7
8
9
// 嵌套 Lambda 表达式
auto outerLambda = [](int x) {
return [x](int y) {
return x + y;
};
};

auto add5 = outerLambda(5);
std::cout << "5 + 3 = " << add5(3) << std::endl; // 输出: 8

Lambda 表达式可以嵌套,内部 Lambda 可以访问外部 Lambda 捕获的变量。

2. 带初始化捕获的 Lambda 表达式(C++14)

1
2
3
4
5
// 带初始化捕获的 Lambda 表达式(C++14)
auto lambdaWithInit = [x = 10]() {
std::cout << "Initialized value: " << x << std::endl;
};
lambdaWithInit(); // 输出: Initialized value: 10

C++14 引入了初始化捕获,允许在捕获列表中初始化变量。

3. 泛型 Lambda 表达式(C++14)

1
2
3
4
5
6
7
8
// 泛型 Lambda 表达式(C++14)
auto genericLambda = [](auto a, auto b) {
return a + b;
};

std::cout << "5 + 3 = " << genericLambda(5, 3) << std::endl; // 整数相加
std::cout << "5.5 + 3.3 = " << genericLambda(5.5, 3.3) << std::endl; // 浮点数相加
std::cout << "Hello " + "World" << std::endl; // 字符串相加

C++14 引入了泛型 Lambda 表达式,使用 auto 关键字作为参数类型。

4. Lambda 表达式与智能指针

1
2
3
4
5
6
7
8
9
10
11
// Lambda 表达式与智能指针
std::vector<std::shared_ptr<int>> ptrs;
ptrs.push_back(std::make_shared<int>(10));
ptrs.push_back(std::make_shared<int>(20));
ptrs.push_back(std::make_shared<int>(30));

// 使用 Lambda 表达式遍历智能指针
std::for_each(ptrs.begin(), ptrs.end(), [](const std::shared_ptr<int>& p) {
std::cout << *p << " ";
});
std::cout << std::endl;

Lambda 表达式可以方便地处理智能指针等复杂类型。

编译与运行

使用以下命令编译 lambda.cpp 文件:

1
g++ -fexec-charset=GBK lambda.cpp -o lambda.exe

其中 -fexec-charset=GBK 参数指定编译后的可执行文件使用 GBK 字符集,确保中文输出正常显示。

运行编译后的程序:

1
lambda.exe

总结

Lambda 表达式是 C++11 引入的一种强大特性,它允许我们在需要函数的地方直接定义函数,而不需要单独声明函数。Lambda 表达式的主要优势包括:

  1. 简洁性:可以在需要的地方直接定义函数,不需要单独声明
  2. 灵活性:支持值捕获和引用捕获,可以根据需要选择不同的捕获方式
  3. 强大的表达能力:可以与 STL 算法无缝配合,使代码更加简洁易读
  4. 闭包特性:可以捕获外部变量,形成闭包,使函数更加灵活

通过本文档的介绍,相信您已经对 Lambda 表达式的各种用法有了详细的了解。在实际编程中,合理使用 Lambda 表达式可以使代码更加简洁、易读和高效。

lambda.cpp

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include <string>
#include <numeric>

int main() {
std::cout << "=== Basic Lambda ===" << std::endl;
auto greet = []() {
std::cout << "Hello, Lambda!" << std::endl;
};
greet();

std::cout << "\n=== Lambda with Parameters ===" << std::endl;
auto add = [](int a, int b) {
return a + b;
};
std::cout << "5 + 3 = " << add(5, 3) << std::endl;

std::cout << "\n=== Lambda with Return Type ===" << std::endl;
auto multiply = [](int a, int b) -> int {
return a * b;
};
std::cout << "4 * 7 = " << multiply(4, 7) << std::endl;

std::cout << "\n=== Lambda Capturing by Value ===" << std::endl;
int x = 10;
auto captureByValue = [x]() {
std::cout << "Captured value: " << x << std::endl;
};
captureByValue();

x = 20;
captureByValue();

std::cout << "\n=== Lambda Capturing by Reference ===" << std::endl;
int y = 10;
auto captureByReference = [&y]() {
std::cout << "Captured reference: " << y << std::endl;
};
captureByReference();

y = 20;
captureByReference();

std::cout << "\n=== Lambda with Mixed Capture ===" << std::endl;
int a = 5, b = 10;
auto mixedCapture = [a, &b]() {
std::cout << "a (by value): " << a << ", b (by reference): " << b << std::endl;
};
mixedCapture();

a = 15;
b = 20;
mixedCapture();

std::cout << "\n=== Lambda with Default Capture ===" << std::endl;
int m = 1, n = 2;
auto defaultCaptureValue = [=]() {
std::cout << "m: " << m << ", n: " << n << std::endl;
};
defaultCaptureValue();

auto defaultCaptureRef = [&]() {
std::cout << "m: " << m << ", n: " << n << std::endl;
};
defaultCaptureRef();

m = 10;
n = 20;
defaultCaptureValue();
defaultCaptureRef();

std::cout << "\n=== Lambda with Mutable ===" << std::endl;
int counter = 0;
auto increment = [counter]() mutable {
counter++;
std::cout << "Counter: " << counter << std::endl;
};
increment();
increment();
increment();
std::cout << "Original counter: " << counter << std::endl;

std::cout << "\n=== Lambda with STL Algorithms ===" << std::endl;
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

std::cout << "Original: ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;

std::for_each(numbers.begin(), numbers.end(), [](int& n) {
n *= 2;
});

std::cout << "After for_each (multiply by 2): ";
for (int num : numbers) {
std::cout << num << " ";
}
std::cout << std::endl;

auto evenNumbers = std::count_if(numbers.begin(), numbers.end(), [](int n) {
return n % 2 == 0;
});
std::cout << "Count of even numbers: " << evenNumbers << std::endl;

auto greaterThanTen = std::find_if(numbers.begin(), numbers.end(), [](int n) {
return n > 10;
});
if (greaterThanTen != numbers.end()) {
std::cout << "First number > 10: " << *greaterThanTen << std::endl;
}

std::cout << "\n=== Lambda with Sort ===" << std::endl;
std::vector<int> unsorted = {5, 2, 8, 1, 9, 3, 7, 4, 6};

std::sort(unsorted.begin(), unsorted.end(), [](int a, int b) {
return a < b;
});
std::cout << "Ascending: ";
for (int num : unsorted) {
std::cout << num << " ";
}
std::cout << std::endl;

std::sort(unsorted.begin(), unsorted.end(), [](int a, int b) {
return a > b;
});
std::cout << "Descending: ";
for (int num : unsorted) {
std::cout << num << " ";
}
std::cout << std::endl;

std::cout << "\n=== Lambda with Transform ===" << std::endl;
std::vector<int> original = {1, 2, 3, 4, 5};
std::vector<int> squared(original.size());

std::transform(original.begin(), original.end(), squared.begin(),
[](int n) { return n * n; });

std::cout << "Squared: ";
for (int num : squared) {
std::cout << num << " ";
}
std::cout << std::endl;

std::cout << "\n=== Lambda with std::function ===" << std::endl;
std::function<int(int, int)> operation = [](int a, int b) {
return a + b;
};
std::cout << "Operation(3, 4) = " << operation(3, 4) << std::endl;

operation = [](int a, int b) {
return a * b;
};
std::cout << "Operation(3, 4) = " << operation(3, 4) << std::endl;

std::cout << "\n=== Lambda as Function Parameter ===" << std::endl;
auto applyOperation = [](std::vector<int>& vec, std::function<int(int)> op) {
for (int& n : vec) {
n = op(n);
}
};

std::vector<int> nums = {1, 2, 3, 4, 5};
applyOperation(nums, [](int n) { return n * n; });
std::cout << "Squared: ";
for (int num : nums) {
std::cout << num << " ";
}
std::cout << std::endl;

std::cout << "\n=== Lambda with String ===" << std::endl;
std::vector<std::string> words = {"apple", "banana", "cherry", "date", "elderberry"};

std::sort(words.begin(), words.end(), [](const std::string& a, const std::string& b) {
return a.length() < b.length();
});

std::cout << "Sorted by length: ";
for (const auto& word : words) {
std::cout << word << " ";
}
std::cout << std::endl;

auto longWords = std::count_if(words.begin(), words.end(), [](const std::string& w) {
return w.length() > 5;
});
std::cout << "Count of words with length > 5: " << longWords << std::endl;

std::cout << "\n=== Lambda with Accumulate ===" << std::endl;
std::vector<int> values = {1, 2, 3, 4, 5};
int sum = std::accumulate(values.begin(), values.end(), 0, [](int acc, int n) {
return acc + n;
});
std::cout << "Sum: " << sum << std::endl;

int product = std::accumulate(values.begin(), values.end(), 1, [](int acc, int n) {
return acc * n;
});
std::cout << "Product: " << product << std::endl;

std::cout << "\n=== Lambda with Remove_if ===" << std::endl;
std::vector<int> toRemove = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto newEnd = std::remove_if(toRemove.begin(), toRemove.end(), [](int n) {
return n % 2 == 0;
});
toRemove.erase(newEnd, toRemove.end());

std::cout << "After removing even numbers: ";
for (int num : toRemove) {
std::cout << num << " ";
}
std::cout << std::endl;

std::cout << "\n=== Lambda with Copy_if ===" << std::endl;
std::vector<int> source = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> dest;

std::copy_if(source.begin(), source.end(), std::back_inserter(dest), [](int n) {
return n % 2 != 0;
});

std::cout << "Copied odd numbers: ";
for (int num : dest) {
std::cout << num << " ";
}
std::cout << std::endl;

return 0;
}

C++ 智能指针(Smart Pointers)详细指南

1. 功能说明

本指南基于 smart_pointers.cpp 文件,详细介绍了 C++ 中智能指针的各种类型和用法,包括:

  • unique_ptr:独占所有权的智能指针,确保同一时间只有一个智能指针管理资源
  • shared_ptr:共享所有权的智能指针,使用引用计数管理资源
  • weak_ptr:弱引用智能指针,不增加引用计数,用于解决循环引用问题

智能指针是 C++11 引入的重要特性,它们能够自动管理动态内存,避免内存泄漏,提高代码的安全性和可靠性。本指南将详细介绍每种智能指针的使用方法、优缺点以及适用场景。

2. 代码解析

2.1 资源类定义

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
// 资源类,用于演示智能指针的使用
class Resource {
private:
std::string name;

public:
// 构造函数
Resource(const std::string& n) : name(n) {
std::cout << "Resource " << name << " created" << std::endl;
}

// 析构函数
~Resource() {
std::cout << "Resource " << name << " destroyed" << std::endl;
}

// 成员函数
void doSomething() const {
std::cout << "Resource " << name << " is doing something" << std::endl;
}

// 获取资源名称
std::string getName() const {
return name;
}
};

解析

  • 定义了一个 Resource 类,用于模拟需要动态管理的资源
  • 构造函数和析构函数都有输出信息,便于观察资源的创建和销毁
  • 提供了 doSomething() 方法和 getName() 方法,用于演示智能指针对资源的访问

2.2 unique_ptr(独占所有权智能指针)

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
std::cout << "=== unique_ptr(独占所有权智能指针)===" << std::endl;
{
// 创建 unique_ptr
std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>("A");
// 使用箭头运算符访问成员
ptr1->doSomething();
std::cout << "Resource name: " << ptr1->getName() << std::endl;

// 移动所有权
std::unique_ptr<Resource> ptr2 = std::move(ptr1);
// 检查 ptr1 是否为空
if (ptr1) {
std::cout << "ptr1 is valid" << std::endl;
} else {
std::cout << "ptr1 is null (moved to ptr2)" << std::endl;
}

// 检查 ptr2 是否有效
if (ptr2) {
ptr2->doSomething();
}

// 创建另一个 unique_ptr
std::unique_ptr<Resource> ptr3 = std::make_unique<Resource>("B");
// 重置 unique_ptr,会自动删除所管理的资源
ptr3.reset();
std::cout << "ptr3 reset" << std::endl;
}
std::cout << "End of unique_ptr scope" << std::endl;

解析

  • std::unique_ptr:独占所有权的智能指针,同一时间只能有一个 unique_ptr 管理资源
  • std::make_unique:C++14 引入的函数,用于创建 unique_ptr,比直接使用 new 更安全
  • std::move:转移 unique_ptr 的所有权,转移后原 unique_ptr 变为空
  • reset():重置 unique_ptr,会自动删除所管理的资源
  • 离开作用域时,unique_ptr 会自动删除所管理的资源

2.3 unique_ptr 与数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::cout << "\n=== unique_ptr 与数组 ===" << std::endl;
{
// 创建管理数组的 unique_ptr
std::unique_ptr<int[]> arr = std::make_unique<int[]>(5);
// 初始化数组元素
for (int i = 0; i < 5; ++i) {
arr[i] = i * 10;
}
// 打印数组元素
std::cout << "Array elements: ";
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// 离开作用域时,unique_ptr 会自动删除数组
}

解析

  • std::unique_ptr<T[]>:用于管理数组的 unique_ptr 特化版本
  • 离开作用域时,会自动使用 delete[] 删除数组,而不是 delete

2.4 shared_ptr(共享所有权智能指针)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::cout << "\n=== shared_ptr(共享所有权智能指针)===" << std::endl;
{
// 创建 shared_ptr
std::shared_ptr<Resource> shared1 = std::make_shared<Resource>("Shared");
// 查看引用计数
std::cout << "Reference count: " << shared1.use_count() << std::endl;

{
// 复制 shared_ptr,引用计数增加
std::shared_ptr<Resource> shared2 = shared1;
std::cout << "Reference count: " << shared1.use_count() << std::endl;
shared2->doSomething();
} // shared2 离开作用域,引用计数减少

// 查看引用计数
std::cout << "Reference count after shared2 scope: " << shared1.use_count() << std::endl;
} // shared1 离开作用域,引用计数为 0,资源被删除
std::cout << "End of shared_ptr scope" << std::endl;

解析

  • std::shared_ptr:共享所有权的智能指针,使用引用计数管理资源
  • std::make_shared:创建 shared_ptr 的推荐方法,比直接使用 new 更高效
  • use_count():获取当前 shared_ptr 所管理资源的引用计数
  • 当引用计数为 0 时,资源会被自动删除

2.5 shared_ptr 与自定义删除器

1
2
3
4
5
6
7
8
9
10
11
std::cout << "\n=== shared_ptr 与自定义删除器 ===" << std::endl;
{
// 定义自定义删除器
auto customDeleter = [](Resource* r) {
std::cout << "Custom deleter called for " << r->getName() << std::endl;
delete r;
};
// 使用自定义删除器创建 shared_ptr
std::shared_ptr<Resource> customPtr(new Resource("Custom"), customDeleter);
customPtr->doSomething();
} // customPtr 离开作用域,调用自定义删除器

解析

  • 自定义删除器:可以为 shared_ptr 指定自定义的删除器,用于特殊的资源释放逻辑
  • 删除器可以是函数、函数对象或 lambda 表达式
  • shared_ptr 离开作用域且引用计数为 0 时,会调用自定义删除器

2.6 weak_ptr(弱引用智能指针)

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
std::cout << "\n=== weak_ptr(弱引用智能指针)===" << std::endl;
{
// 创建 shared_ptr
std::shared_ptr<Resource> shared = std::make_shared<Resource>("Weak");
// 创建 weak_ptr,不增加引用计数
std::weak_ptr<Resource> weak = shared;

// 查看引用计数(应该为 1,因为 weak_ptr 不增加引用计数)
std::cout << "Reference count: " << shared.use_count() << std::endl;

// 锁定 weak_ptr,获取 shared_ptr
if (auto locked = weak.lock()) {
locked->doSomething();
std::cout << "Weak pointer locked successfully" << std::endl;
} else {
std::cout << "Weak pointer is expired" << std::endl;
}

// 重置 shared_ptr,资源被删除
shared.reset();
std::cout << "After shared.reset()" << std::endl;

// 再次尝试锁定 weak_ptr
if (auto locked = weak.lock()) {
locked->doSomething();
} else {
std::cout << "Weak pointer is expired" << std::endl;
}
}

解析

  • std::weak_ptr:弱引用智能指针,不增加引用计数
  • lock():尝试获取一个 shared_ptr,如果资源仍然存在则返回有效的 shared_ptr,否则返回空 shared_ptr
  • 用于解决循环引用问题,以及需要检查资源是否仍然存在的场景

2.7 weak_ptr 打破循环引用

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
std::cout << "\n=== weak_ptr 打破循环引用 ===" << std::endl;
// 定义节点类
class Node {
public:
std::string name;
std::shared_ptr<Node> next; // 共享所有权
std::weak_ptr<Node> prev; // 弱引用,不增加引用计数

// 构造函数
Node(const std::string& n) : name(n) {
std::cout << "Node " << name << " created" << std::endl;
}

// 析构函数
~Node() {
std::cout << "Node " << name << " destroyed" << std::endl;
}
};

{
// 创建两个节点
auto node1 = std::make_shared<Node>("Node1");
auto node2 = std::make_shared<Node>("Node2");

// 建立双向链接
node1->next = node2;
node2->prev = node1;

// 查看引用计数
std::cout << "node1 use_count: " << node1.use_count() << std::endl;
std::cout << "node2 use_count: " << node2.use_count() << std::endl;
} // 离开作用域,两个节点都会被正确删除,因为使用了 weak_ptr 打破循环引用
std::cout << "End of node scope" << std::endl;

解析

  • 循环引用问题:当两个或多个 shared_ptr 相互引用时,会导致引用计数永远不为 0,从而导致内存泄漏
  • 解决方案:使用 weak_ptr 代替 shared_ptr 作为反向引用,这样就不会增加引用计数
  • 在上面的例子中,node1->nextshared_ptr,而 node2->prevweak_ptr,这样就打破了循环引用

2.8 shared_ptr 与 std::vector

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::cout << "\n=== shared_ptr 与 std::vector ===" << std::endl;
{
// 创建存储 shared_ptr 的向量
std::vector<std::shared_ptr<Resource>> resources;
// 添加元素
resources.push_back(std::make_shared<Resource>("Resource1"));
resources.push_back(std::make_shared<Resource>("Resource2"));
resources.push_back(std::make_shared<Resource>("Resource3"));

// 遍历向量并使用元素
std::cout << "Resources in vector:" << std::endl;
for (const auto& res : resources) {
res->doSomething();
}
} // 离开作用域,向量中的所有 shared_ptr 被销毁,资源被删除
std::cout << "End of vector scope" << std::endl;

解析

  • std::vector 存储 shared_ptr 是一种常见的用法,用于管理多个资源
  • 当向量被销毁时,其中的所有 shared_ptr 也会被销毁,引用计数相应减少
  • 当引用计数为 0 时,资源会被自动删除

2.9 make_shared vs new

1
2
3
4
5
6
7
std::cout << "\n=== make_shared vs new(性能比较)===" << std::endl;
{
// 使用 make_shared 创建 shared_ptr(推荐)
auto ptr1 = std::make_shared<Resource>("MakeShared");
// 使用 new 创建 shared_ptr(不推荐)
std::shared_ptr<Resource> ptr2(new Resource("New"));
} // 离开作用域,两个资源都被删除

解析

  • std::make_shared 的优点:
    • 更简洁,代码可读性更好
    • 更高效,只进行一次内存分配(同时分配控制块和资源)
    • 更安全,避免了在创建 shared_ptr 之前发生异常导致的内存泄漏
  • 推荐使用 std::make_shared 而不是直接使用 new 创建 shared_ptr

2.10 智能指针与异常安全

1
2
3
4
5
6
7
8
9
10
11
12
13
std::cout << "\n=== 智能指针与异常安全 ===" << std::endl;
{
try {
// 创建智能指针
std::unique_ptr<Resource> ptr = std::make_unique<Resource>("ExceptionSafe");
// 模拟异常
throw std::runtime_error("Test exception");
// 即使发生异常,ptr 离开作用域时也会自动删除资源
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
std::cout << "Resource was properly deleted despite exception" << std::endl;
}
}

解析

  • 异常安全:即使在发生异常的情况下,智能指针也能确保资源被正确释放
  • 当异常发生时,局部变量会被自动销毁,智能指针的析构函数会被调用,从而释放资源
  • 相比之下,使用原始指针时,如果在 newdelete 之间发生异常,会导致内存泄漏

2.11 shared_ptr 的引用计数管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
std::cout << "\n=== shared_ptr 的引用计数管理 ===" << std::endl;
{
// 创建 shared_ptr
auto shared1 = std::make_shared<Resource>("RefCount");
std::cout << "After creation: " << shared1.use_count() << std::endl;

// 复制到另一个 shared_ptr
auto shared2 = shared1;
std::cout << "After copy: " << shared1.use_count() << std::endl;

// 复制到向量
std::vector<std::shared_ptr<Resource>> vec;
vec.push_back(shared1);
std::cout << "After push_back: " << shared1.use_count() << std::endl;

// 从向量中移除
vec.pop_back();
std::cout << "After pop_back: " << shared1.use_count() << std::endl;

// 重置 shared2
shared2.reset();
std::cout << "After shared2.reset(): " << shared1.use_count() << std::endl;
} // shared1 离开作用域,引用计数为 0,资源被删除

解析

  • 引用计数的变化:
    • 创建 shared_ptr 时,引用计数为 1
    • 复制 shared_ptr 时,引用计数增加
    • shared_ptr 离开作用域或被重置时,引用计数减少
    • 当引用计数为 0 时,资源被删除

2.12 智能指针的空检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
std::cout << "\n=== 智能指针的空检查 ===" << std::endl;
{
// 创建空的 unique_ptr
std::unique_ptr<Resource> emptyPtr;
// 检查是否为空
if (!emptyPtr) {
std::cout << "emptyPtr is null" << std::endl;
}

// 创建非空的 unique_ptr
std::unique_ptr<Resource> nonEmptyPtr = std::make_unique<Resource>("NotNull");
// 检查是否为空
if (nonEmptyPtr) {
std::cout << "nonEmptyPtr is not null" << std::endl;
nonEmptyPtr->doSomething();
}
}

解析

  • 智能指针可以像原始指针一样进行空检查
  • 空的智能指针转换为布尔值时为 false,非空的智能指针转换为布尔值时为 true

3. 编译和运行说明

3.1 编译命令

使用以下命令编译 smart_pointers.cpp 文件:

1
g++ -std=c++14 -fexec-charset=GBK -o smart_pointers smart_pointers.cpp

参数说明:

  • -std=c++14:使用 C++14 标准(因为使用了 std::make_unique,这是 C++14 引入的特性)
  • -fexec-charset=GBK:确保输出的中文字符正确显示
  • -o smart_pointers:指定输出可执行文件名为 smart_pointers

3.2 运行命令

编译成功后,使用以下命令运行程序:

1
2
./smart_pointers  # Linux/macOS
.\smart_pointers.exe # Windows

3.3 预期输出

运行程序后,您将看到类似以下的输出:

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
=== unique_ptr(独占所有权智能指针)===
Resource A created
Resource A is doing something
Resource name: A
ptr1 is null (moved to ptr2)
Resource A is doing something
Resource B created
Resource B destroyed
ptr3 reset
Resource A destroyed
End of unique_ptr scope

=== unique_ptr 与数组 ===
Array elements: 0 10 20 30 40

=== shared_ptr(共享所有权智能指针)===
Resource Shared created
Reference count: 1
Reference count: 2
Resource Shared is doing something
Reference count after shared2 scope: 1
Resource Shared destroyed
End of shared_ptr scope

=== shared_ptr 与自定义删除器 ===
Resource Custom created
Resource Custom is doing something
Custom deleter called for Custom
Resource Custom destroyed

=== weak_ptr(弱引用智能指针)===
Resource Weak created
Reference count: 1
Resource Weak is doing something
Weak pointer locked successfully
Resource Weak destroyed
After shared.reset()
Weak pointer is expired

=== weak_ptr 打破循环引用 ===
Node Node1 created
Node Node2 created
node1 use_count: 1
node2 use_count: 2
Node Node1 destroyed
Node Node2 destroyed
End of node scope

=== shared_ptr 与 std::vector ===
Resource Resource1 created
Resource Resource2 created
Resource Resource3 created
Resources in vector:
Resource Resource1 is doing something
Resource Resource2 is doing something
Resource Resource3 is doing something
Resource Resource1 destroyed
Resource Resource2 destroyed
Resource Resource3 destroyed
End of vector scope

=== make_shared vs new(性能比较)===
Resource MakeShared created
Resource New created
Resource New destroyed
Resource MakeShared destroyed

=== 智能指针与异常安全 ===
Resource ExceptionSafe created
Resource ExceptionSafe destroyed
Caught exception: Test exception
Resource was properly deleted despite exception

=== shared_ptr 的引用计数管理 ===
Resource RefCount created
After creation: 1
After copy: 2
After push_back: 3
After pop_back: 2
After shared2.reset(): 1
Resource RefCount destroyed

=== 智能指针的空检查 ===
emptyPtr is null
Resource NotNull created
nonEmptyPtr is not null
Resource NotNull is doing something
Resource NotNull destroyed

4. 技术要点

4.1 智能指针的类型和特性

智能指针类型 所有权 引用计数 线程安全 主要用途
unique_ptr 独占 管理不需要共享的资源
shared_ptr 共享 部分(引用计数操作是线程安全的) 管理需要共享的资源
weak_ptr 解决循环引用问题,观察资源是否存在

4.2 智能指针的使用场景

unique_ptr 的使用场景

  • 独占资源:当资源只需要被一个所有者管理时
  • 工厂函数返回值:工厂函数返回新创建的对象时
  • 作为成员变量:当类需要独占一个资源时
  • 容器元素:当容器需要存储对象的所有权时
  • 移动语义:当需要转移资源所有权时

shared_ptr 的使用场景

  • 共享资源:当资源需要被多个所有者共享时
  • 长期存在的对象:当对象的生命周期由多个部分共同管理时
  • 循环依赖:与 weak_ptr 配合使用,解决循环依赖问题
  • 多线程环境:当多个线程需要访问同一个对象时(需要注意对象本身的线程安全性)

weak_ptr 的使用场景

  • 打破循环引用:当存在 shared_ptr 循环引用时
  • 观察者模式:当需要观察一个对象但不希望影响其生命周期时
  • 缓存:当需要缓存对象但不希望阻止其被释放时
  • 延迟加载:当需要在需要时才获取对象时

4.3 智能指针的性能考虑

  • 内存开销

    • unique_ptr:最小,只需要存储一个指针
    • shared_ptr:较大,需要存储指针和引用计数(控制块)
    • weak_ptr:与 shared_ptr 类似,需要访问控制块
  • 时间开销

    • unique_ptr:几乎没有额外开销,与原始指针类似
    • shared_ptr
      • 创建:使用 make_shared 时开销较小
      • 复制:需要原子操作增加引用计数,有一定开销
      • 销毁:需要原子操作减少引用计数,有一定开销
    • weak_ptr
      • 锁定:需要检查资源是否存在,有一定开销
  • 内存分配

    • std::make_shared:只进行一次内存分配,同时分配控制块和资源
    • new + shared_ptr:进行两次内存分配,分别分配资源和控制块

4.4 智能指针的线程安全性

  • 引用计数的线程安全性

    • shared_ptr 的引用计数操作是线程安全的,多个线程可以同时增加或减少引用计数
    • weak_ptrlock() 操作也是线程安全的
  • 对象访问的线程安全性

    • 智能指针本身不保证对象访问的线程安全性
    • 如果多个线程同时访问智能指针管理的对象,需要额外的同步措施(如互斥锁)
  • 注意事项

    • 避免在多个线程之间共享 unique_ptr,因为它不支持并发访问
    • 对于 shared_ptr,虽然引用计数是线程安全的,但对象本身可能不是

4.5 智能指针的最佳实践

  • 优先使用 unique_ptr

    • 当资源只需要一个所有者时,优先使用 unique_ptr
    • unique_ptr 更轻量,语义更清晰
  • 合理使用 shared_ptr

    • 当资源需要多个所有者时,使用 shared_ptr
    • 优先使用 std::make_shared 创建 shared_ptr
    • 避免循环引用,使用 weak_ptr 打破循环引用
  • 正确使用 weak_ptr

    • 使用 weak_ptr 解决循环引用问题
    • 使用 lock() 方法安全地获取 shared_ptr
    • 检查 lock() 的返回值,确保资源仍然存在
  • 避免混合使用智能指针和原始指针

    • 尽量避免在智能指针和原始指针之间转换
    • 如果必须转换,确保原始指针的生命周期不超过智能指针
  • 避免使用智能指针管理非动态内存

    • 智能指针只应该用于管理动态分配的内存
    • 不要使用智能指针管理栈上的对象或全局对象

5. 常见问题解答

5.1 unique_ptr 和 shared_ptr 有什么区别?

解答

  • 所有权unique_ptr 独占所有权,同一时间只能有一个 unique_ptr 管理资源;shared_ptr 共享所有权,多个 shared_ptr 可以管理同一个资源。
  • 引用计数unique_ptr 不使用引用计数,shared_ptr 使用引用计数。
  • 性能unique_ptr 更轻量,几乎没有额外开销;shared_ptr 有一定的内存和时间开销。
  • 线程安全shared_ptr 的引用计数操作是线程安全的,unique_ptr 不是。
  • 使用场景unique_ptr 适用于资源只需要一个所有者的场景;shared_ptr 适用于资源需要多个所有者的场景。

5.2 如何解决 shared_ptr 的循环引用问题?

解答

  • 使用 weak_ptr 打破循环引用。当两个或多个 shared_ptr 相互引用时,将其中一个引用改为 weak_ptr,这样就不会增加引用计数。
  • 例如,在双向链表中,前向指针使用 shared_ptr,反向指针使用 weak_ptr

5.3 make_shared 和 new 有什么区别?

解答

  • 内存分配std::make_shared 只进行一次内存分配,同时分配控制块和资源;new + shared_ptr 进行两次内存分配,分别分配资源和控制块。
  • 异常安全std::make_shared 更安全,避免了在创建 shared_ptr 之前发生异常导致的内存泄漏。
  • 代码可读性std::make_shared 更简洁,代码可读性更好。
  • 内存使用std::make_shared 创建的对象和控制块在同一块内存中,可能会导致对象的内存比实际需要的时间更长(因为控制块可能被 weak_ptr 引用)。

5.4 智能指针如何与异常处理交互?

解答

  • 智能指针提供了异常安全的内存管理。即使在发生异常的情况下,智能指针也能确保资源被正确释放。
  • 当异常发生时,局部变量会被自动销毁,智能指针的析构函数会被调用,从而释放资源。
  • 相比之下,使用原始指针时,如果在 newdelete 之间发生异常,会导致内存泄漏。

5.5 如何在智能指针和原始指针之间转换?

解答

  • 从智能指针获取原始指针
    • unique_ptr:使用 get() 方法
    • shared_ptr:使用 get() 方法
  • 从原始指针创建智能指针
    • unique_ptrstd::unique_ptr<T> ptr(new T)std::make_unique<T>()
    • shared_ptrstd::shared_ptr<T> ptr(new T)std::make_shared<T>()
  • 注意事项
    • 避免将同一个原始指针同时用于多个智能指针
    • 避免使用智能指针管理由其他方式分配的内存
    • 确保原始指针的生命周期不超过智能指针

5.6 智能指针可以管理数组吗?

解答

  • unique_ptr:可以管理数组,使用 std::unique_ptr<T[]> 特化版本,会自动使用 delete[] 删除数组。
  • shared_ptr:可以管理数组,但需要指定自定义删除器,因为默认情况下会使用 delete 而不是 delete[]
    • 例如:std::shared_ptr<int[]> arr(new int[5], std::default_delete<int[]>());
    • 或者使用 std::make_shared 配合 std::unique_ptrstd::shared_ptr<int[]> arr(std::make_unique<int[]>(5).release());

5.7 智能指针的控制块是什么?

解答

  • 控制块是 shared_ptrweak_ptr 用于管理资源的内部数据结构,包含以下信息:
    • 引用计数(被 shared_ptr 引用的次数)
    • 弱引用计数(被 weak_ptr 引用的次数)
    • 自定义删除器(如果有)
    • 自定义分配器(如果有)
  • 控制块的内存分配:
    • 使用 std::make_shared 时,控制块和资源在同一块内存中分配
    • 使用 new + shared_ptr 时,控制块和资源在不同的内存块中分配
  • 弱引用计数:当弱引用计数为 0 时,控制块会被销毁

6. 代码优化建议

6.1 智能指针的选择

  • 优先使用 unique_ptr

    • 当资源只需要一个所有者时,优先使用 unique_ptr
    • unique_ptr 更轻量,语义更清晰
    • 示例:
      1
      2
      3
      4
      5
      // 推荐
      std::unique_ptr<Resource> ptr = std::make_unique<Resource>("A");

      // 不推荐
      std::shared_ptr<Resource> ptr = std::make_shared<Resource>("A");
  • 合理使用 shared_ptr

    • 当资源需要多个所有者时,使用 shared_ptr
    • 示例:
      1
      2
      3
      // 当需要共享所有权时
      std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>("Shared");
      std::shared_ptr<Resource> ptr2 = ptr1; // 共享所有权

6.2 智能指针的创建

  • 使用 make_shared 和 make_unique
    • 优先使用 std::make_sharedstd::make_unique 创建智能指针
    • 更简洁,更高效,更安全
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      // 推荐
      auto ptr1 = std::make_shared<Resource>("A");
      auto ptr2 = std::make_unique<Resource>("B");

      // 不推荐
      std::shared_ptr<Resource> ptr1(new Resource("A"));
      std::unique_ptr<Resource> ptr2(new Resource("B"));

6.3 智能指针的使用

  • 避免不必要的复制

    • 对于 shared_ptr,避免不必要的复制,因为这会增加引用计数的开销
    • 当只需要访问对象而不需要共享所有权时,使用引用或指针
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // 推荐:使用引用
      void processResource(const Resource& res) {
      res.doSomething();
      }

      // 调用
      std::shared_ptr<Resource> ptr = std::make_shared<Resource>("A");
      processResource(*ptr);

      // 不推荐:不必要的复制
      void processResource(std::shared_ptr<Resource> res) {
      res->doSomething();
      }
  • 使用移动语义

    • 对于 unique_ptr,当需要转移所有权时,使用 std::move
    • 示例:
      1
      2
      std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>("A");
      std::unique_ptr<Resource> ptr2 = std::move(ptr1); // 转移所有权

6.4 智能指针与容器

  • 在容器中存储智能指针
    • 当容器需要管理对象的生命周期时,存储智能指针
    • 对于 unique_ptr,需要使用移动语义或 C++11 后的容器
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      // 存储 unique_ptr
      std::vector<std::unique_ptr<Resource>> resources;
      resources.push_back(std::make_unique<Resource>("A"));

      // 存储 shared_ptr
      std::vector<std::shared_ptr<Resource>> resources;
      resources.push_back(std::make_shared<Resource>("A"));

6.5 智能指针与继承

  • 使用智能指针管理多态对象
    • 智能指针可以很好地管理多态对象
    • 确保基类有虚析构函数
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      class Base {
      public:
      virtual ~Base() = default;
      virtual void doSomething() = 0;
      };

      class Derived : public Base {
      public:
      void doSomething() override {
      std::cout << "Derived::doSomething()" << std::endl;
      }
      };

      // 使用智能指针管理多态对象
      std::unique_ptr<Base> ptr = std::make_unique<Derived>();
      ptr->doSomething(); // 多态调用

6.6 智能指针与异常处理

  • 利用智能指针的异常安全性
    • 当在函数中分配资源并可能抛出异常时,使用智能指针
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      // 推荐:使用智能指针
      void function() {
      auto ptr = std::make_unique<Resource>("A");
      // 可能抛出异常的代码
      throw std::runtime_error("Error");
      // 即使抛出异常,ptr 也会自动释放资源
      }

      // 不推荐:使用原始指针
      void function() {
      Resource* ptr = new Resource("A");
      // 可能抛出异常的代码
      throw std::runtime_error("Error");
      // 抛出异常后,delete 不会被执行,导致内存泄漏
      delete ptr;
      }

6.7 智能指针的自定义删除器

  • 使用 lambda 表达式作为自定义删除器
    • 当需要特殊的资源释放逻辑时,使用自定义删除器
    • lambda 表达式是定义自定义删除器的便捷方式
    • 示例:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 自定义删除器
      auto deleter = [](Resource* r) {
      std::cout << "Custom deleter" << std::endl;
      delete r;
      };

      // 使用自定义删除器
      std::unique_ptr<Resource, decltype(deleter)> ptr(new Resource("A"), deleter);
      std::shared_ptr<Resource> ptr2(new Resource("B"), deleter);

7. 代码优化示例

7.1 从原始指针到智能指针的转换

优化前

1
2
3
4
5
6
7
8
9
10
11
void process() {
Resource* ptr = new Resource("A");
try {
ptr->doSomething();
// 可能抛出异常的代码
} catch (const std::exception& e) {
delete ptr; // 需要手动释放资源
throw;
}
delete ptr; // 需要手动释放资源
}

优化后

1
2
3
4
5
6
void process() {
auto ptr = std::make_unique<Resource>("A");
ptr->doSomething();
// 可能抛出异常的代码
// 不需要手动释放资源,ptr 离开作用域时会自动释放
}

7.2 解决循环引用问题

优化前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Node {
public:
std::string name;
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev; // 循环引用

Node(const std::string& n) : name(n) {}
~Node() { std::cout << "Node " << name << " destroyed" << std::endl; }
};

void process() {
auto node1 = std::make_shared<Node>("Node1");
auto node2 = std::make_shared<Node>("Node2");

node1->next = node2;
node2->prev = node1; // 循环引用,导致内存泄漏

std::cout << "node1 use_count: " << node1.use_count() << std::endl;
std::cout << "node2 use_count: " << node2.use_count() << std::endl;
} // 离开作用域后,node1 和 node2 的引用计数仍然为 1,导致内存泄漏

优化后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Node {
public:
std::string name;
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用 weak_ptr 打破循环引用

Node(const std::string& n) : name(n) {}
~Node() { std::cout << "Node " << name << " destroyed" << std::endl; }
};

void process() {
auto node1 = std::make_shared<Node>("Node1");
auto node2 = std::make_shared<Node>("Node2");

node1->next = node2;
node2->prev = node1; // 不再是循环引用

std::cout << "node1 use_count: " << node1.use_count() << std::endl;
std::cout << "node2 use_count: " << node2.use_count() << std::endl;
} // 离开作用域后,node1 和 node2 的引用计数为 0,资源被正确释放

7.3 智能指针与工厂模式

优化前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Product {
public:
virtual ~Product() = default;
virtual void use() = 0;
};

class ConcreteProduct : public Product {
public:
void use() override { std::cout << "Using ConcreteProduct" << std::endl; }
};

Product* createProduct() {
return new ConcreteProduct();
}

void process() {
Product* product = createProduct();
product->use();
delete product; // 需要手动释放资源
}

优化后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Product {
public:
virtual ~Product() = default;
virtual void use() = 0;
};

class ConcreteProduct : public Product {
public:
void use() override { std::cout << "Using ConcreteProduct" << std::endl; }
};

std::unique_ptr<Product> createProduct() {
return std::make_unique<ConcreteProduct>();
}

void process() {
auto product = createProduct();
product->use();
// 不需要手动释放资源,product 离开作用域时会自动释放
}

7.4 智能指针与容器

优化前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void process() {
std::vector<Resource*> resources;
for (int i = 0; i < 5; ++i) {
resources.push_back(new Resource(std::to_string(i)));
}

for (auto* res : resources) {
res->doSomething();
}

for (auto* res : resources) {
delete res; // 需要手动释放资源
}
}

优化后

1
2
3
4
5
6
7
8
9
10
11
void process() {
std::vector<std::unique_ptr<Resource>> resources;
for (int i = 0; i < 5; ++i) {
resources.push_back(std::make_unique<Resource>(std::to_string(i)));
}

for (const auto& res : resources) {
res->doSomething();
}
// 不需要手动释放资源,vector 销毁时会自动释放所有资源
}

7.5 智能指针与多线程

优化前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void threadFunction(Resource* res) {
// 处理资源
res->doSomething();
}

void process() {
Resource* res = new Resource("A");

std::thread t1(threadFunction, res);
std::thread t2(threadFunction, res);

t1.join();
t2.join();

delete res; // 需要手动释放资源
}

优化后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void threadFunction(std::shared_ptr<Resource> res) {
// 处理资源
res->doSomething();
}

void process() {
auto res = std::make_shared<Resource>("A");

std::thread t1(threadFunction, res);
std::thread t2(threadFunction, res);

t1.join();
t2.join();
// 不需要手动释放资源,所有线程结束后会自动释放
}

8. 总结

C++ 智能指针是现代 C++ 中管理动态内存的重要工具,它们能够自动管理资源的生命周期,避免内存泄漏,提高代码的安全性和可靠性。本指南详细介绍了三种主要的智能指针:

  1. unique_ptr:独占所有权的智能指针,适用于资源只需要一个所有者的场景,是最轻量的智能指针。

  2. shared_ptr:共享所有权的智能指针,使用引用计数管理资源,适用于资源需要多个所有者的场景。

  3. weak_ptr:弱引用智能指针,不增加引用计数,主要用于解决循环引用问题,以及观察资源是否存在。

8.1 核心要点

  • RAII 原则:智能指针基于 RAII(资源获取即初始化)原则,利用对象的生命周期管理资源。
  • 自动内存管理:智能指针能够自动释放所管理的资源,避免内存泄漏。
  • 异常安全:即使在发生异常的情况下,智能指针也能确保资源被正确释放。
  • 所有权管理:智能指针明确表达了资源的所有权关系,使代码更易于理解和维护。
  • 性能考虑:不同的智能指针有不同的性能特点,应根据具体场景选择合适的智能指针。

8.2 最佳实践

  • 优先使用 unique_ptr:当资源只需要一个所有者时,优先使用 unique_ptr
  • 合理使用 shared_ptr:当资源需要多个所有者时,使用 shared_ptr
  • 使用 make_shared 和 make_unique:优先使用 std::make_sharedstd::make_unique 创建智能指针。
  • 避免循环引用:使用 weak_ptr 打破 shared_ptr 的循环引用。
  • 注意异常安全:利用智能指针的异常安全性,避免手动管理内存导致的内存泄漏。
  • 考虑性能:根据具体场景选择合适的智能指针,平衡安全性和性能。

8.3 应用场景

  • 资源管理:管理动态分配的内存、文件句柄、网络连接等资源。
  • 工厂模式:工厂函数返回智能指针,确保资源被正确释放。
  • 容器:在容器中存储智能指针,管理对象的生命周期。
  • 多线程:在多线程环境中安全地共享资源。
  • 观察者模式:使用 weak_ptr 实现观察者模式,避免内存泄漏。

通过掌握智能指针的使用方法和最佳实践,您可以编写更安全、更可靠、更易于维护的 C++ 代码,避免内存泄漏等常见问题,提高代码质量和开发效率。

smart_pointers.cpp

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
#include <iostream>
#include <memory>
#include <string>
#include <vector>
#include <functional>

// 资源类,用于演示智能指针的使用
class Resource {
private:
std::string name;

public:
// 构造函数
Resource(const std::string& n) : name(n) {
std::cout << "Resource " << name << " created" << std::endl;
}

// 析构函数
~Resource() {
std::cout << "Resource " << name << " destroyed" << std::endl;
}

// 成员函数
void doSomething() const {
std::cout << "Resource " << name << " is doing something" << std::endl;
}

// 获取资源名称
std::string getName() const {
return name;
}
};

int main() {
std::cout << "=== unique_ptr(独占所有权智能指针)===" << std::endl;
{
// 创建 unique_ptr
std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>("A");
// 使用箭头运算符访问成员
ptr1->doSomething();
std::cout << "Resource name: " << ptr1->getName() << std::endl;

// 移动所有权
std::unique_ptr<Resource> ptr2 = std::move(ptr1);
// 检查 ptr1 是否为空
if (ptr1) {
std::cout << "ptr1 is valid" << std::endl;
} else {
std::cout << "ptr1 is null (moved to ptr2)" << std::endl;
}

// 检查 ptr2 是否有效
if (ptr2) {
ptr2->doSomething();
}

// 创建另一个 unique_ptr
std::unique_ptr<Resource> ptr3 = std::make_unique<Resource>("B");
// 重置 unique_ptr,会自动删除所管理的资源
ptr3.reset();
std::cout << "ptr3 reset" << std::endl;
}
std::cout << "End of unique_ptr scope" << std::endl;

std::cout << "\n=== unique_ptr 与数组 ===" << std::endl;
{
// 创建管理数组的 unique_ptr
std::unique_ptr<int[]> arr = std::make_unique<int[]>(5);
// 初始化数组元素
for (int i = 0; i < 5; ++i) {
arr[i] = i * 10;
}
// 打印数组元素
std::cout << "Array elements: ";
for (int i = 0; i < 5; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// 离开作用域时,unique_ptr 会自动删除数组
}

std::cout << "\n=== shared_ptr(共享所有权智能指针)===" << std::endl;
{
// 创建 shared_ptr
std::shared_ptr<Resource> shared1 = std::make_shared<Resource>("Shared");
// 查看引用计数
std::cout << "Reference count: " << shared1.use_count() << std::endl;

{
// 复制 shared_ptr,引用计数增加
std::shared_ptr<Resource> shared2 = shared1;
std::cout << "Reference count: " << shared1.use_count() << std::endl;
shared2->doSomething();
} // shared2 离开作用域,引用计数减少

// 查看引用计数
std::cout << "Reference count after shared2 scope: " << shared1.use_count() << std::endl;
} // shared1 离开作用域,引用计数为 0,资源被删除
std::cout << "End of shared_ptr scope" << std::endl;

std::cout << "\n=== shared_ptr 与自定义删除器 ===" << std::endl;
{
// 定义自定义删除器
auto customDeleter = [](Resource* r) {
std::cout << "Custom deleter called for " << r->getName() << std::endl;
delete r;
};
// 使用自定义删除器创建 shared_ptr
std::shared_ptr<Resource> customPtr(new Resource("Custom"), customDeleter);
customPtr->doSomething();
} // customPtr 离开作用域,调用自定义删除器

std::cout << "\n=== weak_ptr(弱引用智能指针)===" << std::endl;
{
// 创建 shared_ptr
std::shared_ptr<Resource> shared = std::make_shared<Resource>("Weak");
// 创建 weak_ptr,不增加引用计数
std::weak_ptr<Resource> weak = shared;

// 查看引用计数(应该为 1,因为 weak_ptr 不增加引用计数)
std::cout << "Reference count: " << shared.use_count() << std::endl;

// 锁定 weak_ptr,获取 shared_ptr
if (auto locked = weak.lock()) {
locked->doSomething();
std::cout << "Weak pointer locked successfully" << std::endl;
} else {
std::cout << "Weak pointer is expired" << std::endl;
}

// 重置 shared_ptr,资源被删除
shared.reset();
std::cout << "After shared.reset()" << std::endl;

// 再次尝试锁定 weak_ptr
if (auto locked = weak.lock()) {
locked->doSomething();
} else {
std::cout << "Weak pointer is expired" << std::endl;
}
}

std::cout << "\n=== weak_ptr 打破循环引用 ===" << std::endl;
// 定义节点类
class Node {
public:
std::string name;
std::shared_ptr<Node> next; // 共享所有权
std::weak_ptr<Node> prev; // 弱引用,不增加引用计数

// 构造函数
Node(const std::string& n) : name(n) {
std::cout << "Node " << name << " created" << std::endl;
}

// 析构函数
~Node() {
std::cout << "Node " << name << " destroyed" << std::endl;
}
};

{
// 创建两个节点
auto node1 = std::make_shared<Node>("Node1");
auto node2 = std::make_shared<Node>("Node2");

// 建立双向链接
node1->next = node2;
node2->prev = node1;

// 查看引用计数
std::cout << "node1 use_count: " << node1.use_count() << std::endl;
std::cout << "node2 use_count: " << node2.use_count() << std::endl;
} // 离开作用域,两个节点都会被正确删除,因为使用了 weak_ptr 打破循环引用
std::cout << "End of node scope" << std::endl;

std::cout << "\n=== shared_ptr 与 std::vector ===" << std::endl;
{
// 创建存储 shared_ptr 的向量
std::vector<std::shared_ptr<Resource>> resources;
// 添加元素
resources.push_back(std::make_shared<Resource>("Resource1"));
resources.push_back(std::make_shared<Resource>("Resource2"));
resources.push_back(std::make_shared<Resource>("Resource3"));

// 遍历向量并使用元素
std::cout << "Resources in vector:" << std::endl;
for (const auto& res : resources) {
res->doSomething();
}
} // 离开作用域,向量中的所有 shared_ptr 被销毁,资源被删除
std::cout << "End of vector scope" << std::endl;

std::cout << "\n=== make_shared vs new(性能比较)===" << std::endl;
{
// 使用 make_shared 创建 shared_ptr(推荐)
auto ptr1 = std::make_shared<Resource>("MakeShared");
// 使用 new 创建 shared_ptr(不推荐)
std::shared_ptr<Resource> ptr2(new Resource("New"));
} // 离开作用域,两个资源都被删除

std::cout << "\n=== 智能指针与异常安全 ===" << std::endl;
{
try {
// 创建智能指针
std::unique_ptr<Resource> ptr = std::make_unique<Resource>("ExceptionSafe");
// 模拟异常
throw std::runtime_error("Test exception");
// 即使发生异常,ptr 离开作用域时也会自动删除资源
} catch (const std::exception& e) {
std::cout << "Caught exception: " << e.what() << std::endl;
std::cout << "Resource was properly deleted despite exception" << std::endl;
}
}

std::cout << "\n=== shared_ptr 的引用计数管理 ===" << std::endl;
{
// 创建 shared_ptr
auto shared1 = std::make_shared<Resource>("RefCount");
std::cout << "After creation: " << shared1.use_count() << std::endl;

// 复制到另一个 shared_ptr
auto shared2 = shared1;
std::cout << "After copy: " << shared1.use_count() << std::endl;

// 复制到向量
std::vector<std::shared_ptr<Resource>> vec;
vec.push_back(shared1);
std::cout << "After push_back: " << shared1.use_count() << std::endl;

// 从向量中移除
vec.pop_back();
std::cout << "After pop_back: " << shared1.use_count() << std::endl;

// 重置 shared2
shared2.reset();
std::cout << "After shared2.reset(): " << shared1.use_count() << std::endl;
} // shared1 离开作用域,引用计数为 0,资源被删除

std::cout << "\n=== 智能指针的空检查 ===" << std::endl;
{
// 创建空的 unique_ptr
std::unique_ptr<Resource> emptyPtr;
// 检查是否为空
if (!emptyPtr) {
std::cout << "emptyPtr is null" << std::endl;
}

// 创建非空的 unique_ptr
std::unique_ptr<Resource> nonEmptyPtr = std::make_unique<Resource>("NotNull");
// 检查是否为空
if (nonEmptyPtr) {
std::cout << "nonEmptyPtr is not null" << std::endl;
nonEmptyPtr->doSomething();
}
}

return 0;
}

C++ 文件 I/O(File Input/Output)详细指南

1. 功能说明

本指南基于 file_io.cpp 文件,详细介绍了 C++ 中文件输入/输出(I/O)的各种操作和技术,包括:

  • 文本文件操作:写入、读取、追加文本文件
  • 二进制文件操作:写入、读取二进制文件
  • 文件信息获取:获取文件大小、检查文件状态
  • 结构化数据操作:读写结构体等复杂数据
  • 字符串流操作:使用 stringstream 进行字符串处理
  • 异常处理:文件操作中的异常捕获和处理
  • 文件系统操作:使用 C++17 文件系统库进行文件管理

文件 I/O 是程序与外部存储设备交互的重要方式,掌握这些操作对于开发各种应用程序都非常重要。

2. 代码解析

2.1 写入文本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::cout << "=== 写入文本文件 ===" << std::endl;
// 创建输出文件流,打开 example.txt 文件
std::ofstream outFile("example.txt");

if (outFile.is_open()) {
// 写入各种类型的数据
outFile << "Hello, File I/O!" << std::endl;
outFile << "这是第2行。" << std::endl;
outFile << "这是第3行。" << std::endl;
outFile << "数字: " << 42 << std::endl;
outFile << "浮点数: " << 3.14 << std::endl;
// 关闭文件
outFile.close();
std::cout << "文件写入成功。" << std::endl;
} else {
std::cerr << "无法打开文件进行写入。" << std::endl;
return 1;
}

解析

  • std::ofstream:输出文件流类,用于写入文件
  • is_open():检查文件是否成功打开
  • 使用流操作符 << 写入各种类型的数据
  • close():关闭文件,释放资源
  • 错误处理:如果文件无法打开,输出错误信息并退出程序

2.2 逐行读取文本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::cout << "\n=== 逐行读取文本文件 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream inFile("example.txt");

if (inFile.is_open()) {
std::string line;
int lineNumber = 1;
// 逐行读取文件内容
while (std::getline(inFile, line)) {
std::cout << "第 " << lineNumber << " 行: " << line << std::endl;
lineNumber++;
}
// 关闭文件
inFile.close();
} else {
std::cerr << "无法打开文件进行读取。" << std::endl;
return 1;
}

解析

  • std::ifstream:输入文件流类,用于读取文件
  • std::getline():逐行读取文件内容,将结果存储在字符串中
  • 循环读取直到文件结束
  • 关闭文件,释放资源

2.3 逐词读取文本文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::cout << "\n=== 逐词读取文本文件 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream inFile2("example.txt");

if (inFile2.is_open()) {
std::string word;
// 逐词读取文件内容(默认以空格分隔)
while (inFile2 >> word) {
std::cout << word << std::endl;
}
// 关闭文件
inFile2.close();
} else {
std::cerr << "无法打开文件进行读取。" << std::endl;
return 1;
}

解析

  • 使用流操作符 >> 逐词读取文件内容,默认以空格分隔
  • 循环读取直到文件结束
  • 关闭文件,释放资源

2.4 写入二进制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::cout << "\n=== 写入二进制文件 ===" << std::endl;
// 创建二进制输出文件流,打开 data.bin 文件
std::ofstream binOutFile("data.bin", std::ios::binary);

if (binOutFile.is_open()) {
// 准备要写入的数组
int numbers[] = {10, 20, 30, 40, 50};
// 写入二进制数据
binOutFile.write(reinterpret_cast<char*>(numbers), sizeof(numbers));
// 关闭文件
binOutFile.close();
std::cout << "二进制文件写入成功。" << std::endl;
} else {
std::cerr << "无法打开二进制文件进行写入。" << std::endl;
return 1;
}

解析

  • std::ios::binary:以二进制模式打开文件
  • write():写入二进制数据
  • reinterpret_cast<char*>:将整数数组指针转换为字符指针
  • sizeof(numbers):计算要写入的数据大小(字节)

2.5 读取二进制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
std::cout << "\n=== 读取二进制文件 ===" << std::endl;
// 创建二进制输入文件流,打开 data.bin 文件
std::ifstream binInFile("data.bin", std::ios::binary);

if (binInFile.is_open()) {
// 准备存储读取数据的数组
int readNumbers[5];
// 读取二进制数据
binInFile.read(reinterpret_cast<char*>(readNumbers), sizeof(readNumbers));

std::cout << "读取的数字: ";
for (int i = 0; i < 5; ++i) {
std::cout << readNumbers[i] << " ";
}
std::cout << std::endl;
// 关闭文件
binInFile.close();
} else {
std::cerr << "无法打开二进制文件进行读取。" << std::endl;
return 1;
}

解析

  • std::ios::binary:以二进制模式打开文件
  • read():读取二进制数据
  • reinterpret_cast<char*>:将整数数组指针转换为字符指针
  • sizeof(readNumbers):计算要读取的数据大小(字节)

2.6 追加文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::cout << "\n=== 追加文件内容 ===" << std::endl;
// 创建输出文件流,以追加模式打开 example.txt 文件
std::ofstream appendFile("example.txt", std::ios::app);

if (appendFile.is_open()) {
// 追加内容
appendFile << "这行是追加的。" << std::endl;
appendFile << "另一行追加的内容。" << std::endl;
// 关闭文件
appendFile.close();
std::cout << "内容追加成功。" << std::endl;
} else {
std::cerr << "无法打开文件进行追加。" << std::endl;
return 1;
}

解析

  • std::ios::app:以追加模式打开文件,新写入的数据会添加到文件末尾
  • 使用流操作符 << 追加内容
  • 关闭文件,释放资源

2.7 带错误检查的文件读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
std::cout << "\n=== 带错误检查的文件读取 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream checkFile("example.txt");

if (checkFile.is_open()) {
std::string line;
// 检查文件是否可读
while (checkFile.good()) {
std::getline(checkFile, line);
if (!line.empty()) {
std::cout << line << std::endl;
}
}
// 检查是否到达文件末尾
if (checkFile.eof()) {
std::cout << "已到达文件末尾。" << std::endl;
} else if (checkFile.fail()) {
std::cout << "文件读取失败。" << std::endl;
}
// 关闭文件
checkFile.close();
}

解析

  • good():检查流是否处于良好状态(可读写且无错误)
  • eof():检查是否到达文件末尾
  • fail():检查是否发生非致命错误(如类型不匹配)
  • 错误处理:根据不同的错误状态输出相应的信息

2.8 获取文件信息

1
2
3
4
5
6
7
8
9
10
11
12
13
std::cout << "\n=== 获取文件信息 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream infoFile("example.txt");

if (infoFile.is_open()) {
// 定位到文件末尾
infoFile.seekg(0, std::ios::end);
// 获取当前位置(即文件大小)
std::streampos fileSize = infoFile.tellg();
std::cout << "文件大小: " << fileSize << " 字节" << std::endl;
// 关闭文件
infoFile.close();
}

解析

  • seekg():设置输入流的位置指针
  • std::ios::end:文件末尾位置
  • tellg():获取输入流的当前位置指针
  • 文件大小等于文件末尾的位置值

2.9 读写结构化数据

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
std::cout << "\n=== 读写结构化数据 ===" << std::endl;

// 定义结构体
struct Person {
std::string name;
int age;
double salary;
};

// 创建 Person 结构体向量
std::vector<Person> people = {
{"Alice", 30, 50000.0},
{"Bob", 25, 45000.0},
{"Charlie", 35, 60000.0}
};

// 写入结构化数据到文件
std::ofstream peopleOut("people.txt");
if (peopleOut.is_open()) {
for (const auto& person : people) {
peopleOut << person.name << " " << person.age << " " << person.salary << std::endl;
}
peopleOut.close();
std::cout << "人员数据写入文件成功。" << std::endl;
}

// 从文件读取结构化数据
std::vector<Person> readPeople;
std::ifstream peopleIn("people.txt");
if (peopleIn.is_open()) {
std::string name;
int age;
double salary;
while (peopleIn >> name >> age >> salary) {
readPeople.push_back({name, age, salary});
}
peopleIn.close();

std::cout << "读取的人员数据:" << std::endl;
for (const auto& person : readPeople) {
std::cout << "姓名: " << person.name
<< ", 年龄: " << person.age
<< ", 工资: " << person.salary << std::endl;
}
}

解析

  • 定义 Person 结构体,包含姓名、年龄和工资字段
  • 创建 Person 结构体向量并初始化
  • 写入结构化数据:将每个结构体的字段按顺序写入文件
  • 读取结构化数据:按相同的顺序从文件读取字段,创建新的结构体并添加到向量中

2.10 使用 stringstream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
std::cout << "\n=== 使用 stringstream ===" << std::endl;
#include <sstream>
// 创建输出 stringstream
std::stringstream ss;
// 向 stringstream 写入数据
ss << "Hello " << 42 << " " << 3.14;
// 获取 stringstream 中的字符串
std::string str = ss.str();
std::cout << "String stream 内容: " << str << std::endl;

// 创建输入 stringstream
std::stringstream ss2("10 20 30");
int a, b, c;
// 从 stringstream 读取数据
ss2 >> a >> b >> c;
std::cout << "解析的值: " << a << ", " << b << ", " << c << std::endl;

解析

  • std::stringstream:字符串流类,用于在内存中进行字符串的输入/输出操作
  • 输出操作:使用流操作符 << 向 stringstream 写入数据
  • str():获取 stringstream 中的字符串
  • 输入操作:使用流操作符 >> 从 stringstream 读取数据,自动进行类型转换

2.11 文件操作异常处理

1
2
3
4
5
6
7
8
9
10
11
std::cout << "\n=== 文件操作异常处理 ===" << std::endl;
try {
// 尝试打开一个不存在的文件进行读取
std::ifstream nonExistentFile("non_existent_file.txt");
if (!nonExistentFile.is_open()) {
throw std::runtime_error("无法打开文件:non_existent_file.txt");
}
nonExistentFile.close();
} catch (const std::exception& e) {
std::cerr << "异常: " << e.what() << std::endl;
}

解析

  • 使用 try-catch 块捕获文件操作中的异常
  • 当无法打开文件时,抛出 std::runtime_error 异常
  • 在 catch 块中捕获并处理异常,输出错误信息

2.12 使用文件系统库(C++17)

1
2
3
4
5
6
7
8
9
std::cout << "\n=== 使用文件系统库(C++17) ===" << std::endl;
// 检查文件是否存在
if (std::filesystem::exists("example.txt")) {
std::cout << "文件 example.txt 存在。" << std::endl;
// 获取文件大小
std::cout << "文件大小: " << std::filesystem::file_size("example.txt") << " 字节" << std::endl;
} else {
std::cout << "文件 example.txt 不存在。" << std::endl;
}

解析

  • std::filesystem:C++17 引入的文件系统库
  • exists():检查文件是否存在
  • file_size():获取文件大小(字节)
  • 注意:需要包含 <filesystem> 头文件,并且编译器需要支持 C++17 标准

3. 编译和运行说明

3.1 编译命令

使用以下命令编译 file_io.cpp 文件:

1
g++ -std=c++17 -fexec-charset=GBK -o file_io file_io.cpp

参数说明:

  • -std=c++17:使用 C++17 标准(因为使用了 std::filesystem 库)
  • -fexec-charset=GBK:确保输出的中文字符正确显示
  • -o file_io:指定输出可执行文件名为 file_io

3.2 运行命令

编译成功后,使用以下命令运行程序:

1
2
./file_io  # Linux/macOS
.\file_io.exe # Windows

3.3 预期输出

运行程序后,您将看到类似以下的输出:

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
=== 写入文本文件 ===
文件写入成功。

=== 逐行读取文本文件 ===
第 1 行: Hello, File I/O!
第 2 行: 这是第2行。
第 3 行: 这是第3行。
第 4 行: 数字: 42
第 5 行: 浮点数: 3.14

=== 逐词读取文本文件 ===
Hello,
File
I/O!
这是第2行。
这是第3行。
数字:
42
浮点数:
3.14

=== 写入二进制文件 ===
二进制文件写入成功。

=== 读取二进制文件 ===
读取的数字: 10 20 30 40 50

=== 追加文件内容 ===
内容追加成功。

=== 带错误检查的文件读取 ===
Hello, File I/O!
这是第2行。
这是第3行。
数字: 42
浮点数: 3.14
这行是追加的。
另一行追加的内容。
已到达文件末尾。

=== 获取文件信息 ===
文件大小: 104 字节
=== 读写结构化数据 ===
人员数据写入文件成功。
读取的人员数据:
姓名: Alice, 年龄: 30, 工资: 50000
姓名: Bob, 年龄: 25, 工资: 45000
姓名: Charlie, 年龄: 35, 工资: 60000

=== 使用 stringstream ===
String stream 内容: Hello 42 3.14
解析的值: 10, 20, 30

=== 文件操作异常处理 ===
异常: 无法打开文件:non_existent_file.txt

=== 使用文件系统库(C++17) ===
文件 example.txt 存在。
文件大小: 104 字节

4. 技术要点

4.1 文件流类

类名 描述 头文件
std::ifstream 输入文件流,用于读取文件 <fstream>
std::ofstream 输出文件流,用于写入文件 <fstream>
std::fstream 双向文件流,用于读写文件 <fstream>
std::stringstream 字符串流,用于内存中的字符串操作 <sstream>

4.2 文件打开模式

模式标志 描述
std::ios::in 以输入模式打开文件(默认用于 ifstream
std::ios::out 以输出模式打开文件(默认用于 ofstream
std::ios::app 以追加模式打开文件,新数据添加到文件末尾
std::ios::ate 打开文件并定位到文件末尾
std::ios::trunc 打开文件并截断文件长度为 0(默认用于 ofstream
std::ios::binary 以二进制模式打开文件
std::ios::nocreate 仅当文件存在时才打开(已弃用,建议使用文件系统库)
std::ios::noreplace 仅当文件不存在时才打开(已弃用,建议使用文件系统库)

4.3 流状态标志

状态标志 描述 检查函数
goodbit 流状态良好,无错误 good()
eofbit 已到达文件末尾 eof()
failbit 发生非致命错误(如类型不匹配) fail()
badbit 发生致命错误(如文件损坏) bad()
rdstate() 获取当前流状态 rdstate()
clear() 清除流状态标志 clear()

4.4 文件定位函数

函数 描述
seekg() 设置输入流的位置指针
seekp() 设置输出流的位置指针
tellg() 获取输入流的当前位置
tellp() 获取输出流的当前位置

4.5 二进制文件操作

  • 二进制模式:使用 std::ios::binary 打开文件
  • 写入操作:使用 write() 函数,需要指定数据指针和大小
  • 读取操作:使用 read() 函数,需要指定缓冲区指针和大小
  • 类型转换:使用 reinterpret_cast<char*> 进行指针类型转换

4.6 字符串流操作

  • 创建std::stringstream ss;
  • 写入ss << "data";
  • 读取ss >> variable;
  • 获取字符串std::string str = ss.str();
  • 设置字符串ss.str("new data");

4.7 文件系统库(C++17)

函数 描述
std::filesystem::exists() 检查文件或目录是否存在
std::filesystem::file_size() 获取文件大小
std::filesystem::is_regular_file() 检查是否为普通文件
std::filesystem::is_directory() 检查是否为目录
std::filesystem::create_directory() 创建目录
std::filesystem::remove() 删除文件或目录
std::filesystem::rename() 重命名文件或目录
std::filesystem::copy() 复制文件或目录

5. 常见问题解答

5.1 如何处理文件路径?

解答

  • 相对路径:相对于当前工作目录的路径,如 "example.txt"
  • 绝对路径:从文件系统根目录开始的完整路径,如 "C:\\Users\\example.txt"(Windows)或 "/home/user/example.txt"(Linux)
  • 路径分隔符:Windows 使用 \,Linux/macOS 使用 /,建议使用 std::filesystem::path 来处理跨平台路径

5.2 如何确保文件正确关闭?

解答

  • 显式关闭:使用 close() 函数关闭文件
  • RAII 原则:利用文件流对象的析构函数自动关闭文件,当对象超出作用域时会自动调用析构函数关闭文件
  • 推荐做法:使用局部作用域的文件流对象,或使用智能指针管理文件流对象

5.3 如何处理大文件?

解答

  • 分块读取:对于大文件,使用分块读取的方式,避免一次性加载整个文件到内存
  • 二进制模式:对于二进制大文件,使用二进制模式进行读写,提高效率
  • 内存映射:对于需要随机访问的大文件,考虑使用内存映射(memory-mapped files)技术

5.4 如何处理文件编码?

解答

  • ASCII 编码:适用于纯英文文本,是最基本的编码方式
  • UTF-8 编码:适用于包含多种语言的文本,是互联网上最常用的编码方式
  • GBK 编码:适用于中文文本,在 Windows 系统中较为常用
  • 编码转换:使用专门的库(如 ICU)进行不同编码之间的转换

5.5 如何提高文件 I/O 性能?

解答

  • 使用二进制模式:对于非文本数据,使用二进制模式可以提高读写速度
  • 缓冲区大小:调整文件流的缓冲区大小,如 stream.rdbuf()->pubsetbuf(buffer, size);
  • 批量操作:减少读写操作的次数,尽量进行批量读写
  • 异步 I/O:对于需要同时处理多个文件的场景,考虑使用异步 I/O 操作
  • 内存映射:对于需要随机访问的大文件,使用内存映射技术

5.6 如何处理文件锁?

解答

  • 标准库:C++ 标准库没有直接提供文件锁机制
  • 平台特定
    • Windows:使用 LockFileUnlockFile 等函数
    • Linux/macOS:使用 flockfcntl 等函数
  • 第三方库:使用 Boost.Interprocess 等跨平台库提供的文件锁机制

5.7 如何使用异常处理文件操作?

解答

  • 启用异常:通过 stream.exceptions(std::ios::failbit | std::ios::badbit); 启用异常
  • 捕获异常:使用 try-catch 块捕获文件操作中的异常
  • 自定义异常:对于特定的文件操作错误,可以定义自定义异常类

6. 代码优化建议

6.1 文件流管理

  • 使用 RAII 原则:利用文件流对象的析构函数自动关闭文件

    1
    2
    3
    4
    5
    // 推荐做法
    {
    std::ifstream file("example.txt");
    // 使用文件
    } // 文件自动关闭
  • 避免重复打开关闭:尽量减少文件的打开和关闭次数,特别是在循环中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 不推荐
    for (int i = 0; i < 10; ++i) {
    std::ofstream file("example.txt", std::ios::app);
    file << i << std::endl;
    file.close();
    }

    // 推荐
    std::ofstream file("example.txt", std::ios::app);
    for (int i = 0; i < 10; ++i) {
    file << i << std::endl;
    }
    // 文件自动关闭

6.2 错误处理

  • 全面的错误检查:检查文件操作的每一步,不仅仅是文件打开

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    std::ifstream file("example.txt");
    if (!file) {
    std::cerr << "无法打开文件" << std::endl;
    return 1;
    }

    std::string line;
    if (!std::getline(file, line)) {
    std::cerr << "读取文件失败" << std::endl;
    return 1;
    }
  • 使用异常处理:对于关键的文件操作,使用异常处理机制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    try {
    std::ifstream file("example.txt");
    if (!file) {
    throw std::runtime_error("无法打开文件");
    }
    // 其他操作
    } catch (const std::exception& e) {
    std::cerr << "错误: " << e.what() << std::endl;
    }

6.3 性能优化

  • 使用二进制模式:对于非文本数据,使用二进制模式可以提高读写速度

    1
    2
    // 对于二进制数据
    std::ofstream file("data.bin", std::ios::binary);
  • 调整缓冲区大小:根据文件大小和操作类型,调整文件流的缓冲区大小

    1
    2
    3
    char buffer[4096];
    std::ifstream file("example.txt");
    file.rdbuf()->pubsetbuf(buffer, sizeof(buffer));
  • 批量读写:对于大块数据,使用 read()write() 函数进行批量读写

    1
    2
    3
    4
    5
    6
    // 批量读取
    char buffer[4096];
    std::ifstream file("example.txt", std::ios::binary);
    while (file.read(buffer, sizeof(buffer))) {
    // 处理数据
    }

6.4 代码可读性

  • 使用命名空间别名:对于频繁使用的命名空间,使用别名简化代码

    1
    2
    3
    4
    namespace fs = std::filesystem;
    if (fs::exists("example.txt")) {
    // 操作
    }
  • 使用结构化绑定:C++17 中可以使用结构化绑定简化代码

    1
    2
    3
    4
    5
    6
    7
    8
    // 读取多个值
    std::string name;
    int age;
    double salary;
    file >> name >> age >> salary;

    // C++17 结构化绑定
    auto [name, age, salary] = readPerson(file);
  • 使用辅助函数:将重复的文件操作封装为辅助函数

    1
    2
    3
    4
    5
    6
    bool readFile(const std::string& filename, std::string& content) {
    std::ifstream file(filename);
    if (!file) return false;
    content.assign(std::istreambuf_iterator<char>(file), {});
    return true;
    }

7. 代码优化示例

7.1 使用 RAII 原则管理文件流

优化前

1
2
3
4
5
6
7
8
std::ofstream file;
file.open("example.txt");
if (file.is_open()) {
file << "Hello World" << std::endl;
file.close();
} else {
std::cerr << "无法打开文件" << std::endl;
}

优化后

1
2
3
4
5
6
7
8
9
{
std::ofstream file("example.txt");
if (file) {
file << "Hello World" << std::endl;
} else {
std::cerr << "无法打开文件" << std::endl;
}
// 文件自动关闭
}

7.2 使用文件系统库检查文件存在性

优化前

1
2
3
4
5
6
7
std::ifstream file("example.txt");
if (file.is_open()) {
std::cout << "文件存在" << std::endl;
file.close();
} else {
std::cout << "文件不存在" << std::endl;
}

优化后

1
2
3
4
5
if (std::filesystem::exists("example.txt")) {
std::cout << "文件存在" << std::endl;
} else {
std::cout << "文件不存在" << std::endl;
}

7.3 使用 stringstream 进行字符串格式化

优化前

1
2
3
std::string name = "Alice";
int age = 30;
std::string message = "姓名: " + name + ", 年龄: " + std::to_string(age);

优化后

1
2
3
4
5
std::string name = "Alice";
int age = 30;
std::stringstream ss;
ss << "姓名: " << name << ", 年龄: " << age;
std::string message = ss.str();

7.4 批量读取文件内容

优化前

1
2
3
4
5
6
std::ifstream file("example.txt");
std::string line;
std::vector<std::string> lines;
while (std::getline(file, line)) {
lines.push_back(line);
}

优化后

1
2
3
4
5
6
7
std::ifstream file("example.txt");
std::vector<std::string> lines;
std::string line;
lines.reserve(1000); // 预分配空间
while (std::getline(file, line)) {
lines.push_back(std::move(line)); // 使用移动语义
}

7.5 使用异常处理文件操作

优化前

1
2
3
4
5
6
7
8
9
10
11
std::ifstream file("example.txt");
if (!file) {
std::cerr << "无法打开文件" << std::endl;
return 1;
}

std::string line;
if (!std::getline(file, line)) {
std::cerr << "读取文件失败" << std::endl;
return 1;
}

优化后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try {
std::ifstream file("example.txt");
if (!file) {
throw std::runtime_error("无法打开文件");
}

std::string line;
if (!std::getline(file, line)) {
throw std::runtime_error("读取文件失败");
}
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
return 1;
}

8. 总结

C++ 文件 I/O 是程序与外部存储设备交互的重要方式,掌握各种文件操作技术对于开发各种应用程序都非常重要。本指南详细介绍了 C++ 中文件 I/O 的各种操作和技术,包括文本文件操作、二进制文件操作、文件信息获取、结构化数据操作、字符串流操作、异常处理和文件系统操作等。

8.1 核心要点

  • 文件流类ifstreamofstreamfstreamstringstream 是处理文件 I/O 的核心类
  • 文件打开模式:根据操作类型选择合适的文件打开模式
  • 流状态管理:检查和处理流状态,确保文件操作的正确性
  • 错误处理:使用条件判断或异常处理机制处理文件操作中的错误
  • 性能优化:根据文件类型和操作需求,选择合适的操作方式和优化策略
  • 现代 C++ 特性:利用 C++17 中的文件系统库等现代特性,简化文件操作代码

8.2 最佳实践

  • 使用 RAII 原则:利用对象的生命周期管理文件资源
  • 错误处理:全面检查文件操作的每一步,及时处理错误
  • 性能优化:根据实际需求选择合适的文件操作方式和优化策略
  • 代码可读性:使用命名空间别名、结构化绑定和辅助函数等提高代码可读性
  • 跨平台兼容:使用 std::filesystem 等跨平台库,确保代码在不同平台上的兼容性

8.3 应用场景

  • 配置文件读写:读取和写入程序配置信息
  • 数据持久化:将程序运行数据保存到文件,或从文件加载数据
  • 日志记录:将程序运行日志写入文件
  • 文件格式转换:在不同文件格式之间进行转换
  • 大文件处理:处理大型数据文件,如视频、音频、图像等
  • 网络文件传输:通过网络发送和接收文件数据

通过掌握本指南介绍的文件 I/O 技术,您可以更加高效、可靠地处理各种文件操作需求,为开发高质量的 C++ 应用程序打下坚实的基础。

file_io.cpp

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <filesystem>

int main() {
std::cout << "=== 写入文本文件 ===" << std::endl;
// 创建输出文件流,打开 example.txt 文件
std::ofstream outFile("example.txt");

if (outFile.is_open()) {
// 写入各种类型的数据
outFile << "Hello, File I/O!" << std::endl;
outFile << "这是第2行。" << std::endl;
outFile << "这是第3行。" << std::endl;
outFile << "数字: " << 42 << std::endl;
outFile << "浮点数: " << 3.14 << std::endl;
// 关闭文件
outFile.close();
std::cout << "文件写入成功。" << std::endl;
} else {
std::cerr << "无法打开文件进行写入。" << std::endl;
return 1;
}

std::cout << "\n=== 逐行读取文本文件 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream inFile("example.txt");

if (inFile.is_open()) {
std::string line;
int lineNumber = 1;
// 逐行读取文件内容
while (std::getline(inFile, line)) {
std::cout << "第 " << lineNumber << " 行: " << line << std::endl;
lineNumber++;
}
// 关闭文件
inFile.close();
} else {
std::cerr << "无法打开文件进行读取。" << std::endl;
return 1;
}

std::cout << "\n=== 逐词读取文本文件 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream inFile2("example.txt");

if (inFile2.is_open()) {
std::string word;
// 逐词读取文件内容(默认以空格分隔)
while (inFile2 >> word) {
std::cout << word << std::endl;
}
// 关闭文件
inFile2.close();
} else {
std::cerr << "无法打开文件进行读取。" << std::endl;
return 1;
}

std::cout << "\n=== 写入二进制文件 ===" << std::endl;
// 创建二进制输出文件流,打开 data.bin 文件
std::ofstream binOutFile("data.bin", std::ios::binary);

if (binOutFile.is_open()) {
// 准备要写入的数组
int numbers[] = {10, 20, 30, 40, 50};
// 写入二进制数据
binOutFile.write(reinterpret_cast<char*>(numbers), sizeof(numbers));
// 关闭文件
binOutFile.close();
std::cout << "二进制文件写入成功。" << std::endl;
} else {
std::cerr << "无法打开二进制文件进行写入。" << std::endl;
return 1;
}

std::cout << "\n=== 读取二进制文件 ===" << std::endl;
// 创建二进制输入文件流,打开 data.bin 文件
std::ifstream binInFile("data.bin", std::ios::binary);

if (binInFile.is_open()) {
// 准备存储读取数据的数组
int readNumbers[5];
// 读取二进制数据
binInFile.read(reinterpret_cast<char*>(readNumbers), sizeof(readNumbers));

std::cout << "读取的数字: ";
for (int i = 0; i < 5; ++i) {
std::cout << readNumbers[i] << " ";
}
std::cout << std::endl;
// 关闭文件
binInFile.close();
} else {
std::cerr << "无法打开二进制文件进行读取。" << std::endl;
return 1;
}

std::cout << "\n=== 追加文件内容 ===" << std::endl;
// 创建输出文件流,以追加模式打开 example.txt 文件
std::ofstream appendFile("example.txt", std::ios::app);

if (appendFile.is_open()) {
// 追加内容
appendFile << "这行是追加的。" << std::endl;
appendFile << "另一行追加的内容。" << std::endl;
// 关闭文件
appendFile.close();
std::cout << "内容追加成功。" << std::endl;
} else {
std::cerr << "无法打开文件进行追加。" << std::endl;
return 1;
}

std::cout << "\n=== 带错误检查的文件读取 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream checkFile("example.txt");

if (checkFile.is_open()) {
std::string line;
// 检查文件是否可读
while (checkFile.good()) {
std::getline(checkFile, line);
if (!line.empty()) {
std::cout << line << std::endl;
}
}
// 检查是否到达文件末尾
if (checkFile.eof()) {
std::cout << "已到达文件末尾。" << std::endl;
} else if (checkFile.fail()) {
std::cout << "文件读取失败。" << std::endl;
}
// 关闭文件
checkFile.close();
}

std::cout << "\n=== 获取文件信息 ===" << std::endl;
// 创建输入文件流,打开 example.txt 文件
std::ifstream infoFile("example.txt");

if (infoFile.is_open()) {
// 定位到文件末尾
infoFile.seekg(0, std::ios::end);
// 获取当前位置(即文件大小)
std::streampos fileSize = infoFile.tellg();
std::cout << "文件大小: " << fileSize << " 字节" << std::endl;
// 关闭文件
infoFile.close();
}

std::cout << "\n=== 读写结构化数据 ===" << std::endl;

// 定义结构体
struct Person {
std::string name;
int age;
double salary;
};

// 创建 Person 结构体向量
std::vector<Person> people = {
{"Alice", 30, 50000.0},
{"Bob", 25, 45000.0},
{"Charlie", 35, 60000.0}
};

// 写入结构化数据到文件
std::ofstream peopleOut("people.txt");
if (peopleOut.is_open()) {
for (const auto& person : people) {
peopleOut << person.name << " " << person.age << " " << person.salary << std::endl;
}
peopleOut.close();
std::cout << "人员数据写入文件成功。" << std::endl;
}

// 从文件读取结构化数据
std::vector<Person> readPeople;
std::ifstream peopleIn("people.txt");
if (peopleIn.is_open()) {
std::string name;
int age;
double salary;
while (peopleIn >> name >> age >> salary) {
readPeople.push_back({name, age, salary});
}
peopleIn.close();

std::cout << "读取的人员数据:" << std::endl;
for (const auto& person : readPeople) {
std::cout << "姓名: " << person.name
<< ", 年龄: " << person.age
<< ", 工资: " << person.salary << std::endl;
}
}

std::cout << "\n=== 使用 stringstream ===" << std::endl;
#include <sstream>
// 创建输出 stringstream
std::stringstream ss;
// 向 stringstream 写入数据
ss << "Hello " << 42 << " " << 3.14;
// 获取 stringstream 中的字符串
std::string str = ss.str();
std::cout << "String stream 内容: " << str << std::endl;

// 创建输入 stringstream
std::stringstream ss2("10 20 30");
int a, b, c;
// 从 stringstream 读取数据
ss2 >> a >> b >> c;
std::cout << "解析的值: " << a << ", " << b << ", " << c << std::endl;

std::cout << "\n=== 文件操作异常处理 ===" << std::endl;
try {
// 尝试打开一个不存在的文件进行读取
std::ifstream nonExistentFile("non_existent_file.txt");
if (!nonExistentFile.is_open()) {
throw std::runtime_error("无法打开文件:non_existent_file.txt");
}
nonExistentFile.close();
} catch (const std::exception& e) {
std::cerr << "异常: " << e.what() << std::endl;
}

std::cout << "\n=== 使用文件系统库(C++17) ===" << std::endl;
// 检查文件是否存在
if (std::filesystem::exists("example.txt")) {
std::cout << "文件 example.txt 存在。" << std::endl;
// 获取文件大小
std::cout << "文件大小: " << std::filesystem::file_size("example.txt") << " 字节" << std::endl;
} else {
std::cout << "文件 example.txt 不存在。" << std::endl;
}

return 0;
}

C++ STL 算法(Algorithms)详细指南

1. 功能说明

本指南基于 stl_algorithms.cpp 文件,详细介绍了 C++ 标准模板库(STL)中各种算法的使用方法和特性,包括:

  • 排序算法:sort、partial_sort、stable_sort 等
  • 查找算法:find、binary_search、lower_bound、upper_bound 等
  • 修改算法:copy、fill、iota、reverse、random_shuffle 等
  • 数值算法:accumulate、adjacent_difference 等
  • 最值算法:minmax_element、min、max 等
  • 计数算法:count、count_if 等
  • 转换算法:transform 等
  • 遍历算法:for_each 等
  • 删除算法:remove、remove_if 等
  • 去重算法:unique 等
  • 合并算法:merge 等
  • 分区算法:partition 等
  • 逻辑算法:all_of、any_of、none_of 等
  • 相等性算法:equal 等
  • 包含算法:search 等

STL 算法是一组独立于容器的通用函数,它们通过迭代器操作容器中的元素,提供了丰富的操作功能,可以大大简化代码编写,提高开发效率。

2. 代码解析

2.1 排序算法

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
// 初始化一个整数向量
std::vector<int> nums = {5, 2, 8, 1, 9, 3, 7, 4, 6};

std::cout << "原始向量: ";
for (int x : nums) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 排序算法 ===" << std::endl;
// 升序排序
std::sort(nums.begin(), nums.end());
std::cout << "升序排序后: ";
for (int x : nums) {
std::cout << x << " ";
}
std::cout << std::endl;

// 降序排序
std::sort(nums.begin(), nums.end(), std::greater<int>());
std::cout << "降序排序后: ";
for (int x : nums) {
std::cout << x << " ";
}
std::cout << std::endl;

// 部分排序(前3个元素排序)
std::partial_sort(nums.begin(), nums.begin() + 3, nums.end());
std::cout << "部分排序(前3个元素): ";
for (int x : nums) {
std::cout << x << " ";
}
std::cout << std::endl;

解析

  • std::sort:对指定范围内的元素进行排序,默认是升序排序
  • 可以通过传入自定义比较函数来改变排序方式,如使用 std::greater<int>() 进行降序排序
  • std::partial_sort:对指定范围内的元素进行部分排序,只保证前 n 个元素是有序的
  • 排序算法的时间复杂度通常为 O(n log n)

2.2 查找算法

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
std::cout << "\n=== 查找算法 ===" << std::endl;
// 先排序以便使用二分查找
std::sort(nums.begin(), nums.end());
// 线性查找
auto it = std::find(nums.begin(), nums.end(), 5);
if (it != nums.end()) {
std::cout << "找到5,位置: " << (it - nums.begin()) << std::endl;
}

// 二分查找
bool exists = std::binary_search(nums.begin(), nums.end(), 7);
std::cout << "7 存在: " << (exists ? "是" : "否") << std::endl;

// 下界和上界
auto lower = std::lower_bound(nums.begin(), nums.end(), 5);
auto upper = std::upper_bound(nums.begin(), nums.end(), 5);
std::cout << "5的下界位置: " << (lower - nums.begin()) << std::endl;
std::cout << "5的上界位置: " << (upper - nums.begin()) << std::endl;

// 查找第一个满足条件的元素
auto firstEven = std::find_if(nums.begin(), nums.end(),
[](int x) { return x % 2 == 0; });
if (firstEven != nums.end()) {
std::cout << "第一个偶数: " << *firstEven << ",位置: " << (firstEven - nums.begin()) << std::endl;
}

解析

  • std::find:线性查找指定值,返回第一个匹配元素的迭代器
  • std::binary_search:二分查找指定值,返回是否存在(要求容器已排序)
  • std::lower_bound:返回第一个不小于指定值的元素的迭代器(要求容器已排序)
  • std::upper_bound:返回第一个大于指定值的元素的迭代器(要求容器已排序)
  • std::find_if:查找第一个满足条件的元素,通过谓词函数指定条件

2.3 修改算法

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
std::cout << "\n=== 修改算法 ===" << std::endl;
std::vector<int> vec1 = {1, 2, 3, 4, 5};
std::vector<int> vec2(5);

// 复制元素
std::copy(vec1.begin(), vec1.end(), vec2.begin());
std::cout << "复制后的向量: ";
for (int x : vec2) {
std::cout << x << " ";
}
std::cout << std::endl;

// 填充元素
std::fill(vec2.begin(), vec2.end(), 0);
std::cout << "填充0后: ";
for (int x : vec2) {
std::cout << x << " ";
}
std::cout << std::endl;

// 生成递增序列
std::iota(vec2.begin(), vec2.end(), 1);
std::cout << "生成递增序列(1,2,3,4,5): ";
for (int x : vec2) {
std::cout << x << " ";
}
std::cout << std::endl;

// 反转元素
std::vector<int> vec3 = {1, 2, 3, 4, 5};
std::reverse(vec3.begin(), vec3.end());
std::cout << "反转后: ";
for (int x : vec3) {
std::cout << x << " ";
}
std::cout << std::endl;

// 随机打乱元素(C++11后推荐使用shuffle)
std::random_shuffle(vec3.begin(), vec3.end());
std::cout << "随机打乱后: ";
for (int x : vec3) {
std::cout << x << " ";
}
std::cout << std::endl;

解析

  • std::copy:将一个范围的元素复制到另一个范围
  • std::fill:将指定范围内的所有元素设置为给定值
  • std::iota:生成从给定值开始的递增序列
  • std::reverse:反转指定范围内的元素顺序
  • std::random_shuffle:随机打乱指定范围内的元素顺序(C++11后推荐使用 std::shuffle

2.4 数值算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::cout << "\n=== 数值算法 ===" << std::endl;
std::vector<int> values = {1, 2, 3, 4, 5};
// 计算总和
int sum = std::accumulate(values.begin(), values.end(), 0);
std::cout << "总和: " << sum << std::endl;

// 计算乘积
int product = std::accumulate(values.begin(), values.end(), 1, std::multiplies<int>());
std::cout << "乘积: " << product << std::endl;

// 计算相邻元素的差
std::vector<int> diff(values.size() - 1);
std::adjacent_difference(values.begin(), values.end(), diff.begin());
std::cout << "相邻元素的差: ";
for (int x : diff) {
std::cout << x << " ";
}
std::cout << std::endl;

解析

  • std::accumulate:计算指定范围内元素的累加和,也可以通过自定义操作计算其他聚合值(如乘积)
  • std::adjacent_difference:计算指定范围内相邻元素的差值

2.5 最值算法

1
2
3
4
5
6
7
8
9
std::cout << "\n=== 最值算法 ===" << std::endl;
// 查找最小值和最大值
auto minMax = std::minmax_element(values.begin(), values.end());
std::cout << "最小值: " << *minMax.first << ", 最大值: " << *minMax.second << std::endl;

// 直接比较两个值
int a = 10, b = 20;
std::cout << "a和b的最小值: " << std::min(a, b) << std::endl;
std::cout << "a和b的最大值: " << std::max(a, b) << std::endl;

解析

  • std::minmax_element:查找指定范围内的最小值和最大值,返回一个 pair 迭代器
  • std::min:返回两个值中的较小值
  • std::max:返回两个值中的较大值

2.6 计数算法

1
2
3
4
5
6
7
8
9
10
std::cout << "\n=== 计数算法 ===" << std::endl;
std::vector<int> countVec = {1, 2, 1, 3, 1, 4, 1};
// 计数元素出现次数
int count = std::count(countVec.begin(), countVec.end(), 1);
std::cout << "1出现的次数: " << count << std::endl;

// 计数满足条件的元素
int evenCount = std::count_if(countVec.begin(), countVec.end(),
[](int x) { return x % 2 == 0; });
std::cout << "偶数的个数: " << evenCount << std::endl;

解析

  • std::count:计算指定值在范围内出现的次数
  • std::count_if:计算满足条件的元素在范围内出现的次数

2.7 转换算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
std::cout << "\n=== 转换算法 ===" << std::endl;
std::vector<int> original = {1, 2, 3, 4, 5};
std::vector<int> squared(5);
// 转换元素(计算平方)
std::transform(original.begin(), original.end(), squared.begin(),
[](int x) { return x * x; });
std::cout << "平方值: ";
for (int x : squared) {
std::cout << x << " ";
}
std::cout << std::endl;

// 两个序列的元素运算
std::vector<int> vecA = {1, 2, 3, 4, 5};
std::vector<int> vecB = {10, 20, 30, 40, 50};
std::vector<int> result(5);
std::transform(vecA.begin(), vecA.end(), vecB.begin(), result.begin(),
[](int a, int b) { return a + b; });
std::cout << "两个向量的和: ";
for (int x : result) {
std::cout << x << " ";
}
std::cout << std::endl;

解析

  • std::transform:将指定范围内的元素转换后存储到另一个范围
  • 可以处理单个序列的转换,也可以处理两个序列的元素级运算

2.8 遍历算法

1
2
3
4
5
6
7
8
9
10
std::cout << "\n=== 遍历算法 ===" << std::endl;
std::vector<int> forEachVec = {1, 2, 3, 4, 5};
// 遍历并修改元素
std::for_each(forEachVec.begin(), forEachVec.end(),
[](int& x) { x *= 2; });
std::cout << "遍历后(乘以2): ";
for (int x : forEachVec) {
std::cout << x << " ";
}
std::cout << std::endl;

解析

  • std::for_each:遍历指定范围内的所有元素,并对每个元素执行指定操作
  • 可以通过引用修改元素的值

2.9 删除算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
std::cout << "\n=== 删除算法 ===" << std::endl;
std::vector<int> removeVec = {1, 2, 3, 2, 4, 2, 5};
// 移除元素(返回新的结束迭代器)
auto newEnd = std::remove(removeVec.begin(), removeVec.end(), 2);
// 擦除从新结束迭代器到原结束迭代器的元素
removeVec.erase(newEnd, removeVec.end());
std::cout << "移除2后: ";
for (int x : removeVec) {
std::cout << x << " ";
}
std::cout << std::endl;

// 条件删除
std::vector<int> removeIfVec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto newEndIf = std::remove_if(removeIfVec.begin(), removeIfVec.end(),
[](int x) { return x % 2 == 0; });
removeIfVec.erase(newEndIf, removeIfVec.end());
std::cout << "移除偶数后: ";
for (int x : removeIfVec) {
std::cout << x << " ";
}
std::cout << std::endl;

解析

  • std::remove:移除指定值的元素,返回新的结束迭代器
  • std::remove_if:移除满足条件的元素,返回新的结束迭代器
  • 注意:这些算法只是将需要移除的元素移到了范围的末尾,并没有真正删除它们,需要配合 erase 函数使用

2.10 去重算法

1
2
3
4
5
6
7
8
9
10
11
12
13
std::cout << "\n=== 去重算法 ===" << std::endl;
std::vector<int> uniqueVec = {1, 1, 2, 2, 3, 3, 4, 4};
// 先排序
std::sort(uniqueVec.begin(), uniqueVec.end());
// 去重
auto uniqueEnd = std::unique(uniqueVec.begin(), uniqueVec.end());
// 擦除重复元素
uniqueVec.erase(uniqueEnd, uniqueVec.end());
std::cout << "去重后: ";
for (int x : uniqueVec) {
std::cout << x << " ";
}
std::cout << std::endl;

解析

  • std::unique:移除相邻的重复元素,返回新的结束迭代器
  • 通常需要先对元素进行排序,以确保所有重复元素都相邻

2.11 合并算法

1
2
3
4
5
6
7
8
9
10
11
12
std::cout << "\n=== 合并算法 ===" << std::endl;
// 两个已排序的向量
std::vector<int> v1 = {1, 3, 5, 7, 9};
std::vector<int> v2 = {2, 4, 6, 8, 10};
std::vector<int> merged(10);
// 合并两个已排序的向量
std::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), merged.begin());
std::cout << "合并后: ";
for (int x : merged) {
std::cout << x << " ";
}
std::cout << std::endl;

解析

  • std::merge:合并两个已排序的范围,结果也会保持排序状态

2.12 分区算法

1
2
3
4
5
6
7
8
9
10
11
std::cout << "\n=== 分区算法 ===" << std::endl;
std::vector<int> partitionVec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 分区(偶数在前,奇数在后)
auto partitionPoint = std::partition(partitionVec.begin(), partitionVec.end(),
[](int x) { return x % 2 == 0; });
std::cout << "分区后(偶数在前): ";
for (int x : partitionVec) {
std::cout << x << " ";
}
std::cout << std::endl;
std::cout << "分区点位置: " << (partitionPoint - partitionVec.begin()) << std::endl;

解析

  • std::partition:根据条件将范围分为两部分,满足条件的元素在前,不满足的在后
  • 返回分区点,即第一个不满足条件的元素的迭代器

2.13 逻辑算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
std::cout << "\n=== 逻辑算法 ===" << std::endl;
std::vector<int> checkVec = {2, 4, 6, 8, 10};
// 检查所有元素是否满足条件
bool allEven = std::all_of(checkVec.begin(), checkVec.end(),
[](int x) { return x % 2 == 0; });
// 检查是否有元素满足条件
bool anyOdd = std::any_of(checkVec.begin(), checkVec.end(),
[](int x) { return x % 2 != 0; });
// 检查是否没有元素满足条件
bool noneNegative = std::none_of(checkVec.begin(), checkVec.end(),
[](int x) { return x < 0; });
std::cout << "所有元素都是偶数: " << (allEven ? "是" : "否") << std::endl;
std::cout << "存在奇数: " << (anyOdd ? "是" : "否") << std::endl;
std::cout << "没有负数: " << (noneNegative ? "是" : "否") << std::endl;

解析

  • std::all_of:检查范围内的所有元素是否都满足条件
  • std::any_of:检查范围内是否有元素满足条件
  • std::none_of:检查范围内是否没有元素满足条件

2.14 相等性算法

1
2
3
4
5
6
7
8
9
std::cout << "\n=== 相等性算法 ===" << std::endl;
std::vector<int> eqVec1 = {1, 2, 3, 4, 5};
std::vector<int> eqVec2 = {1, 2, 3, 4, 5};
std::vector<int> eqVec3 = {1, 2, 3, 4, 6};
// 检查两个序列是否相等
bool equal1 = std::equal(eqVec1.begin(), eqVec1.end(), eqVec2.begin());
bool equal2 = std::equal(eqVec1.begin(), eqVec1.end(), eqVec3.begin());
std::cout << "eqVec1 和 eqVec2 相等: " << (equal1 ? "是" : "否") << std::endl;
std::cout << "eqVec1 和 eqVec3 相等: " << (equal2 ? "是" : "否") << std::endl;

解析

  • std::equal:检查两个范围是否相等,即对应位置的元素是否都相等

2.15 包含算法

1
2
3
4
5
6
7
std::cout << "\n=== 包含算法 ===" << std::endl;
std::vector<int> haystack = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> needle = {3, 4, 5};
// 检查一个序列是否是另一个序列的子序列
bool contains = std::search(haystack.begin(), haystack.end(),
needle.begin(), needle.end()) != haystack.end();
std::cout << "haystack 包含 needle: " << (contains ? "是" : "否") << std::endl;

解析

  • std::search:在一个范围中搜索另一个范围,返回第一次出现的位置

3. 编译和运行说明

3.1 编译命令

使用以下命令编译 stl_algorithms.cpp 文件:

1
g++ -std=c++11 -fexec-charset=GBK -o stl_algorithms stl_algorithms.cpp

参数说明:

  • -std=c++11:使用 C++11 标准
  • -fexec-charset=GBK:确保输出的中文字符正确显示
  • -o stl_algorithms:指定输出可执行文件名为 stl_algorithms

3.2 运行命令

编译成功后,使用以下命令运行程序:

1
2
./stl_algorithms  # Linux/macOS
.\stl_algorithms.exe # Windows

3.3 预期输出

运行程序后,您将看到类似以下的输出:

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
原始向量: 5 2 8 1 9 3 7 4 6 

=== 排序算法 ===
升序排序后: 1 2 3 4 5 6 7 8 9
降序排序后: 9 8 7 6 5 4 3 2 1
部分排序(前3个元素): 1 2 3 9 8 7 6 5 4

=== 查找算法 ===
找到5,位置: 4
7 存在: 是
5的下界位置: 4
5的上界位置: 5
第一个偶数: 2,位置: 1

=== 修改算法 ===
复制后的向量: 1 2 3 4 5
填充0后: 0 0 0 0 0
生成递增序列(1,2,3,4,5): 1 2 3 4 5
反转后: 5 4 3 2 1
随机打乱后: 1 4 2 3 5

=== 数值算法 ===
总和: 15
乘积: 120
相邻元素的差: 1 1 1 1

=== 最值算法 ===
最小值: 1, 最大值: 5
a和b的最小值: 10
a和b的最大值: 20

=== 计数算法 ===
1出现的次数: 4
偶数的个数: 2

=== 转换算法 ===
平方值: 1 4 9 16 25
两个向量的和: 11 22 33 44 55

=== 遍历算法 ===
遍历后(乘以2): 2 4 6 8 10

=== 删除算法 ===
移除2后: 1 3 4 5
移除偶数后: 1 3 5 7 9

=== 去重算法 ===
去重后: 1 2 3 4

合并后: 1 2 3 4 5 6 7 8 9 10

=== 分区算法 ===
分区后(偶数在前): 10 2 8 4 6 5 7 3 9 1
分区点位置: 5

=== 逻辑算法 ===
所有元素都是偶数: 是
存在奇数: 否
没有负数: 是

=== 相等性算法 ===
eqVec1 和 eqVec2 相等: 是
eqVec1 和 eqVec3 相等: 否

=== 包含算法 ===
haystack 包含 needle: 是

4. 技术要点

4.1 迭代器的重要性

  • 迭代器:是 STL 算法的核心,它们提供了一种统一的方式来访问容器中的元素,无论容器的具体实现如何
  • 迭代器类别
    • InputIterator:只读,只能单向移动
    • OutputIterator:只写,只能单向移动
    • ForwardIterator:可读可写,只能单向移动
    • BidirectionalIterator:可读可写,可双向移动
    • RandomAccessIterator:可读可写,可随机访问
  • 算法对迭代器的要求:不同的算法对迭代器有不同的要求,例如 sort 需要随机访问迭代器,而 find 只需要输入迭代器

4.2 谓词函数

  • 谓词函数:是返回布尔值的函数,用于判断元素是否满足特定条件
  • 一元谓词:接受一个参数的谓词函数,如 std::count_if 使用的谓词
  • 二元谓词:接受两个参数的谓词函数,如 std::sort 使用的比较函数
  • Lambda 表达式:C++11 引入的 lambda 表达式是定义谓词函数的便捷方式

4.3 算法的时间复杂度

算法类别 算法名称 时间复杂度 空间复杂度
排序算法 sort O(n log n) O(log n)
partial_sort O(n log k) O(log k)
stable_sort O(n log n) O(n)
查找算法 find O(n) O(1)
binary_search O(log n) O(1)
lower_bound O(log n) O(1)
upper_bound O(log n) O(1)
修改算法 copy O(n) O(1)
fill O(n) O(1)
reverse O(n) O(1)
random_shuffle O(n) O(1)
数值算法 accumulate O(n) O(1)
adjacent_difference O(n) O(1)
最值算法 minmax_element O(n) O(1)
计数算法 count O(n) O(1)
count_if O(n) O(1)
转换算法 transform O(n) O(1)
遍历算法 for_each O(n) O(1)
删除算法 remove O(n) O(1)
remove_if O(n) O(1)
去重算法 unique O(n) O(1)
合并算法 merge O(n) O(n)
分区算法 partition O(n) O(1)
逻辑算法 all_of O(n) O(1)
any_of O(n) O(1)
none_of O(n) O(1)
相等性算法 equal O(n) O(1)
包含算法 search O(n*m) O(1)

4.4 算法的使用技巧

  • 选择合适的算法:根据具体需求选择最适合的算法,例如需要排序时使用 sort,需要查找时使用 findbinary_search
  • 注意算法的前提条件:例如 binary_search 要求容器已排序,unique 要求重复元素相邻
  • 使用 lambda 表达式:lambda 表达式是定义谓词函数的便捷方式,使代码更简洁
  • 注意算法的返回值:例如 remove 返回新的结束迭代器,需要配合 erase 使用
  • 考虑算法的性能:不同算法的时间复杂度不同,对于大型数据集,选择高效的算法非常重要

4.5 算法与容器的配合

  • 容器提供迭代器:所有 STL 容器都提供了迭代器,使算法能够操作它们
  • 容器的成员函数:有些容器提供了与算法同名的成员函数,例如 list::sort,这些成员函数通常针对容器的特性进行了优化
  • 算法的通用性:算法通过迭代器操作元素,与具体的容器实现无关,这使得算法可以用于任何提供适当迭代器的容器

5. 常见问题解答

5.1 如何选择合适的排序算法?

解答

  • 需要完全排序:使用 std::sort
  • 需要保持相等元素的相对顺序:使用 std::stable_sort
  • 只需要部分元素排序:使用 std::partial_sort
  • 只需要找到前 k 个最小元素:使用 std::nth_element
  • 对于链表:使用 list::sort 成员函数,因为链表不支持随机访问迭代器

5.2 为什么 std::remove 没有真正删除元素?

解答

  • std::remove 的设计理念是”算法不修改容器的大小”,它只是将需要保留的元素移到容器的前面,将需要删除的元素移到容器的后面
  • 这样设计的原因是为了保持算法的通用性,因为并不是所有容器都支持高效的删除操作
  • 使用 std::remove 后,需要配合 erase 函数来真正删除元素,例如:
    1
    vec.erase(std::remove(vec.begin(), vec.end(), value), vec.end());

5.3 如何在自定义类型上使用 STL 算法?

解答

  • 对于排序算法:需要为自定义类型定义 < 运算符,或者提供自定义比较函数
  • 对于查找算法:需要为自定义类型定义 == 运算符,或者提供自定义相等性函数
  • 对于其他算法:根据算法的要求,可能需要定义相应的操作符或函数

5.4 如何使用 STL 算法处理多个容器?

解答

  • 使用 std::transform:可以处理两个容器的元素级运算
  • 使用 std::merge:可以合并两个已排序的容器
  • 使用 std::set_unionstd::set_intersection:可以处理集合操作
  • 使用迭代器:可以手动控制多个容器的迭代器,实现复杂的操作

5.5 如何提高 STL 算法的性能?

解答

  • 选择合适的算法:根据具体需求选择时间复杂度最低的算法
  • 选择合适的容器:不同的容器对不同算法的支持效率不同
  • 减少不必要的拷贝:使用移动语义和引用传递
  • 预分配内存:对于需要输出结果的算法,预分配足够的内存
  • 使用并行算法:C++17 引入了并行算法,可以利用多核处理器提高性能

5.6 如何使用 STL 算法处理自定义数据结构?

解答

  • 实现迭代器:为自定义数据结构实现适当的迭代器
  • 使用适配器:使用 std::reference_wrapper 等适配器来包装元素
  • 使用 lambda 表达式:使用 lambda 表达式来处理自定义数据结构的元素
  • 继承现有容器:如果可能,继承现有容器并扩展其功能

5.7 如何调试 STL 算法?

解答

  • 使用调试器:使用 GDB 或 Visual Studio 调试器来跟踪算法的执行
  • 添加日志:在谓词函数中添加日志,查看算法的执行过程
  • 简化问题:使用小型数据集来测试算法,以便更容易理解其行为
  • 查看文档:仔细阅读算法的文档,了解其行为和限制

6. 代码优化建议

6.1 算法选择优化

  • 根据数据规模选择算法:对于小型数据集,简单算法可能比复杂算法更快;对于大型数据集,应选择时间复杂度低的算法
  • 根据数据特性选择算法:例如,对于几乎有序的数据,插入排序可能比快速排序更快
  • 考虑空间复杂度:对于内存受限的环境,应选择空间复杂度低的算法

6.2 迭代器使用优化

  • 使用正确的迭代器类型:根据算法的要求选择合适的迭代器类型
  • 避免不必要的迭代器操作:例如,在循环中避免重复计算迭代器的位置
  • 使用 std::beginstd::end:这些函数可以统一处理容器和数组

6.3 谓词函数优化

  • 使用 inline 函数:对于简单的谓词函数,使用 inline 可以提高性能
  • 使用 lambda 表达式:lambda 表达式可以捕获上下文,使代码更简洁
  • 避免复杂的谓词函数:复杂的谓词函数会增加算法的执行时间

6.4 内存使用优化

  • 预分配内存:对于需要输出结果的算法,使用 reserve 预分配足够的内存
  • 使用移动语义:对于大对象,使用移动语义减少拷贝开销
  • 避免不必要的临时对象:尽可能使用引用传递,避免创建临时对象

6.5 并行计算

  • 使用 C++17 并行算法:C++17 引入了并行算法,可以利用多核处理器提高性能
  • 使用 OpenMP:对于不支持 C++17 的环境,可以使用 OpenMP 进行并行计算
  • 考虑数据依赖性:并行计算需要考虑数据依赖性,避免竞态条件

7. 总结

C++ STL 算法是一组功能强大的通用函数,它们通过迭代器操作容器中的元素,提供了丰富的操作功能。正确使用这些算法可以大大简化代码编写,提高开发效率和代码质量。

7.1 算法分类总结

  • 排序算法:用于对元素进行排序
  • 查找算法:用于在容器中查找元素
  • 修改算法:用于修改容器中的元素
  • 数值算法:用于数值计算
  • 最值算法:用于查找最大值和最小值
  • 计数算法:用于计数元素
  • 转换算法:用于转换元素
  • 遍历算法:用于遍历元素
  • 删除算法:用于删除元素
  • 去重算法:用于去除重复元素
  • 合并算法:用于合并容器
  • 分区算法:用于分区容器
  • 逻辑算法:用于逻辑判断
  • 相等性算法:用于比较容器
  • 包含算法:用于检查容器是否包含子序列

7.2 最佳实践

  • 了解算法的功能和限制:仔细阅读算法的文档,了解其功能、时间复杂度和限制
  • 选择合适的算法:根据具体需求选择最适合的算法
  • 使用正确的迭代器:根据算法的要求选择合适的迭代器类型
  • 使用 lambda 表达式:lambda 表达式是定义谓词函数的便捷方式
  • 注意算法的返回值:例如 remove 返回新的结束迭代器,需要配合 erase 使用
  • 考虑性能:对于大型数据集,选择高效的算法和容器
  • 编写清晰的代码:使用有意义的变量名和注释,使代码易于理解
  • 测试算法:在关键场景下测试算法的性能和正确性

通过合理选择和使用 STL 算法,可以显著提高 C++ 程序的性能和可维护性。希望本指南能够帮助您更好地理解和使用 C++ STL 算法。

stl_algorithms.cpp

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <string>
#include <list>
#include <functional>

int main() {
// 初始化一个整数向量
std::vector<int> nums = {5, 2, 8, 1, 9, 3, 7, 4, 6};

std::cout << "原始向量: ";
for (int x : nums) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 排序算法 ===" << std::endl;
// 升序排序
std::sort(nums.begin(), nums.end());
std::cout << "升序排序后: ";
for (int x : nums) {
std::cout << x << " ";
}
std::cout << std::endl;

// 降序排序
std::sort(nums.begin(), nums.end(), std::greater<int>());
std::cout << "降序排序后: ";
for (int x : nums) {
std::cout << x << " ";
}
std::cout << std::endl;

// 部分排序(前3个元素排序)
std::partial_sort(nums.begin(), nums.begin() + 3, nums.end());
std::cout << "部分排序(前3个元素): ";
for (int x : nums) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 查找算法 ===" << std::endl;
// 先排序以便使用二分查找
std::sort(nums.begin(), nums.end());
// 线性查找
auto it = std::find(nums.begin(), nums.end(), 5);
if (it != nums.end()) {
std::cout << "找到5,位置: " << (it - nums.begin()) << std::endl;
}

// 二分查找
bool exists = std::binary_search(nums.begin(), nums.end(), 7);
std::cout << "7 存在: " << (exists ? "是" : "否") << std::endl;

// 下界和上界
auto lower = std::lower_bound(nums.begin(), nums.end(), 5);
auto upper = std::upper_bound(nums.begin(), nums.end(), 5);
std::cout << "5的下界位置: " << (lower - nums.begin()) << std::endl;
std::cout << "5的上界位置: " << (upper - nums.begin()) << std::endl;

// 查找第一个满足条件的元素
auto firstEven = std::find_if(nums.begin(), nums.end(),
[](int x) { return x % 2 == 0; });
if (firstEven != nums.end()) {
std::cout << "第一个偶数: " << *firstEven << ",位置: " << (firstEven - nums.begin()) << std::endl;
}

std::cout << "\n=== 修改算法 ===" << std::endl;
std::vector<int> vec1 = {1, 2, 3, 4, 5};
std::vector<int> vec2(5);

// 复制元素
std::copy(vec1.begin(), vec1.end(), vec2.begin());
std::cout << "复制后的向量: ";
for (int x : vec2) {
std::cout << x << " ";
}
std::cout << std::endl;

// 填充元素
std::fill(vec2.begin(), vec2.end(), 0);
std::cout << "填充0后: ";
for (int x : vec2) {
std::cout << x << " ";
}
std::cout << std::endl;

// 生成递增序列
std::iota(vec2.begin(), vec2.end(), 1);
std::cout << "生成递增序列(1,2,3,4,5): ";
for (int x : vec2) {
std::cout << x << " ";
}
std::cout << std::endl;

// 反转元素
std::vector<int> vec3 = {1, 2, 3, 4, 5};
std::reverse(vec3.begin(), vec3.end());
std::cout << "反转后: ";
for (int x : vec3) {
std::cout << x << " ";
}
std::cout << std::endl;

// 随机打乱元素(C++11后推荐使用shuffle)
std::random_shuffle(vec3.begin(), vec3.end());
std::cout << "随机打乱后: ";
for (int x : vec3) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 数值算法 ===" << std::endl;
std::vector<int> values = {1, 2, 3, 4, 5};
// 计算总和
int sum = std::accumulate(values.begin(), values.end(), 0);
std::cout << "总和: " << sum << std::endl;

// 计算乘积
int product = std::accumulate(values.begin(), values.end(), 1, std::multiplies<int>());
std::cout << "乘积: " << product << std::endl;

// 计算相邻元素的差
std::vector<int> diff(values.size() - 1);
std::adjacent_difference(values.begin(), values.end(), diff.begin());
std::cout << "相邻元素的差: ";
for (int x : diff) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 最值算法 ===" << std::endl;
// 查找最小值和最大值
auto minMax = std::minmax_element(values.begin(), values.end());
std::cout << "最小值: " << *minMax.first << ", 最大值: " << *minMax.second << std::endl;

// 直接比较两个值
int a = 10, b = 20;
std::cout << "a和b的最小值: " << std::min(a, b) << std::endl;
std::cout << "a和b的最大值: " << std::max(a, b) << std::endl;

std::cout << "\n=== 计数算法 ===" << std::endl;
std::vector<int> countVec = {1, 2, 1, 3, 1, 4, 1};
// 计数元素出现次数
int count = std::count(countVec.begin(), countVec.end(), 1);
std::cout << "1出现的次数: " << count << std::endl;

// 计数满足条件的元素
int evenCount = std::count_if(countVec.begin(), countVec.end(),
[](int x) { return x % 2 == 0; });
std::cout << "偶数的个数: " << evenCount << std::endl;

std::cout << "\n=== 转换算法 ===" << std::endl;
std::vector<int> original = {1, 2, 3, 4, 5};
std::vector<int> squared(5);
// 转换元素(计算平方)
std::transform(original.begin(), original.end(), squared.begin(),
[](int x) { return x * x; });
std::cout << "平方值: ";
for (int x : squared) {
std::cout << x << " ";
}
std::cout << std::endl;

// 两个序列的元素运算
std::vector<int> vecA = {1, 2, 3, 4, 5};
std::vector<int> vecB = {10, 20, 30, 40, 50};
std::vector<int> result(5);
std::transform(vecA.begin(), vecA.end(), vecB.begin(), result.begin(),
[](int a, int b) { return a + b; });
std::cout << "两个向量的和: ";
for (int x : result) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 遍历算法 ===" << std::endl;
std::vector<int> forEachVec = {1, 2, 3, 4, 5};
// 遍历并修改元素
std::for_each(forEachVec.begin(), forEachVec.end(),
[](int& x) { x *= 2; });
std::cout << "遍历后(乘以2): ";
for (int x : forEachVec) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 删除算法 ===" << std::endl;
std::vector<int> removeVec = {1, 2, 3, 2, 4, 2, 5};
// 移除元素(返回新的结束迭代器)
auto newEnd = std::remove(removeVec.begin(), removeVec.end(), 2);
// 擦除从新结束迭代器到原结束迭代器的元素
removeVec.erase(newEnd, removeVec.end());
std::cout << "移除2后: ";
for (int x : removeVec) {
std::cout << x << " ";
}
std::cout << std::endl;

// 条件删除
std::vector<int> removeIfVec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto newEndIf = std::remove_if(removeIfVec.begin(), removeIfVec.end(),
[](int x) { return x % 2 == 0; });
removeIfVec.erase(newEndIf, removeIfVec.end());
std::cout << "移除偶数后: ";
for (int x : removeIfVec) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 去重算法 ===" << std::endl;
std::vector<int> uniqueVec = {1, 1, 2, 2, 3, 3, 4, 4};
// 先排序
std::sort(uniqueVec.begin(), uniqueVec.end());
// 去重
auto uniqueEnd = std::unique(uniqueVec.begin(), uniqueVec.end());
// 擦除重复元素
uniqueVec.erase(uniqueEnd, uniqueVec.end());
std::cout << "去重后: ";
for (int x : uniqueVec) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 合并算法 ===" << std::endl;
// 两个已排序的向量
std::vector<int> v1 = {1, 3, 5, 7, 9};
std::vector<int> v2 = {2, 4, 6, 8, 10};
std::vector<int> merged(10);
// 合并两个已排序的向量
std::merge(v1.begin(), v1.end(), v2.begin(), v2.end(), merged.begin());
std::cout << "合并后: ";
for (int x : merged) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 分区算法 ===" << std::endl;
std::vector<int> partitionVec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 分区(偶数在前,奇数在后)
auto partitionPoint = std::partition(partitionVec.begin(), partitionVec.end(),
[](int x) { return x % 2 == 0; });
std::cout << "分区后(偶数在前): ";
for (int x : partitionVec) {
std::cout << x << " ";
}
std::cout << std::endl;
std::cout << "分区点位置: " << (partitionPoint - partitionVec.begin()) << std::endl;

std::cout << "\n=== 逻辑算法 ===" << std::endl;
std::vector<int> checkVec = {2, 4, 6, 8, 10};
// 检查所有元素是否满足条件
bool allEven = std::all_of(checkVec.begin(), checkVec.end(),
[](int x) { return x % 2 == 0; });
// 检查是否有元素满足条件
bool anyOdd = std::any_of(checkVec.begin(), checkVec.end(),
[](int x) { return x % 2 != 0; });
// 检查是否没有元素满足条件
bool noneNegative = std::none_of(checkVec.begin(), checkVec.end(),
[](int x) { return x < 0; });
std::cout << "所有元素都是偶数: " << (allEven ? "是" : "否") << std::endl;
std::cout << "存在奇数: " << (anyOdd ? "是" : "否") << std::endl;
std::cout << "没有负数: " << (noneNegative ? "是" : "否") << std::endl;

std::cout << "\n=== 相等性算法 ===" << std::endl;
std::vector<int> eqVec1 = {1, 2, 3, 4, 5};
std::vector<int> eqVec2 = {1, 2, 3, 4, 5};
std::vector<int> eqVec3 = {1, 2, 3, 4, 6};
// 检查两个序列是否相等
bool equal1 = std::equal(eqVec1.begin(), eqVec1.end(), eqVec2.begin());
bool equal2 = std::equal(eqVec1.begin(), eqVec1.end(), eqVec3.begin());
std::cout << "eqVec1 和 eqVec2 相等: " << (equal1 ? "是" : "否") << std::endl;
std::cout << "eqVec1 和 eqVec3 相等: " << (equal2 ? "是" : "否") << std::endl;

std::cout << "\n=== 包含算法 ===" << std::endl;
std::vector<int> haystack = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> needle = {3, 4, 5};
// 检查一个序列是否是另一个序列的子序列
bool contains = std::search(haystack.begin(), haystack.end(),
needle.begin(), needle.end()) != haystack.end();
std::cout << "haystack 包含 needle: " << (contains ? "是" : "否") << std::endl;

return 0;
}

C++ STL 容器(Containers)详细指南

1. 功能说明

本指南基于 stl_containers.cpp 文件,详细介绍了 C++ 标准模板库(STL)中各种容器的使用方法和特性,包括:

  • 序列容器:vector、list、deque、array
  • 容器适配器:stack、queue、priority_queue
  • 关联容器:set、multiset、map、multimap
  • 无序关联容器:unordered_set、unordered_map
  • 元组和对:pair、tuple

每个容器都有其特定的用途和性能特点,本指南将详细介绍它们的使用方法、优缺点以及适用场景。

2. 代码解析

2.1 序列容器

2.1.1 std::vector(动态数组)

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
// 创建并初始化vector
std::vector<int> vec = {1, 2, 3, 4, 5};
// 在末尾添加元素
vec.push_back(6);
// 插入元素
vec.insert(vec.begin() + 2, 100);

std::cout << "Vector元素: ";
for (int x : vec) {
std::cout << x << " ";
}
std::cout << "\n大小: " << vec.size() << std::endl;
std::cout << "容量: " << vec.capacity() << std::endl;
std::cout << "是否为空: " << (vec.empty() ? "是" : "否") << std::endl;

// 删除元素
vec.erase(vec.begin() + 2);
std::cout << "删除元素后: ";
for (int x : vec) {
std::cout << x << " ";
}
std::cout << std::endl;

// 清空vector
vec.clear();
std::cout << "清空后大小: " << vec.size() << std::endl;

解析

  • std::vector 是最常用的序列容器,底层实现为动态数组
  • 支持随机访问,时间复杂度为 O(1)
  • 在末尾添加和删除元素的时间复杂度为均摊 O(1)
  • 在中间插入和删除元素的时间复杂度为 O(n)
  • 自动管理内存,会根据需要扩容(通常是翻倍)
  • size() 返回当前元素个数,capacity() 返回当前容量

2.1.2 std::list(双向链表)

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
// 创建并初始化list
std::list<int> lst = {10, 20, 30};
// 在头部添加元素
lst.push_front(5);
// 在尾部添加元素
lst.push_back(40);
// 在指定位置插入元素
auto it = lst.begin();
++it; // 移动到第二个元素
lst.insert(it, 15);

std::cout << "List元素: ";
for (int x : lst) {
std::cout << x << " ";
}
std::cout << "\n大小: " << lst.size() << std::endl;

// 删除元素
lst.remove(20); // 删除所有值为20的元素
std::cout << "删除元素后: ";
for (int x : lst) {
std::cout << x << " ";
}
std::cout << std::endl;

// 排序
lst.sort();
std::cout << "排序后: ";
for (int x : lst) {
std::cout << x << " ";
}
std::cout << std::endl;

解析

  • std::list 是双向链表,每个节点包含数据和前后指针
  • 不支持随机访问,访问元素的时间复杂度为 O(n)
  • 在任意位置插入和删除元素的时间复杂度为 O(1)(需要先找到位置)
  • 内存开销较大,每个元素需要额外的指针空间
  • 提供了很多成员函数,如 sort()remove()unique()

2.1.3 std::deque(双端队列)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 创建并初始化deque
std::deque<int> dq = {1, 2, 3};
// 在头部添加元素
dq.push_front(0);
// 在尾部添加元素
dq.push_back(4);

std::cout << "Deque元素: ";
for (int x : dq) {
std::cout << x << " ";
}
std::cout << "\n大小: " << dq.size() << std::endl;

// 删除头部和尾部元素
dq.pop_front();
dq.pop_back();
std::cout << "删除头尾后: ";
for (int x : dq) {
std::cout << x << " ";
}
std::cout << std::endl;

解析

  • std::deque 是双端队列,支持在两端高效插入和删除
  • 支持随机访问,时间复杂度为 O(1)
  • 在两端插入和删除元素的时间复杂度为 O(1)
  • 内存布局为分段连续,内部使用多个缓冲区
  • 适用于需要在两端频繁操作的场景

2.1.4 std::array(固定大小数组)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 创建array(固定大小)
std::array<int, 5> arr = {1, 2, 3, 4, 5};

std::cout << "Array元素: ";
for (int x : arr) {
std::cout << x << " ";
}
std::cout << "\n大小: " << arr.size() << std::endl;
std::cout << "是否为空: " << (arr.empty() ? "是" : "否") << std::endl;

// 访问元素
std::cout << "第一个元素: " << arr.front() << std::endl;
std::cout << "最后一个元素: " << arr.back() << std::endl;
std::cout << "索引2的元素: " << arr[2] << std::endl;

解析

  • std::array 是固定大小的数组,在编译时确定大小
  • 支持随机访问,时间复杂度为 O(1)
  • 内存分配在栈上,效率高
  • 提供了与容器一致的接口,如 size()empty()front()back()
  • 适用于需要固定大小数组且希望使用容器接口的场景

2.2 容器适配器

2.2.1 std::queue(队列)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建queue
std::queue<int> q;
// 入队
q.push(1);
q.push(2);
q.push(3);

std::cout << "Queue元素(FIFO): ";
while (!q.empty()) {
std::cout << q.front() << " "; // 访问队首元素
q.pop(); // 出队
}
std::cout << std::endl;

解析

  • std::queue 是队列适配器,基于 deque 实现
  • 遵循先进先出(FIFO)原则
  • 只允许在队尾插入,在队首删除
  • 不支持随机访问,只能访问队首元素

2.2.2 std::stack(栈)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 创建stack
std::stack<int> st;
// 入栈
st.push(1);
st.push(2);
st.push(3);

std::cout << "Stack元素(LIFO): ";
while (!st.empty()) {
std::cout << st.top() << " "; // 访问栈顶元素
st.pop(); // 出栈
}
std::cout << std::endl;

解析

  • std::stack 是栈适配器,基于 deque 实现
  • 遵循后进先出(LIFO)原则
  • 只允许在栈顶插入和删除
  • 不支持随机访问,只能访问栈顶元素

2.2.3 std::priority_queue(优先队列)

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
// 创建优先队列(默认是最大堆)
std::priority_queue<int> pq;
// 入队
pq.push(30);
pq.push(10);
pq.push(50);
pq.push(20);

std::cout << "优先队列元素(从大到小): ";
while (!pq.empty()) {
std::cout << pq.top() << " "; // 访问优先级最高的元素
pq.pop(); // 出队
}
std::cout << std::endl;

// 创建最小堆
std::priority_queue<int, std::vector<int>, std::greater<int>> min_pq;
min_pq.push(30);
min_pq.push(10);
min_pq.push(50);
min_pq.push(20);

std::cout << "最小堆元素(从小到大): ";
while (!min_pq.empty()) {
std::cout << min_pq.top() << " ";
min_pq.pop();
}
std::cout << std::endl;

解析

  • std::priority_queue 是优先队列适配器,默认基于 vector 实现,使用最大堆
  • 元素按照优先级自动排序,默认情况下最大元素优先级最高
  • 可以通过第三个模板参数自定义比较方式,如使用 std::greater<int> 实现最小堆
  • 插入和删除操作的时间复杂度为 O(log n)
  • 适用于需要自动排序的场景,如任务调度

2.3 关联容器

2.3.1 std::set(有序集合)

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
// 创建并初始化set(自动排序,去重)
std::set<int> s = {5, 2, 8, 1, 9, 2, 5};
// 插入元素
s.insert(7);

std::cout << "Set元素(有序,唯一): ";
for (int x : s) {
std::cout << x << " ";
}
std::cout << "\n大小: " << s.size() << std::endl;
std::cout << "是否包含5: " << (s.count(5) ? "是" : "否") << std::endl;

// 查找元素
auto find_it = s.find(8);
if (find_it != s.end()) {
std::cout << "找到元素: " << *find_it << std::endl;
}

// 删除元素
s.erase(5);
std::cout << "删除5后: ";
for (int x : s) {
std::cout << x << " ";
}
std::cout << std::endl;

解析

  • std::set 是有序集合,基于红黑树实现
  • 元素自动排序,且不允许重复
  • 插入、删除和查找操作的时间复杂度为 O(log n)
  • 适用于需要有序且无重复元素的场景
  • 不支持通过迭代器修改元素,因为这会破坏排序

2.3.2 std::multiset(有序多重集合)

1
2
3
4
5
6
7
8
// 创建并初始化multiset(自动排序,允许重复)
std::multiset<int> ms = {5, 2, 8, 1, 9, 2, 5};

std::cout << "Multiset元素(有序,允许重复): ";
for (int x : ms) {
std::cout << x << " ";
}
std::cout << "\n5的数量: " << ms.count(5) << std::endl;

解析

  • std::multiset 是有序多重集合,基于红黑树实现
  • 元素自动排序,但允许重复
  • 插入、删除和查找操作的时间复杂度为 O(log n)
  • 适用于需要有序且允许重复元素的场景

2.3.3 std::map(有序映射)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 创建map
std::map<std::string, int> m;
// 插入键值对
m["apple"] = 5;
m["banana"] = 3;
m["cherry"] = 8;
m.insert({"date", 2});

std::cout << "Map元素(键值对):" << std::endl;
for (const auto& pair : m) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
std::cout << "banana的值: " << m["banana"] << std::endl;

// 查找元素
auto map_it = m.find("cherry");
if (map_it != m.end()) {
std::cout << "找到键: " << map_it->first << ", 值: " << map_it->second << std::endl;
}

解析

  • std::map 是有序映射,基于红黑树实现
  • 存储键值对,键自动排序,且不允许重复
  • 插入、删除和查找操作的时间复杂度为 O(log n)
  • 可以通过键快速查找对应的值
  • 适用于需要键值对且键有序的场景

2.3.4 std::multimap(有序多重映射)

1
2
3
4
5
6
7
8
9
10
11
// 创建multimap(允许重复键)
std::multimap<std::string, int> mm;
// 插入键值对
mm.insert({"fruit", 1});
mm.insert({"fruit", 2});
mm.insert({"vegetable", 3});

std::cout << "Multimap元素:" << std::endl;
for (const auto& pair : mm) {
std::cout << pair.first << ": " << pair.second << std::endl;
}

解析

  • std::multimap 是有序多重映射,基于红黑树实现
  • 存储键值对,键自动排序,但允许重复
  • 插入、删除和查找操作的时间复杂度为 O(log n)
  • 适用于需要键值对且允许重复键的场景

2.4 无序关联容器

2.4.1 std::unordered_set(无序集合)

1
2
3
4
5
6
7
8
9
// 创建并初始化unordered_set(无序,去重,哈希表实现)
std::unordered_set<int> us = {5, 2, 8, 1, 9, 2, 5};

std::cout << "Unordered set元素(无序,唯一): ";
for (int x : us) {
std::cout << x << " ";
}
std::cout << "\n大小: " << us.size() << std::endl;
std::cout << "是否包含3: " << (us.count(3) ? "是" : "否") << std::endl;

解析

  • std::unordered_set 是无序集合,基于哈希表实现
  • 元素无序,且不允许重复
  • 插入、删除和查找操作的平均时间复杂度为 O(1),最坏情况为 O(n)
  • 适用于需要快速查找且不关心顺序的场景
  • 内存开销较大,需要存储哈希表结构

2.4.2 std::unordered_map(无序映射)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建unordered_map(无序,哈希表实现)
std::unordered_map<std::string, int> um;
// 插入键值对
um["one"] = 1;
um["two"] = 2;
um["three"] = 3;

std::cout << "Unordered map元素(无序):" << std::endl;
for (const auto& pair : um) {
std::cout << pair.first << ": " << pair.second << std::endl;
}

// 查看桶信息
std::cout << "桶数: " << um.bucket_count() << std::endl;
std::cout << "最大桶数: " << um.max_bucket_count() << std::endl;

解析

  • std::unordered_map 是无序映射,基于哈希表实现
  • 存储键值对,键无序,且不允许重复
  • 插入、删除和查找操作的平均时间复杂度为 O(1),最坏情况为 O(n)
  • 适用于需要快速查找键值对且不关心键顺序的场景
  • 内存开销较大,需要存储哈希表结构

2.5 元组和对

2.5.1 std::pair(键值对)

1
2
3
4
5
6
7
8
// 创建pair
std::pair<std::string, int> myPair("age", 25);
// 访问pair元素
std::cout << "Pair: " << myPair.first << " = " << myPair.second << std::endl;

// 使用make_pair创建pair
auto anotherPair = std::make_pair("height", 180);
std::cout << "Another pair: " << anotherPair.first << " = " << anotherPair.second << std::endl;

解析

  • std::pair 是模板类,用于存储两个不同类型的值
  • 可以通过 firstsecond 成员访问两个值
  • 常用于返回两个值的函数,或作为 map 的元素类型
  • std::make_pair 函数可以自动推导类型创建 pair

2.5.2 std::tuple(元组)

1
2
3
4
5
6
7
8
9
10
11
12
// 创建tuple
std::tuple<std::string, int, double> myTuple("John", 30, 3.14);
// 访问tuple元素(通过索引)
std::cout << "Tuple: " << std::get<0>(myTuple) << ", "
<< std::get<1>(myTuple) << ", "
<< std::get<2>(myTuple) << std::endl;

// 使用make_tuple创建tuple
auto anotherTuple = std::make_tuple("Alice", 25, 2.718);
// 使用结构化绑定(C++17特性)
auto [name, age, value] = anotherTuple;
std::cout << "Another tuple: " << name << ", " << age << ", " << value << std::endl;

解析

  • std::tuple 是模板类,用于存储多个不同类型的值
  • 可以通过 std::get<Index>(tuple) 访问指定索引的元素
  • std::make_tuple 函数可以自动推导类型创建 tuple
  • C++17 引入了结构化绑定,可以更方便地访问 tuple 元素
  • 适用于需要返回多个值的函数,或存储不同类型的值

3. 编译和运行说明

3.1 编译命令

使用以下命令编译 stl_containers.cpp 文件:

1
g++ -std=c++11 -fexec-charset=GBK -o stl_containers stl_containers.cpp

参数说明:

  • -std=c++11:使用 C++11 标准
  • -fexec-charset=GBK:确保输出的中文字符正确显示
  • -o stl_containers:指定输出可执行文件名为 stl_containers

3.2 运行命令

编译成功后,使用以下命令运行程序:

1
2
./stl_containers  # Linux/macOS
.\stl_containers.exe # Windows

3.3 预期输出

运行程序后,您将看到类似以下的输出:

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
=== 1. std::vector(动态数组)===
Vector元素: 1 2 100 3 4 5 6
大小: 7
容量: 10
是否为空: 否
删除元素后: 1 2 3 4 5 6
清空后大小: 0

=== 2. std::list(双向链表)===
List元素: 5 15 10 20 30 40
大小: 6
删除元素后: 5 15 10 30 40
排序后: 5 10 15 30 40

=== 3. std::deque(双端队列)===
Deque元素: 0 1 2 3 4
大小: 5
删除头尾后: 1 2 3

=== 4. std::queue(队列)===
Queue元素(FIFO): 1 2 3

=== 5. std::stack(栈)===
Stack元素(LIFO): 3 2 1

=== 6. std::priority_queue(优先队列)===
优先队列元素(从大到小): 50 30 20 10
最小堆元素(从小到大): 10 20 30 50

=== 7. std::set(有序集合)===
Set元素(有序,唯一): 1 2 5 7 8 9
大小: 6
是否包含5: 是
找到元素: 8
删除5后: 1 2 7 8 9

=== 8. std::multiset(有序多重集合)===
Multiset元素(有序,允许重复): 1 2 2 5 5 8 9
5的数量: 2

=== 9. std::map(有序映射)===
Map元素(键值对):
apple: 5
banana: 3
cherry: 8
date: 2
banana的值: 3
找到键: cherry, 值: 8

=== 10. std::multimap(有序多重映射)===
Multimap元素:
fruit: 1
fruit: 2
vegetable: 3

=== 11. std::unordered_set(无序集合)===
Unordered set元素(无序,唯一): 9 1 8 2 5
大小: 5
是否包含3: 否

=== 12. std::unordered_map(无序映射)===
Unordered map元素(无序):
three: 3
two: 2
one: 1
最大桶数: 164703072086692425

=== 13. std::pair(键值对)===
Pair: age = 25
Another pair: height = 180

=== 14. std::tuple(元组)===
Tuple: John, 30, 3.14
Another tuple: Alice, 25, 2.718

=== 15. std::array(固定大小数组)===
Array元素: 1 2 3 4 5
大小: 5
是否为空: 否
第一个元素: 1
最后一个元素: 5
索引2的元素: 3

4. 技术要点

4.1 容器选择指南

容器类型 底层实现 随机访问 插入/删除(头部) 插入/删除(中间) 插入/删除(尾部) 查找 排序 适用场景
vector 动态数组 O(1) O(n) O(n) 均摊O(1) O(n) O(n log n) 需要随机访问,主要在尾部操作
list 双向链表 O(n) O(1) O(1) O(1) O(n) O(n log n) 需要频繁在任意位置插入删除
deque 分段连续 O(1) O(1) O(n) O(1) O(n) O(n log n) 需要在两端频繁操作
array 固定数组 O(1) O(n) O(n) O(n) O(n) O(n log n) 固定大小,需要栈分配
stack deque 不支持 不支持 不支持 O(1) 不支持 不支持 后进先出操作
queue deque 不支持 O(1) 不支持 O(1) 不支持 不支持 先进先出操作
priority_queue vector 不支持 不支持 不支持 O(log n) 不支持 自动排序 优先级操作
set 红黑树 不支持 O(log n) O(log n) O(log n) O(log n) 自动排序 有序无重复元素
multiset 红黑树 不支持 O(log n) O(log n) O(log n) O(log n) 自动排序 有序允许重复元素
map 红黑树 不支持 O(log n) O(log n) O(log n) O(log n) 自动排序 有序键值对
multimap 红黑树 不支持 O(log n) O(log n) O(log n) O(log n) 自动排序 有序允许多重复键
unordered_set 哈希表 不支持 平均O(1) 平均O(1) 平均O(1) 平均O(1) 不支持 快速查找无重复元素
unordered_map 哈希表 不支持 平均O(1) 平均O(1) 平均O(1) 平均O(1) 不支持 快速查找键值对

4.2 迭代器

  • 迭代器:是一种抽象的设计概念,提供了一种方法来访问容器中的元素,而不暴露容器的底层表示
  • 迭代器类型
    • InputIterator:只读,只能单向移动
    • OutputIterator:只写,只能单向移动
    • ForwardIterator:可读可写,只能单向移动
    • BidirectionalIterator:可读可写,可双向移动
    • RandomAccessIterator:可读可写,可随机访问
  • 迭代器操作
    • ++it:移动到下一个元素
    • --it:移动到上一个元素(双向及以上)
    • it + n:移动到n个元素之后(随机访问)
    • *it:访问当前元素
    • it->:访问当前元素的成员

4.3 内存管理

  • 容器的内存分配
    • vector:动态分配,当容量不足时会重新分配更大的内存
    • list:每个节点单独分配内存
    • deque:分段分配内存
    • array:在栈上分配内存(如果是局部变量)
    • 关联容器:通常使用动态内存分配
  • 内存优化
    • 对于 vector,可以使用 reserve() 预分配内存,减少重新分配
    • 对于频繁插入删除的场景,优先使用 listdeque
    • 对于小而固定大小的数组,使用 array 可以提高性能

4.4 线程安全性

  • STL容器的线程安全性
    • 多个线程同时读取是安全的
    • 多个线程同时写入是不安全的
    • 一个线程写入,其他线程读取是不安全的
  • 线程安全的使用
    • 使用互斥锁保护容器访问
    • 考虑使用 C++11 中的并发容器
    • 对于只读操作,可以共享容器;对于读写操作,使用同步机制

4.5 自定义类型的使用

  • 在容器中使用自定义类型
    • 对于 setmap 等有序容器,需要定义 < 运算符
    • 对于 unordered_setunordered_map 等无序容器,需要定义哈希函数和 == 运算符
    • 对于所有容器,自定义类型应该有适当的拷贝构造函数和赋值运算符
  • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Person {
    public:
    std::string name;
    int age;

    bool operator<(const Person& other) const {
    return age < other.age;
    }
    };

    // 可以在set中使用
    std::set<Person> people;

5. 常见问题解答

5.1 如何选择合适的容器?

解答:选择容器时应考虑以下因素:

  • 访问模式:是否需要随机访问?
  • 插入删除模式:插入删除操作主要在哪些位置?
  • 排序需求:是否需要元素有序?
  • 查找频率:查找操作是否频繁?
  • 内存限制:内存使用是否受限?
  • 线程安全性:是否需要在多线程环境中使用?

5.2 vector 和 list 的区别是什么?

解答

  • 底层实现vector 是动态数组,list 是双向链表
  • 访问速度vector 支持随机访问(O(1)),list 只能顺序访问(O(n))
  • 插入删除vector 在中间插入删除较慢(O(n)),list 在任意位置插入删除较快(O(1))
  • 内存使用vector 内存连续,内存使用更高效;list 每个节点有额外指针,内存开销较大
  • 缓存友好性vector 内存连续,缓存命中率高;list 内存分散,缓存命中率低

5.3 map 和 unordered_map 的区别是什么?

解答

  • 底层实现map 基于红黑树,unordered_map 基于哈希表
  • 元素顺序map 中的元素按键有序,unordered_map 中的元素无序
  • 查找速度map 查找时间复杂度为 O(log n),unordered_map 平均为 O(1)
  • 内存使用map 内存使用较少,unordered_map 内存使用较多
  • 稳定性map 在元素插入删除时迭代器不会失效(除非删除的是当前迭代器指向的元素),unordered_map 在元素插入时可能会因为重新哈希而使所有迭代器失效
  • 自定义类型map 需要自定义类型实现 < 运算符,unordered_map 需要实现哈希函数和 == 运算符

5.4 什么是容器适配器?

解答:容器适配器是对现有容器的封装,提供特定的接口和行为。STL 中的容器适配器包括:

  • stack:提供后进先出(LIFO)的接口
  • queue:提供先进先出(FIFO)的接口
  • priority_queue:提供优先级队列的接口

容器适配器默认使用 deque 作为底层容器,但也可以指定其他容器,只要满足相应的接口要求。

5.5 如何在容器中存储自定义类型?

解答

  • 对于序列容器(如 vectorlist):只需要自定义类型有适当的构造函数和析构函数
  • 对于有序容器(如 setmap):需要自定义类型实现 < 运算符,或者在声明容器时提供自定义比较器
  • 对于无序容器(如 unordered_setunordered_map):需要为自定义类型提供哈希函数和 == 运算符,或者在声明容器时提供自定义哈希器和相等性比较器

5.6 如何处理容器的内存泄漏?

解答

  • 使用智能指针:对于存储指针的容器,使用 std::unique_ptrstd::shared_ptr 管理内存
  • 手动释放:如果使用原始指针,确保在容器销毁前手动释放所有指针指向的内存
  • 避免循环引用:使用 std::weak_ptr 避免 std::shared_ptr 的循环引用
  • 使用 RAII 原则:资源获取即初始化,确保资源在对象销毁时自动释放

5.7 如何提高容器的性能?

解答

  • 预分配内存:对于 vector,使用 reserve() 预分配内存
  • 选择合适的容器:根据实际使用场景选择最适合的容器
  • 使用移动语义:对于大对象,使用移动构造和移动赋值减少拷贝开销
  • 避免不必要的拷贝:使用引用传递和 const 引用
  • 合理设置哈希表参数:对于无序容器,合理设置桶大小和负载因子
  • 使用 emplace 系列函数:直接在容器中构造元素,避免拷贝或移动

6. 代码优化建议

6.1 容器的选择和使用

  • 根据访问模式选择容器

    • 需要随机访问:vectordequearray
    • 需要频繁插入删除:list
    • 需要两端操作:deque
    • 需要自动排序:setmap
    • 需要快速查找:unordered_setunordered_map
  • 避免不必要的拷贝

    • 使用 emplace_back() 代替 push_back() 插入元素
    • 使用移动语义(std::move)转移所有权
    • 对于大对象,考虑使用指针或引用
  • 内存管理

    • 对于 vector,使用 reserve() 预分配内存
    • 对于不再使用的容器,及时 clear() 或析构
    • 对于存储指针的容器,使用智能指针管理内存

6.2 迭代器的使用

  • 避免无效迭代器

    • 在插入删除操作后,注意迭代器可能失效
    • 对于 vector,插入操作可能导致所有迭代器失效
    • 对于 unordered_map,插入操作可能导致所有迭代器失效
  • 使用范围 for 循环

    • 对于遍历整个容器,使用范围 for 循环更简洁
    • 对于需要修改元素的情况,使用引用
  • 使用 const_iterator

    • 对于只读操作,使用 const_iterator 提高安全性和可读性

6.3 性能优化

  • 使用适当的算法

    • 对于排序,使用 std::sort
    • 对于查找,使用 std::find 或容器的 find 成员函数
    • 对于计数,使用 std::count 或容器的 count 成员函数
  • 批量操作

    • 对于多个插入操作,考虑批量插入
    • 对于需要多次修改的操作,考虑先修改再插入
  • 避免频繁重新哈希

    • 对于无序容器,预先设置足够的桶大小
    • 使用 reserve() 预分配空间

6.4 代码可读性和可维护性

  • 使用类型别名

    • 使用 typedefusing 为复杂的容器类型创建别名
    • 提高代码可读性和可维护性
  • 添加注释

    • 对于复杂的容器操作,添加注释说明
    • 说明选择特定容器的原因
  • 异常安全

    • 确保容器操作的异常安全性
    • 使用 RAII 原则管理资源

7. 总结

C++ STL 提供了丰富的容器类型,每种容器都有其特定的用途和性能特点。选择合适的容器对于提高程序的性能和可维护性至关重要。

7.1 容器分类总结

  • 序列容器:保持元素的插入顺序,包括 vectorlistdequearray
  • 容器适配器:提供特定的接口,包括 stackqueuepriority_queue
  • 关联容器:按键排序,包括 setmultisetmapmultimap
  • 无序关联容器:基于哈希表,包括 unordered_setunordered_map
  • 元组和对:用于存储多个值,包括 pairtuple

7.2 容器选择指南

  1. 首先考虑使用 vector

    • 大多数情况下,vector 是最通用和高效的选择
    • 只有在特定场景下才考虑其他容器
  2. 需要频繁插入删除时

    • 考虑使用 listdeque
  3. 需要有序元素时

    • 考虑使用 setmap 等关联容器
  4. 需要快速查找时

    • 考虑使用 unordered_setunordered_map 等无序容器
  5. 需要固定大小数组时

    • 考虑使用 array
  6. 需要特定的数据结构时

    • 栈操作:stack
    • 队列操作:queue
    • 优先级操作:priority_queue

7.3 最佳实践

  • 选择合适的容器:根据实际使用场景选择最适合的容器
  • 了解容器的性能特点:掌握各种容器的时间复杂度
  • 使用现代 C++ 特性:利用移动语义、emplace 操作、智能指针等
  • 注意内存管理:避免内存泄漏和不必要的内存使用
  • 编写清晰的代码:使用类型别名和注释提高代码可读性
  • 测试和基准测试:在关键场景下测试不同容器的性能

通过合理选择和使用 STL 容器,可以显著提高 C++ 程序的性能和可维护性。希望本指南能够帮助您更好地理解和使用 C++ STL 容器。

stl_containers.cpp

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
#include <iostream>
#include <vector>
#include <list>
#include <deque>
#include <queue>
#include <stack>
#include <set>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <algorithm>
#include <tuple>
#include <array>

int main() {
std::cout << "=== 1. std::vector(动态数组)===" << std::endl;
// 创建并初始化vector
std::vector<int> vec = {1, 2, 3, 4, 5};
// 在末尾添加元素
vec.push_back(6);
// 插入元素
vec.insert(vec.begin() + 2, 100);

std::cout << "Vector元素: ";
for (int x : vec) {
std::cout << x << " ";
}
std::cout << "\n大小: " << vec.size() << std::endl;
std::cout << "容量: " << vec.capacity() << std::endl;
std::cout << "是否为空: " << (vec.empty() ? "是" : "否") << std::endl;

// 删除元素
vec.erase(vec.begin() + 2);
std::cout << "删除元素后: ";
for (int x : vec) {
std::cout << x << " ";
}
std::cout << std::endl;

// 清空vector
vec.clear();
std::cout << "清空后大小: " << vec.size() << std::endl;

std::cout << "\n=== 2. std::list(双向链表)===" << std::endl;
// 创建并初始化list
std::list<int> lst = {10, 20, 30};
// 在头部添加元素
lst.push_front(5);
// 在尾部添加元素
lst.push_back(40);
// 在指定位置插入元素
auto it = lst.begin();
++it; // 移动到第二个元素
lst.insert(it, 15);

std::cout << "List元素: ";
for (int x : lst) {
std::cout << x << " ";
}
std::cout << "\n大小: " << lst.size() << std::endl;

// 删除元素
lst.remove(20); // 删除所有值为20的元素
std::cout << "删除元素后: ";
for (int x : lst) {
std::cout << x << " ";
}
std::cout << std::endl;

// 排序
lst.sort();
std::cout << "排序后: ";
for (int x : lst) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 3. std::deque(双端队列)===" << std::endl;
// 创建并初始化deque
std::deque<int> dq = {1, 2, 3};
// 在头部添加元素
dq.push_front(0);
// 在尾部添加元素
dq.push_back(4);

std::cout << "Deque元素: ";
for (int x : dq) {
std::cout << x << " ";
}
std::cout << "\n大小: " << dq.size() << std::endl;

// 删除头部和尾部元素
dq.pop_front();
dq.pop_back();
std::cout << "删除头尾后: ";
for (int x : dq) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 4. std::queue(队列)===" << std::endl;
// 创建queue
std::queue<int> q;
// 入队
q.push(1);
q.push(2);
q.push(3);

std::cout << "Queue元素(FIFO): ";
while (!q.empty()) {
std::cout << q.front() << " "; // 访问队首元素
q.pop(); // 出队
}
std::cout << std::endl;

std::cout << "\n=== 5. std::stack(栈)===" << std::endl;
// 创建stack
std::stack<int> st;
// 入栈
st.push(1);
st.push(2);
st.push(3);

std::cout << "Stack元素(LIFO): ";
while (!st.empty()) {
std::cout << st.top() << " "; // 访问栈顶元素
st.pop(); // 出栈
}
std::cout << std::endl;

std::cout << "\n=== 6. std::priority_queue(优先队列)===" << std::endl;
// 创建优先队列(默认是最大堆)
std::priority_queue<int> pq;
// 入队
pq.push(30);
pq.push(10);
pq.push(50);
pq.push(20);

std::cout << "优先队列元素(从大到小): ";
while (!pq.empty()) {
std::cout << pq.top() << " "; // 访问优先级最高的元素
pq.pop(); // 出队
}
std::cout << std::endl;

// 创建最小堆
std::priority_queue<int, std::vector<int>, std::greater<int>> min_pq;
min_pq.push(30);
min_pq.push(10);
min_pq.push(50);
min_pq.push(20);

std::cout << "最小堆元素(从小到大): ";
while (!min_pq.empty()) {
std::cout << min_pq.top() << " ";
min_pq.pop();
}
std::cout << std::endl;

std::cout << "\n=== 7. std::set(有序集合)===" << std::endl;
// 创建并初始化set(自动排序,去重)
std::set<int> s = {5, 2, 8, 1, 9, 2, 5};
// 插入元素
s.insert(7);

std::cout << "Set元素(有序,唯一): ";
for (int x : s) {
std::cout << x << " ";
}
std::cout << "\n大小: " << s.size() << std::endl;
std::cout << "是否包含5: " << (s.count(5) ? "是" : "否") << std::endl;

// 查找元素
auto find_it = s.find(8);
if (find_it != s.end()) {
std::cout << "找到元素: " << *find_it << std::endl;
}

// 删除元素
s.erase(5);
std::cout << "删除5后: ";
for (int x : s) {
std::cout << x << " ";
}
std::cout << std::endl;

std::cout << "\n=== 8. std::multiset(有序多重集合)===" << std::endl;
// 创建并初始化multiset(自动排序,允许重复)
std::multiset<int> ms = {5, 2, 8, 1, 9, 2, 5};

std::cout << "Multiset元素(有序,允许重复): ";
for (int x : ms) {
std::cout << x << " ";
}
std::cout << "\n5的数量: " << ms.count(5) << std::endl;

std::cout << "\n=== 9. std::map(有序映射)===" << std::endl;
// 创建map
std::map<std::string, int> m;
// 插入键值对
m["apple"] = 5;
m["banana"] = 3;
m["cherry"] = 8;
m.insert({"date", 2});

std::cout << "Map元素(键值对):" << std::endl;
for (const auto& pair : m) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
std::cout << "banana的值: " << m["banana"] << std::endl;

// 查找元素
auto map_it = m.find("cherry");
if (map_it != m.end()) {
std::cout << "找到键: " << map_it->first << ", 值: " << map_it->second << std::endl;
}

std::cout << "\n=== 10. std::multimap(有序多重映射)===" << std::endl;
// 创建multimap(允许重复键)
std::multimap<std::string, int> mm;
// 插入键值对
mm.insert({"fruit", 1});
mm.insert({"fruit", 2});
mm.insert({"vegetable", 3});

std::cout << "Multimap元素:" << std::endl;
for (const auto& pair : mm) {
std::cout << pair.first << ": " << pair.second << std::endl;
}

std::cout << "\n=== 11. std::unordered_set(无序集合)===" << std::endl;
// 创建并初始化unordered_set(无序,去重,哈希表实现)
std::unordered_set<int> us = {5, 2, 8, 1, 9, 2, 5};

std::cout << "Unordered set元素(无序,唯一): ";
for (int x : us) {
std::cout << x << " ";
}
std::cout << "\n大小: " << us.size() << std::endl;
std::cout << "是否包含3: " << (us.count(3) ? "是" : "否") << std::endl;

std::cout << "\n=== 12. std::unordered_map(无序映射)===" << std::endl;
// 创建unordered_map(无序,哈希表实现)
std::unordered_map<std::string, int> um;
// 插入键值对
um["one"] = 1;
um["two"] = 2;
um["three"] = 3;

std::cout << "Unordered map元素(无序):" << std::endl;
for (const auto& pair : um) {
std::cout << pair.first << ": " << pair.second << std::endl;
}

// 查看桶信息
std::cout << "桶数: " << um.bucket_count() << std::endl;
std::cout << "最大桶数: " << um.max_bucket_count() << std::endl;

std::cout << "\n=== 13. std::pair(键值对)===" << std::endl;
// 创建pair
std::pair<std::string, int> myPair("age", 25);
// 访问pair元素
std::cout << "Pair: " << myPair.first << " = " << myPair.second << std::endl;

// 使用make_pair创建pair
auto anotherPair = std::make_pair("height", 180);
std::cout << "Another pair: " << anotherPair.first << " = " << anotherPair.second << std::endl;

std::cout << "\n=== 14. std::tuple(元组)===" << std::endl;
// 创建tuple
std::tuple<std::string, int, double> myTuple("John", 30, 3.14);
// 访问tuple元素(通过索引)
std::cout << "Tuple: " << std::get<0>(myTuple) << ", "
<< std::get<1>(myTuple) << ", "
<< std::get<2>(myTuple) << std::endl;

// 使用make_tuple创建tuple
auto anotherTuple = std::make_tuple("Alice", 25, 2.718);
// 使用结构化绑定(C++17特性)
auto [name, age, value] = anotherTuple;
std::cout << "Another tuple: " << name << ", " << age << ", " << value << std::endl;

std::cout << "\n=== 15. std::array(固定大小数组)===" << std::endl;
// 创建array(固定大小)
std::array<int, 5> arr = {1, 2, 3, 4, 5};

std::cout << "Array元素: ";
for (int x : arr) {
std::cout << x << " ";
}
std::cout << "\n大小: " << arr.size() << std::endl;
std::cout << "是否为空: " << (arr.empty() ? "是" : "否") << std::endl;

// 访问元素
std::cout << "第一个元素: " << arr.front() << std::endl;
std::cout << "最后一个元素: " << arr.back() << std::endl;
std::cout << "索引2的元素: " << arr[2] << std::endl;

return 0;
}

C++ 模板(Templates)详细指南

1. 功能说明

本指南基于 templates.cpp 文件,详细介绍了 C++ 中模板的各种类型和用法,包括:

  • 函数模板:实现通用的函数,如最大值、最小值、交换值等
  • 类模板:实现通用的类,如盒子、栈、数组、对、智能指针等
  • 非类型模板参数:使用常量表达式作为模板参数
  • 模板特化:为特定类型提供专门的实现
  • 变参模板:处理可变数量的模板参数
  • 模板的实例化:隐式实例化和显式实例化

2. 代码解析

2.1 函数模板

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
// 1. 模板函数 - 最大值
template<typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}

// 2. 模板函数 - 最小值
template<typename T>
T minimum(T a, T b) {
return (a < b) ? a : b;
}

// 3. 模板函数 - 交换值
template<typename T>
void swapValues(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}

// 4. 模板函数 - 加法
template<typename T>
T add(T a, T b) {
return a + b;
}

// 5. 模板函数 - 混合类型加法(使用decltype自动推导返回类型)
template<typename T, typename U>
auto addMixed(T a, U b) -> decltype(a + b) {
return a + b;
}

// 6. 模板函数 - 数组求和
template<typename T, size_t N>
T sumArray(const T(&arr)[N]) {
T sum = 0;
for (size_t i = 0; i < N; ++i) {
sum += arr[i];
}
return sum;
}

解析

  • 函数模板使用 template<typename T>template<class T> 声明
  • typenameclass 在模板参数声明中是等价的
  • 可以有多个模板参数,如 template<typename T, typename U>
  • 可以使用 decltype 自动推导返回类型(C++11 特性)
  • 可以使用非类型模板参数,如 template<typename T, size_t N> 中的 size_t N

2.2 类模板

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
// 7. 模板类 - 盒子(存储单个值)
template<typename T>
class Box {
private:
T value; // 存储的值

public:
// 构造函数
Box(T v) : value(v) {}

// 获取值
T getValue() const {
return value;
}

// 设置值
void setValue(T v) {
value = v;
}

// 显示内容
void display() const {
std::cout << "Box contains: " << value << std::endl;
}
};

// 8. 模板类 - 栈
template<typename T>
class Stack {
private:
T* elements; // 元素数组
int top; // 栈顶索引
int capacity; // 栈容量

public:
// 构造函数
Stack(int size = 10) : top(-1), capacity(size) {
elements = new T[capacity];
}

// 析构函数
~Stack() {
delete[] elements;
}

// 入栈
void push(T item) {
if (top < capacity - 1) {
elements[++top] = item;
} else {
std::cout << "Stack is full!" << std::endl;
}
}

// 出栈
T pop() {
if (top >= 0) {
return elements[top--];
}
std::cout << "Stack is empty!" << std::endl;
return T();
}

// 检查是否为空
bool isEmpty() const {
return top == -1;
}

// 检查是否已满
bool isFull() const {
return top == capacity - 1;
}
};

解析

  • 类模板使用 template<typename T> 声明
  • 模板类的成员函数可以在类内部定义,也可以在类外部定义
  • 在类外部定义模板类的成员函数时,需要指定模板参数
  • 模板类可以有多个模板参数,如 template<typename T1, typename T2>

2.3 带非类型参数的模板类

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
// 9. 模板类 - 数组(带非类型参数)
template<typename T, int N>
class Array {
private:
T data[N]; // 固定大小的数组

public:
// 下标运算符重载(非const版本)
T& operator[](int index) {
return data[index];
}

// 下标运算符重载(const版本)
const T& operator[](int index) const {
return data[index];
}

// 获取数组大小
int size() const {
return N;
}

// 填充数组
void fill(T value) {
for (int i = 0; i < N; ++i) {
data[i] = value;
}
}

// 显示数组内容
void display() const {
std::cout << "Array elements: ";
for (int i = 0; i < N; ++i) {
std::cout << data[i] << " ";
}
std::cout << std::endl;
}
};

解析

  • 非类型模板参数必须是常量表达式
  • 非类型模板参数可以是整数类型、枚举类型、指针类型、引用类型等
  • 非类型模板参数在编译时被求值,因此必须是编译时常量
  • 在上面的例子中,int N 是一个非类型模板参数,用于指定数组的大小

2.4 模板特化

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
// 15. 模板特化 - 针对const char*类型的Box特化
template<>
class Box<const char*> {
private:
std::string value; // 使用std::string存储字符串

public:
// 构造函数
Box(const char* v) : value(v) {}

// 获取值
const char* getValue() const {
return value.c_str();
}

// 设置值
void setValue(const char* v) {
value = v;
}

// 显示内容
void display() const {
std::cout << "Box contains: " << value << std::endl;
}
};

解析

  • 模板特化使用 template<> 声明
  • 特化版本必须与原模板的接口保持一致
  • 特化版本可以有不同的实现,以适应特定类型的需求
  • 在上面的例子中,为 const char* 类型提供了特化版本,使用 std::string 来存储字符串,避免了字符指针的问题

2.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
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 10. 模板类 - 对(相同类型)
template<typename T>
class Pair {
private:
T first; // 第一个元素
T second; // 第二个元素

public:
// 构造函数
Pair(T a, T b) : first(a), second(b) {}

// 获取第一个元素
T getFirst() const {
return first;
}

// 获取第二个元素
T getSecond() const {
return second;
}

// 设置第一个元素
void setFirst(T value) {
first = value;
}

// 设置第二个元素
void setSecond(T value) {
second = value;
}

// 显示内容
void display() const {
std::cout << "Pair: (" << first << ", " << second << ")" << std::endl;
}
};

// 11. 模板类 - 对(不同类型)
template<typename T1, typename T2>
class Pair2 {
private:
T1 first; // 第一个元素(类型T1)
T2 second; // 第二个元素(类型T2)

public:
// 构造函数
Pair2(T1 a, T2 b) : first(a), second(b) {}

// 获取第一个元素
T1 getFirst() const {
return first;
}

// 获取第二个元素
T2 getSecond() const {
return second;
}

// 显示内容
void display() const {
std::cout << "Pair: (" << first << ", " << second << ")" << std::endl;
}
};

// 12. 模板类 - 智能指针(简化版)
template<typename T>
class SmartPointer {
private:
T* ptr; // 原始指针

public:
// 构造函数
SmartPointer(T* p = nullptr) : ptr(p) {}

// 析构函数
~SmartPointer() {
delete ptr;
}

// 解引用运算符重载
T& operator*() {
return *ptr;
}

// 箭头运算符重载
T* operator->() {
return ptr;
}

// 获取原始指针
T* get() const {
return ptr;
}
};

// 13. 模板类 - 链表节点
template<typename T>
class Node {
public:
T data; // 节点数据
Node* next; // 指向下一个节点的指针

// 构造函数
Node(T value) : data(value), next(nullptr) {}
};

// 14. 模板类 - 单链表
template<typename T>
class LinkedList {
private:
Node<T>* head; // 链表头节点

public:
// 构造函数
LinkedList() : head(nullptr) {}

// 析构函数
~LinkedList() {
Node<T>* current = head;
while (current) {
Node<T>* next = current->next;
delete current;
current = next;
}
}

// 添加节点到链表末尾
void add(T value) {
Node<T>* newNode = new Node<T>(value);
if (!head) {
head = newNode;
} else {
Node<T>* current = head;
while (current->next) {
current = current->next;
}
current->next = newNode;
}
}

// 显示链表内容
void display() const {
Node<T>* current = head;
std::cout << "LinkedList: ";
while (current) {
std::cout << current->data << " -> ";
current = current->next;
}
std::cout << "nullptr" << std::endl;
}
};

解析

  • Pair 类模板演示了如何使用单个模板参数创建存储相同类型元素的对
  • Pair2 类模板演示了如何使用多个模板参数创建存储不同类型元素的对
  • SmartPointer 类模板演示了如何创建一个简单的智能指针
  • NodeLinkedList 类模板演示了如何创建一个通用的单链表

2.6 主函数 - 测试各种模板

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
int main() {
std::cout << "=== 1. 模板函数 ===" << std::endl;
std::cout << "最大值 of 10 and 20: " << maximum(10, 20) << std::endl;
std::cout << "最大值 of 3.14 and 2.71: " << maximum(3.14, 2.71) << std::endl;
std::cout << "最大值 of 'a' and 'z': " << maximum('a', 'z') << std::endl;

std::cout << "\n最小值 of 10 and 20: " << minimum(10, 20) << std::endl;
std::cout << "最小值 of 3.14 and 2.71: " << minimum(3.14, 2.71) << std::endl;

int x = 5, y = 10;
std::cout << "\n交换前: x = " << x << ", y = " << y << std::endl;
swapValues(x, y);
std::cout << "交换后: x = " << x << ", y = " << y << std::endl;

double a = 1.5, b = 2.5;
std::cout << "交换前: a = " << a << ", b = " << b << std::endl;
swapValues(a, b);
std::cout << "交换后: a = " << a << ", b = " << b << std::endl;

std::cout << "Add(3, 4) = " << add(3, 4) << std::endl;
std::cout << "Add(3.5, 4.5) = " << add(3.5, 4.5) << std::endl;
std::cout << "AddMixed(3, 4.5) = " << addMixed(3, 4.5) << std::endl;

// 测试数组求和函数
int intArray[] = {1, 2, 3, 4, 5};
double doubleArray[] = {1.1, 2.2, 3.3, 4.4};
std::cout << "\n数组求和: " << sumArray(intArray) << std::endl;
std::cout << "数组求和: " << sumArray(doubleArray) << std::endl;

std::cout << "\n=== 2. 模板类 Box ===" << std::endl;
Box<int> intBox(42);
intBox.display();

Box<double> doubleBox(3.14159);
doubleBox.display();

Box<std::string> stringBox("Hello Template!");
stringBox.display();

// 测试模板特化
Box<const char*> charBox("Specialized Template");
charBox.display();

std::cout << "\n=== 3. 模板类 Stack ===" << std::endl;
Stack<int> intStack(5);
intStack.push(10);
intStack.push(20);
intStack.push(30);

while (!intStack.isEmpty()) {
std::cout << "弹出: " << intStack.pop() << std::endl;
}

Stack<std::string> stringStack(3);
stringStack.push("First");
stringStack.push("Second");
stringStack.push("Third");

while (!stringStack.isEmpty()) {
std::cout << "弹出: " << stringStack.pop() << std::endl;
}

std::cout << "\n=== 4. 带非类型参数的模板类 Array ===" << std::endl;
Array<int, 5> intArrayObj;
for (int i = 0; i < intArrayObj.size(); ++i) {
intArrayObj[i] = i * 10;
}

intArrayObj.display();

// 测试fill方法
Array<double, 3> doubleArrayObj;
doubleArrayObj.fill(3.14);
doubleArrayObj.display();

std::cout << "\n=== 5. 模板类 Pair ===" << std::endl;
Pair<int> intPair(10, 20);
intPair.display();

Pair<double> doublePair(3.14, 2.71);
doublePair.display();

Pair2<std::string, int> mixedPair("Age", 25);
mixedPair.display();

Pair2<int, double> mixedPair2(100, 99.99);
mixedPair2.display();

std::cout << "\n=== 6. 模板类 SmartPointer ===" << std::endl;
SmartPointer<int> smartPtr(new int(100));
std::cout << "智能指针值: " << *smartPtr << std::endl;

SmartPointer<std::string> stringPtr(new std::string("Smart String"));
std::cout << "智能指针值: " << *stringPtr << std::endl;

std::cout << "\n=== 7. 模板类 LinkedList ===" << std::endl;
LinkedList<int> intList;
intList.add(10);
intList.add(20);
intList.add(30);
intList.display();

LinkedList<std::string> stringList;
stringList.add("Hello");
stringList.add("World");
stringList.display();

return 0;
}

解析

  • 测试了各种模板函数的使用,包括自动类型推导
  • 测试了各种模板类的使用,包括不同类型的实例化
  • 测试了模板特化的使用
  • 测试了带非类型参数的模板类的使用
  • 展示了模板在实际应用中的灵活性和通用性

3. 编译和运行说明

3.1 编译命令

使用以下命令编译 templates.cpp 文件:

1
g++ -std=c++11 -fexec-charset=GBK -o templates templates.cpp

参数说明:

  • -std=c++11:使用 C++11 标准
  • -fexec-charset=GBK:确保输出的中文字符正确显示
  • -o templates:指定输出可执行文件名为 templates

3.2 运行命令

编译成功后,使用以下命令运行程序:

1
2
./templates  # Linux/macOS
.\templates.exe # Windows

3.3 预期输出

运行程序后,您将看到类似以下的输出:

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
=== 1. 模板函数 ===
最大值 of 10 and 20: 20
最大值 of 3.14 and 2.71: 3.14
最大值 of 'a' and 'z': z

最小值 of 10 and 20: 10
最小值 of 3.14 and 2.71: 2.71

交换前: x = 5, y = 10
交换后: x = 10, y = 5
交换前: a = 1.5, b = 2.5
交换后: a = 2.5, b = 1.5
Add(3, 4) = 7
Add(3.5, 4.5) = 8
AddMixed(3, 4.5) = 7.5

数组求和: 15
数组求和: 11

=== 2. 模板类 Box ===
Box contains: 42
Box contains: 3.14159
Box contains: Hello Template!
Box contains: Specialized Template

=== 3. 模板类 Stack ===
弹出: 30
弹出: 20
弹出: 10
弹出: Third
弹出: Second
弹出: First
=== 4. 带非类型参数的模板类 Array ===
Array elements: 0 10 20 30 40
Array elements: 3.14 3.14 3.14

=== 5. 模板类 Pair ===
Pair: (10, 20)
Pair: (3.14, 2.71)
Pair: (Age, 25)
Pair: (100, 99.99)

=== 6. 模板类 SmartPointer ===
智能指针值: 100
智能指针值: Smart String

=== 7. 模板类 LinkedList ===
LinkedList: 10 -> 20 -> 30 -> nullptr
LinkedList: Hello -> World -> nullptr

4. 技术要点

4.1 模板的基本概念

  • 模板:是 C++ 的一种编程范式,允许创建通用的函数和类
  • 模板参数:在模板定义中使用的占位符,可以是类型参数或非类型参数
  • 模板实例化:根据具体类型创建模板的实例
  • 模板特化:为特定类型提供专门的实现

4.2 函数模板

  • 声明方式template<typename T> ReturnType functionName(Parameters)
  • 类型推导:编译器可以根据实参自动推导模板参数类型
  • 显式实例化:可以使用 <Type> 显式指定模板参数类型
  • 多个模板参数:可以有多个类型参数,如 template<typename T, typename U>
  • 默认模板参数:C++11 允许为模板参数提供默认值

4.3 类模板

  • 声明方式template<typename T> class ClassName { ... }
  • 实例化方式ClassName<Type> objectName(Arguments)
  • 成员函数:可以在类内部或外部定义,外部定义时需要指定模板参数
  • 静态成员:每个模板实例都有自己的静态成员副本
  • 模板作为参数:可以将模板类作为另一个模板的参数

4.4 非类型模板参数

  • 类型限制:必须是整数类型、枚举类型、指针类型、引用类型或 std::nullptr_t
  • 常量表达式:必须是编译时常量表达式
  • 使用场景:常用于指定数组大小、缓冲区大小等固定值
  • 示例template<typename T, int Size> class Array { ... }

4.5 模板特化

  • 全特化:为所有模板参数提供具体类型,如 template<> class Box<const char*> { ... }
  • 偏特化:为部分模板参数提供具体类型,如 template<typename T> class Pair<T, T> { ... }
  • 特化顺序:编译器会优先选择最匹配的特化版本
  • 特化的必要性:处理某些类型的特殊需求,如字符串指针

4.6 变参模板

  • 声明方式template<typename... Args> ReturnType functionName(Args... args)
  • 参数包Args... 表示模板参数包,args... 表示函数参数包
  • 展开参数包:使用递归或逗号表达式展开参数包
  • 使用场景:实现可变参数函数,如 std::tuplestd::function

4.7 模板的编译模型

  • 包含模型:模板定义必须在使用前可见,通常放在头文件中
  • 显式实例化模型:模板声明放在头文件中,定义放在源文件中,然后显式实例化
  • 编译错误:模板错误通常在实例化时才会被发现,称为 “late binding”

4.8 模板的优缺点

优点

  • 代码重用:编写一次代码,适用于多种类型
  • 类型安全:在编译时进行类型检查
  • 性能:没有运行时开销,因为模板在编译时实例化
  • 灵活性:可以创建高度通用的代码

缺点

  • 编译时间:模板会增加编译时间和目标代码大小
  • 错误信息:模板错误信息通常比较复杂,难以理解
  • 代码可读性:复杂的模板代码可能难以阅读和维护

5. 常见问题解答

5.1 什么是模板参数推导?

模板参数推导是指编译器根据函数调用的实参自动确定模板参数类型的过程。例如,当调用 maximum(10, 20) 时,编译器会推导出 Tint 类型。

5.2 如何处理模板中的类型不匹配问题?

  • 使用类型转换:确保实参类型可以隐式转换为模板参数类型
  • 显式指定模板参数:使用 <Type> 显式指定模板参数类型
  • 使用类型萃取:使用 std::enable_ifstd::is_same 等类型特性进行编译时类型检查
  • 提供特化版本:为特定类型提供专门的实现

5.3 模板特化和函数重载有什么区别?

  • 模板特化:是模板的一种特殊形式,为特定类型提供专门的实现
  • 函数重载:是为不同参数列表的函数提供不同的实现
  • 选择顺序:编译器会优先选择非模板函数,然后是模板特化,最后是通用模板

5.4 如何避免模板代码膨胀?

  • 使用共享实现:将通用代码提取到非模板基类中
  • 使用类型擦除:如 std::function 那样使用虚函数表
  • 显式实例化:只实例化需要的模板版本
  • 使用模板参数约束:减少不必要的实例化

5.5 什么是 SFINAE?

SFINAE (Substitution Failure Is Not An Error) 是 C++ 中的一个规则,指的是当模板参数替换失败时,这不是一个错误,而是会尝试其他重载版本。SFINAE 常用于模板元编程中进行编译时类型检查。

5.6 如何在模板中使用默认参数?

  • 函数模板template<typename T = int> T functionName(T a = T())
  • 类模板template<typename T = int> class ClassName { ... }
  • 非类型模板参数template<typename T, int N = 10> class Array { ... }

5.7 如何处理模板中的继承问题?

  • 模板类作为基类template<typename T> class Derived : public Base<T> { ... }
  • 基类依赖:在派生类中使用基类的成员时,需要使用 this->Base<T>:: 限定
  • 模板特化与继承:可以为派生类提供特化版本

6. 代码优化建议

6.1 合理使用模板

  • 只在必要时使用模板:对于简单的函数,普通函数可能更合适
  • 避免过度泛化:不要为了泛化而泛化,保持代码简洁
  • 考虑编译时间:过多的模板可能会显著增加编译时间

6.2 模板参数约束

  • 使用 C++20 概念:使用 concept 关键字约束模板参数类型
  • 使用类型特性:使用 std::enable_if 等类型特性进行编译时类型检查
  • 提供清晰的错误信息:当模板参数不符合要求时,提供明确的错误信息

6.3 模板代码组织

  • 声明与定义分离:对于简单模板,声明和定义都放在头文件中
  • 使用显式实例化:对于复杂模板,考虑使用显式实例化减少编译时间
  • 使用头文件保护:避免头文件重复包含

6.4 性能优化

  • 避免不必要的实例化:只实例化需要的模板版本
  • 使用移动语义:在模板中使用移动构造函数和移动赋值运算符
  • 考虑内联:对于简单的模板函数,考虑使用 inline 关键字

6.5 可读性和可维护性

  • 使用有意义的模板参数名:如 TValueTPolicyT
  • 添加注释:为模板参数和模板函数添加清晰的注释
  • 使用别名模板:使用 using 声明创建模板别名,提高可读性
  • 避免深层嵌套:避免过于复杂的模板嵌套,保持代码结构清晰

6.6 常见陷阱避免

  • 模板参数推导失败:确保实参类型与模板参数类型匹配
  • 二义性:避免模板特化和重载导致的二义性
  • 虚函数与模板:模板成员函数不能是虚函数
  • 默认模板参数:注意默认模板参数的顺序和依赖关系
  • 非类型模板参数的限制:确保非类型模板参数是编译时常量

7. 总结

模板是 C++ 中一种强大的编程工具,它允许创建通用的函数和类,提高代码重用性和类型安全性。通过本文的学习,您应该掌握了:

  • 函数模板的声明和使用
  • 类模板的声明和使用
  • 非类型模板参数的使用
  • 模板特化的实现和应用
  • 模板的编译和实例化机制
  • 模板的优缺点和最佳实践

合理使用模板可以使代码更加灵活、通用和高效,但也需要注意避免过度使用导致的编译时间增加和代码复杂性提高。在实际开发中,应根据具体需求选择合适的模板使用方式,平衡通用性和性能。

C++11 及以后的标准引入了许多模板相关的新特性,如类型推导、变参模板、模板别名等,这些特性进一步增强了模板的能力和易用性。随着 C++ 标准的不断发展,模板的功能和性能也在不断改进,为 C++ 程序员提供了更强大的工具。

templates.cpp

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

// 1. 模板函数 - 最大值
template<typename T>
T maximum(T a, T b) {
return (a > b) ? a : b;
}

// 2. 模板函数 - 最小值
template<typename T>
T minimum(T a, T b) {
return (a < b) ? a : b;
}

// 3. 模板函数 - 交换值
template<typename T>
void swapValues(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}

// 4. 模板函数 - 加法
template<typename T>
T add(T a, T b) {
return a + b;
}

// 5. 模板函数 - 混合类型加法(使用decltype自动推导返回类型)
template<typename T, typename U>
auto addMixed(T a, U b) -> decltype(a + b) {
return a + b;
}

// 6. 模板函数 - 数组求和
template<typename T, size_t N>
T sumArray(const T(&arr)[N]) {
T sum = 0;
for (size_t i = 0; i < N; ++i) {
sum += arr[i];
}
return sum;
}

// 7. 模板类 - 盒子(存储单个值)
template<typename T>
class Box {
private:
T value; // 存储的值

public:
// 构造函数
Box(T v) : value(v) {}

// 获取值
T getValue() const {
return value;
}

// 设置值
void setValue(T v) {
value = v;
}

// 显示内容
void display() const {
std::cout << "Box contains: " << value << std::endl;
}
};

// 8. 模板类 - 栈
template<typename T>
class Stack {
private:
T* elements; // 元素数组
int top; // 栈顶索引
int capacity; // 栈容量

public:
// 构造函数
Stack(int size = 10) : top(-1), capacity(size) {
elements = new T[capacity];
}

// 析构函数
~Stack() {
delete[] elements;
}

// 入栈
void push(T item) {
if (top < capacity - 1) {
elements[++top] = item;
} else {
std::cout << "Stack is full!" << std::endl;
}
}

// 出栈
T pop() {
if (top >= 0) {
return elements[top--];
}
std::cout << "Stack is empty!" << std::endl;
return T();
}

// 检查是否为空
bool isEmpty() const {
return top == -1;
}

// 检查是否已满
bool isFull() const {
return top == capacity - 1;
}
};

// 9. 模板类 - 数组(带非类型参数)
template<typename T, int N>
class Array {
private:
T data[N]; // 固定大小的数组

public:
// 下标运算符重载(非const版本)
T& operator[](int index) {
return data[index];
}

// 下标运算符重载(const版本)
const T& operator[](int index) const {
return data[index];
}

// 获取数组大小
int size() const {
return N;
}

// 填充数组
void fill(T value) {
for (int i = 0; i < N; ++i) {
data[i] = value;
}
}

// 显示数组内容
void display() const {
std::cout << "Array elements: ";
for (int i = 0; i < N; ++i) {
std::cout << data[i] << " ";
}
std::cout << std::endl;
}
};

// 10. 模板类 - 对(相同类型)
template<typename T>
class Pair {
private:
T first; // 第一个元素
T second; // 第二个元素

public:
// 构造函数
Pair(T a, T b) : first(a), second(b) {}

// 获取第一个元素
T getFirst() const {
return first;
}

// 获取第二个元素
T getSecond() const {
return second;
}

// 设置第一个元素
void setFirst(T value) {
first = value;
}

// 设置第二个元素
void setSecond(T value) {
second = value;
}

// 显示内容
void display() const {
std::cout << "Pair: (" << first << ", " << second << ")" << std::endl;
}
};

// 11. 模板类 - 对(不同类型)
template<typename T1, typename T2>
class Pair2 {
private:
T1 first; // 第一个元素(类型T1)
T2 second; // 第二个元素(类型T2)

public:
// 构造函数
Pair2(T1 a, T2 b) : first(a), second(b) {}

// 获取第一个元素
T1 getFirst() const {
return first;
}

// 获取第二个元素
T2 getSecond() const {
return second;
}

// 显示内容
void display() const {
std::cout << "Pair: (" << first << ", " << second << ")" << std::endl;
}
};

// 12. 模板类 - 智能指针(简化版)
template<typename T>
class SmartPointer {
private:
T* ptr; // 原始指针

public:
// 构造函数
SmartPointer(T* p = nullptr) : ptr(p) {}

// 析构函数
~SmartPointer() {
delete ptr;
}

// 解引用运算符重载
T& operator*() {
return *ptr;
}

// 箭头运算符重载
T* operator->() {
return ptr;
}

// 获取原始指针
T* get() const {
return ptr;
}
};

// 13. 模板类 - 链表节点
template<typename T>
class Node {
public:
T data; // 节点数据
Node* next; // 指向下一个节点的指针

// 构造函数
Node(T value) : data(value), next(nullptr) {}
};

// 14. 模板类 - 单链表
template<typename T>
class LinkedList {
private:
Node<T>* head; // 链表头节点

public:
// 构造函数
LinkedList() : head(nullptr) {}

// 析构函数
~LinkedList() {
Node<T>* current = head;
while (current) {
Node<T>* next = current->next;
delete current;
current = next;
}
}

// 添加节点到链表末尾
void add(T value) {
Node<T>* newNode = new Node<T>(value);
if (!head) {
head = newNode;
} else {
Node<T>* current = head;
while (current->next) {
current = current->next;
}
current->next = newNode;
}
}

// 显示链表内容
void display() const {
Node<T>* current = head;
std::cout << "LinkedList: ";
while (current) {
std::cout << current->data << " -> ";
current = current->next;
}
std::cout << "nullptr" << std::endl;
}
};

// 15. 模板特化 - 针对const char*类型的Box特化
template<>
class Box<const char*> {
private:
std::string value; // 使用std::string存储字符串

public:
// 构造函数
Box(const char* v) : value(v) {}

// 获取值
const char* getValue() const {
return value.c_str();
}

// 设置值
void setValue(const char* v) {
value = v;
}

// 显示内容
void display() const {
std::cout << "Box contains: " << value << std::endl;
}
};

int main() {
std::cout << "=== 1. 模板函数 ===" << std::endl;
std::cout << "最大值 of 10 and 20: " << maximum(10, 20) << std::endl;
std::cout << "最大值 of 3.14 and 2.71: " << maximum(3.14, 2.71) << std::endl;
std::cout << "最大值 of 'a' and 'z': " << maximum('a', 'z') << std::endl;

std::cout << "\n最小值 of 10 and 20: " << minimum(10, 20) << std::endl;
std::cout << "最小值 of 3.14 and 2.71: " << minimum(3.14, 2.71) << std::endl;

int x = 5, y = 10;
std::cout << "\n交换前: x = " << x << ", y = " << y << std::endl;
swapValues(x, y);
std::cout << "交换后: x = " << x << ", y = " << y << std::endl;

double a = 1.5, b = 2.5;
std::cout << "交换前: a = " << a << ", b = " << b << std::endl;
swapValues(a, b);
std::cout << "交换后: a = " << a << ", b = " << b << std::endl;

std::cout << "Add(3, 4) = " << add(3, 4) << std::endl;
std::cout << "Add(3.5, 4.5) = " << add(3.5, 4.5) << std::endl;
std::cout << "AddMixed(3, 4.5) = " << addMixed(3, 4.5) << std::endl;

// 测试数组求和函数
int intArray[] = {1, 2, 3, 4, 5};
double doubleArray[] = {1.1, 2.2, 3.3, 4.4};
std::cout << "\n数组求和: " << sumArray(intArray) << std::endl;
std::cout << "数组求和: " << sumArray(doubleArray) << std::endl;

std::cout << "\n=== 2. 模板类 Box ===" << std::endl;
Box<int> intBox(42);
intBox.display();

Box<double> doubleBox(3.14159);
doubleBox.display();

Box<std::string> stringBox("Hello Template!");
stringBox.display();

// 测试模板特化
Box<const char*> charBox("Specialized Template");
charBox.display();

std::cout << "\n=== 3. 模板类 Stack ===" << std::endl;
Stack<int> intStack(5);
intStack.push(10);
intStack.push(20);
intStack.push(30);

while (!intStack.isEmpty()) {
std::cout << "弹出: " << intStack.pop() << std::endl;
}

Stack<std::string> stringStack(3);
stringStack.push("First");
stringStack.push("Second");
stringStack.push("Third");

while (!stringStack.isEmpty()) {
std::cout << "弹出: " << stringStack.pop() << std::endl;
}

std::cout << "\n=== 4. 带非类型参数的模板类 Array ===" << std::endl;
Array<int, 5> intArrayObj;
for (int i = 0; i < intArrayObj.size(); ++i) {
intArrayObj[i] = i * 10;
}

intArrayObj.display();

// 测试fill方法
Array<double, 3> doubleArrayObj;
doubleArrayObj.fill(3.14);
doubleArrayObj.display();

std::cout << "\n=== 5. 模板类 Pair ===" << std::endl;
Pair<int> intPair(10, 20);
intPair.display();

Pair<double> doublePair(3.14, 2.71);
doublePair.display();

Pair2<std::string, int> mixedPair("Age", 25);
mixedPair.display();

Pair2<int, double> mixedPair2(100, 99.99);
mixedPair2.display();

std::cout << "\n=== 6. 模板类 SmartPointer ===" << std::endl;
SmartPointer<int> smartPtr(new int(100));
std::cout << "智能指针值: " << *smartPtr << std::endl;

SmartPointer<std::string> stringPtr(new std::string("Smart String"));
std::cout << "智能指针值: " << *stringPtr << std::endl;

std::cout << "\n=== 7. 模板类 LinkedList ===" << std::endl;
LinkedList<int> intList;
intList.add(10);
intList.add(20);
intList.add(30);
intList.display();

LinkedList<std::string> stringList;
stringList.add("Hello");
stringList.add("World");
stringList.display();

return 0;
}

C++ 继承(Inheritance)详细指南

1. 功能说明

本指南基于 inheritance.cpp 文件,详细介绍了 C++ 中继承的各种类型和用法,包括:

  • 基本继承:单继承的实现和使用
  • 多态:通过虚函数实现的运行时多态
  • 抽象类:包含纯虚函数的抽象基类
  • 多重继承:一个类同时继承多个基类
  • 虚继承:解决多重继承中的菱形继承问题
  • protected 继承:受保护的继承方式
  • private 继承:私有的继承方式

2. 代码解析

2.1 基本继承 - 动物类层次结构

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
// 1. 动物基类 - 演示基本继承
class Animal {
protected:
std::string name; // 名称
int age; // 年龄

public:
// 构造函数
Animal(const std::string& n, int a) : name(n), age(a) {}

// 虚函数 - 发出声音
virtual void makeSound() const {
std::cout << "一些通用的动物声音" << std::endl;
}

// 虚函数 - 进食
virtual void eat() const {
std::cout << name << " 在吃东西" << std::endl;
}

// 普通成员函数 - 显示信息
void display() const {
std::cout << "名称: " << name << ", 年龄: " << age << std::endl;
}

// 虚析构函数
virtual ~Animal() {
std::cout << "动物析构函数被调用" << std::endl;
}
};

// 狗类 - 继承自动物类
class Dog : public Animal {
private:
std::string breed; // 品种

public:
// 构造函数
Dog(const std::string& n, int a, const std::string& b)
: Animal(n, a), breed(b) {}

// 重写虚函数 - 发出声音
void makeSound() const override {
std::cout << "汪汪!汪汪!" << std::endl;
}

// 特有成员函数 - 取物
void fetch() const {
std::cout << name << " 在取球" << std::endl;
}

// 重写显示信息函数
void display() const {
Animal::display();
std::cout << "品种: " << breed << std::endl;
}

// 析构函数
~Dog() {
std::cout << "狗析构函数被调用" << std::endl;
}
};

解析

  • Animal 是基类,包含了动物的基本属性和方法
  • Dog 是派生类,继承自 Animal,添加了特有的属性 breed 和方法 fetch()
  • 使用 protected 访问修饰符使得派生类可以访问基类的成员
  • virtual 关键字用于声明虚函数,支持运行时多态
  • override 关键字明确表示重写基类的虚函数
  • 虚析构函数确保派生类对象销毁时能正确调用基类的析构函数

2.2 抽象类 - 形状类层次结构

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
// 2. 形状抽象类 - 演示抽象类和纯虚函数
class Shape {
public:
// 纯虚函数 - 计算面积
virtual double area() const = 0;
// 纯虚函数 - 计算周长
virtual double perimeter() const = 0;
// 纯虚函数 - 显示信息
virtual void display() const = 0;

// 虚析构函数
virtual ~Shape() {}
};

// 圆形类 - 继承自形状类
class Circle : public Shape {
private:
double radius; // 半径

public:
// 构造函数
Circle(double r) : radius(r) {}

// 实现纯虚函数 - 计算面积
double area() const override {
return 3.14159 * radius * radius;
}

// 实现纯虚函数 - 计算周长
double perimeter() const override {
return 2 * 3.14159 * radius;
}

// 实现纯虚函数 - 显示信息
void display() const override {
std::cout << "圆形 - 半径: " << radius << std::endl;
std::cout << "面积: " << area() << std::endl;
std::cout << "周长: " << perimeter() << std::endl;
}
};

解析

  • Shape 是抽象基类,包含纯虚函数(通过 = 0 标记)
  • 纯虚函数没有实现,派生类必须提供实现
  • 抽象类不能直接实例化,只能作为基类使用
  • CircleRectangle 类实现了 Shape 中的所有纯虚函数

2.3 多重继承和虚继承 - 交通工具类层次结构

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
// 3. 交通工具基类 - 用于多重继承示例
class Vehicle {
protected:
std::string brand; // 品牌
int year; // 年份

public:
// 构造函数
Vehicle(const std::string& b, int y) : brand(b), year(y) {}

// 虚函数 - 启动
virtual void start() const {
std::cout << "交通工具启动..." << std::endl;
}

// 虚函数 - 停止
virtual void stop() const {
std::cout << "交通工具停止..." << std::endl;
}

// 显示信息
void display() const {
std::cout << "品牌: " << brand << ", 年份: " << year << std::endl;
}

// 虚析构函数
virtual ~Vehicle() {}
};

// 汽车类 - 虚继承自交通工具类
class Car : virtual public Vehicle {
protected:
int numDoors; // 车门数量

public:
// 构造函数
Car(const std::string& b, int y, int doors)
: Vehicle(b, y), numDoors(doors) {}

// 重写虚函数 - 启动
void start() const override {
std::cout << "汽车引擎用钥匙启动" << std::endl;
}

// 特有成员函数 - 鸣笛
void honk() const {
std::cout << "嘟嘟!嘟嘟!" << std::endl;
}

// 重写显示信息函数
void display() const {
Vehicle::display();
std::cout << "车门数量: " << numDoors << std::endl;
}
};

// 电动类 - 虚继承自交通工具类
class Electric : virtual public Vehicle {
protected:
int batteryCapacity; // 电池容量(千瓦时)

public:
// 构造函数
Electric(const std::string& b, int y, int capacity)
: Vehicle(b, y), batteryCapacity(capacity) {}

// 特有成员函数 - 充电
void charge() const {
std::cout << "正在给电池充电..." << std::endl;
}

// 重写显示信息函数
void display() const {
Vehicle::display();
std::cout << "电池容量: " << batteryCapacity << " kWh" << std::endl;
}
};

// 电动汽车类 - 多重继承自汽车类和电动类
class ElectricCar : public Car, public Electric {
public:
// 构造函数
ElectricCar(const std::string& b, int y, int doors, int capacity)
: Vehicle(b, y), Car(b, y, doors), Electric(b, y, capacity) {}

// 重写虚函数 - 启动
void start() const override {
std::cout << "电动汽车安静地启动..." << std::endl;
}

// 重写显示信息函数
void display() const {
Car::display();
std::cout << "电池容量: " << batteryCapacity << " kWh" << std::endl;
}
};

解析

  • Vehicle 是基类,定义了交通工具的基本属性和方法
  • CarElectric 都虚继承自 Vehicle,使用 virtual 关键字
  • ElectricCar 多重继承自 CarElectric
  • 虚继承解决了菱形继承问题(即多个派生类继承同一个基类时的二义性)
  • ElectricCar 的构造函数中,需要显式调用 Vehicle 的构造函数

2.4 protected 继承 - 员工类层次结构

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
// 4. 员工基类 - 演示 protected 继承
class Employee {
protected:
std::string name; // 姓名
int employeeId; // 员工ID

public:
// 构造函数
Employee(const std::string& n, int id) : name(n), employeeId(id) {}

// 显示信息
void display() const {
std::cout << "员工姓名: " << name << ", ID: " << employeeId << std::endl;
}

// 虚析构函数
virtual ~Employee() {}
};

// 经理类 - protected 继承自员工类
class Manager : protected Employee {
private:
std::string department; // 部门

public:
// 构造函数
Manager(const std::string& n, int id, const std::string& dept)
: Employee(n, id), department(dept) {}

// 显示信息
void display() const {
Employee::display();
std::cout << "部门: " << department << std::endl;
}

// 管理方法
void manage() const {
std::cout << name << " 正在管理 " << department << " 部门" << std::endl;
}
};

解析

  • Manager 使用 protected 方式继承 Employee
  • protected 继承中,基类的 publicprotected 成员在派生类中变为 protected
  • 派生类可以访问基类的成员,但外部代码不能通过派生类对象访问基类的成员

2.5 private 继承 - 学生类层次结构

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
// 5. 学生基类 - 演示 private 继承
class Student {
protected:
std::string name; // 姓名
int studentId; // 学号

public:
// 构造函数
Student(const std::string& n, int id) : name(n), studentId(id) {}

// 显示信息
void display() const {
std::cout << "学生姓名: " << name << ", 学号: " << studentId << std::endl;
}

// 虚析构函数
virtual ~Student() {}
};

// 研究生类 - private 继承自学生类
class GraduateStudent : private Student {
private:
std::string researchArea; // 研究领域

public:
// 构造函数
GraduateStudent(const std::string& n, int id, const std::string& area)
: Student(n, id), researchArea(area) {}

// 显示信息
void display() const {
Student::display();
std::cout << "研究领域: " << researchArea << std::endl;
}

// 研究方法
void research() const {
std::cout << name << " 正在研究 " << researchArea << std::endl;
}
};

解析

  • GraduateStudent 使用 private 方式继承 Student
  • private 继承中,基类的 publicprotected 成员在派生类中变为 private
  • 只有派生类内部可以访问基类的成员,外部代码完全不能访问

2.6 主函数 - 测试各种继承场景

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
int main() {
std::cout << "=== 1. 动物继承 ===" << std::endl;
Dog dog("Buddy", 3, "金毛寻回犬");
Cat cat("Whiskers", 2, true);

dog.display();
dog.makeSound();
dog.eat();
dog.fetch();

std::cout << std::endl;

cat.display();
cat.makeSound();
cat.eat();
cat.climb();

std::cout << "\n=== 2. 使用动物指针的多态 ===" << std::endl;
Animal* animals[2];
animals[0] = new Dog("Max", 4, "拉布拉多");
animals[1] = new Cat("Mittens", 1, false);

for (int i = 0; i < 2; ++i) {
animals[i]->display();
animals[i]->makeSound();
delete animals[i];
}

std::cout << "\n=== 3. 抽象形状类 ===" << std::endl;
Shape* shapes[2];
shapes[0] = new Circle(5.0);
shapes[1] = new Rectangle(4.0, 6.0);

for (int i = 0; i < 2; ++i) {
shapes[i]->display();
delete shapes[i];
}

std::cout << "\n=== 4. 多重继承 ===" << std::endl;
ElectricCar tesla("Tesla", 2023, 4, 75);
tesla.display();
tesla.start();
tesla.honk();
tesla.charge();
tesla.stop();

std::cout << "\n=== 5. protected 继承 ===" << std::endl;
Manager manager("张三", 1001, "技术部");
manager.display();
manager.manage();

std::cout << "\n=== 6. private 继承 ===" << std::endl;
GraduateStudent gradStudent("李四", 2001, "人工智能");
gradStudent.display();
gradStudent.research();

return 0;
}

解析

  • 测试了基本继承、多态、抽象类、多重继承、protected 继承和 private 继承的使用
  • 通过基类指针指向派生类对象,演示了多态的实现
  • 展示了如何使用抽象类的指针来操作不同的派生类对象
  • 测试了多重继承的构造函数调用顺序和方法调用
  • 验证了 protected 继承和 private 继承的访问控制效果

3. 编译和运行说明

3.1 编译命令

使用以下命令编译 inheritance.cpp 文件:

1
g++ -std=c++11 -fexec-charset=GBK -o inheritance inheritance.cpp

参数说明:

  • -std=c++11:使用 C++11 标准
  • -fexec-charset=GBK:确保输出的中文字符正确显示
  • -o inheritance:指定输出可执行文件名为 inheritance

3.2 运行命令

编译成功后,使用以下命令运行程序:

1
2
./inheritance  # Linux/macOS
.\inheritance.exe # Windows

3.3 预期输出

运行程序后,您将看到类似以下的输出:

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
=== 1. 动物继承 ===
名称: Buddy, 年龄: 3
品种: 金毛寻回犬
汪汪!汪汪!
Buddy 在吃东西
Buddy 在取球

名称: Whiskers, 年龄: 2
室内猫: 是
喵喵!喵喵!
Whiskers 在吃东西
Whiskers 在爬树

=== 2. 使用动物指针的多态 ===
名称: Max, 年龄: 4
汪汪!汪汪!
狗析构函数被调用
动物析构函数被调用
名称: Mittens, 年龄: 1
喵喵!喵喵!
猫析构函数被调用
动物析构函数被调用

=== 3. 抽象形状类 ===
圆形 - 半径: 5
面积: 78.5397
周长: 31.4159
矩形 - 宽度: 4, 高度: 6
面积: 24
周长: 20

=== 4. 多重继承 ===
品牌: Tesla, 年份: 2023
车门数量: 4
电池容量: 75 kWh
电动汽车安静地启动...
嘟嘟!嘟嘟!
正在给电池充电...
交通工具停止...

=== 5. protected 继承 ===
员工姓名: 张三, ID: 1001
部门: 技术部
张三 正在管理 技术部 部门

=== 6. private 继承 ===
学生姓名: 李四, 学号: 2001
研究领域: 人工智能
李四 正在研究 人工智能
猫析构函数被调用
动物析构函数被调用
狗析构函数被调用
动物析构函数被调用

4. 技术要点

4.1 继承的基本概念

  • 继承是面向对象编程的三大特性之一(封装、继承、多态)
  • 继承允许创建一个新类(派生类),从一个或多个现有类(基类)中继承属性和方法
  • 继承的语法:class Derived : access-specifier Base,其中 access-specifier 可以是 publicprotectedprivate

4.2 访问控制

基类中的访问级别 public 继承 protected 继承 private 继承
public public protected private
protected protected protected private
private 不可访问 不可访问 不可访问

4.3 虚函数和多态

  • 虚函数:使用 virtual 关键字声明的函数,允许派生类重写
  • 多态:通过基类指针或引用调用虚函数时,会根据实际指向的对象类型调用相应的函数
  • override:C++11 引入的关键字,明确表示重写基类的虚函数
  • final:C++11 引入的关键字,防止虚函数被进一步重写或类被继承

4.4 抽象类和纯虚函数

  • 纯虚函数:声明为 virtual ReturnType function() const = 0; 的函数,没有实现
  • 抽象类:包含至少一个纯虚函数的类,不能直接实例化
  • 接口:只包含纯虚函数的抽象类,类似于 Java 或 C# 中的接口

4.5 多重继承和菱形继承问题

  • 多重继承:一个类同时继承多个基类,语法:class Derived : access-specifier Base1, access-specifier Base2
  • 菱形继承问题:当两个派生类继承同一个基类,而另一个类又同时继承这两个派生类时,会导致基类成员被重复继承
  • 虚继承:使用 virtual 关键字进行继承,确保基类只被继承一次,语法:class Derived : virtual public Base

4.6 构造函数和析构函数的调用顺序

  • 构造函数顺序:先调用基类构造函数,再调用派生类构造函数
  • 析构函数顺序:先调用派生类析构函数,再调用基类析构函数
  • 多重继承构造顺序:按照继承声明的顺序调用各基类的构造函数
  • 虚继承构造顺序:虚基类的构造函数在所有非虚基类之前调用

4.7 继承与组合的选择

  • 继承:表示 “is-a” 关系(例如,狗是一种动物)
  • 组合:表示 “has-a” 关系(例如,汽车有一个引擎)
  • 优先使用组合:组合通常比继承更灵活,减少了类之间的耦合
  • 继承的使用场景:当派生类确实是基类的一种特殊类型,并且需要多态行为时

5. 常见问题解答

5.1 什么是虚析构函数?为什么需要它?

虚析构函数是使用 virtual 关键字声明的析构函数。当通过基类指针删除派生类对象时,如果基类的析构函数不是虚函数,只会调用基类的析构函数,而不会调用派生类的析构函数,可能导致资源泄漏。

5.2 什么是菱形继承问题?如何解决?

菱形继承问题是指当两个派生类继承同一个基类,而另一个类又同时继承这两个派生类时,会导致基类成员被重复继承,产生二义性。

解决方法:使用虚继承(virtual public),确保基类只被继承一次。

5.3 public、protected 和 private 继承有什么区别?

  • public 继承:最常用的继承方式,表示 “is-a” 关系,基类的 public 成员在派生类中仍然是 public
  • protected 继承:表示 “is-implemented-in-terms-of” 关系的一种形式,基类的 public 和 protected 成员在派生类中变为 protected
  • private 继承:表示 “is-implemented-in-terms-of” 关系,基类的 public 和 protected 成员在派生类中变为 private

5.4 抽象类和接口有什么区别?

  • 抽象类:可以包含纯虚函数、虚函数和普通成员函数,也可以包含成员变量
  • 接口:在 C++ 中通常指只包含纯虚函数的抽象类,不包含任何成员变量和普通成员函数

5.5 什么时候应该使用多重继承?

多重继承应该谨慎使用,通常在以下情况下考虑:

  • 一个类需要同时具有多个不同类的行为
  • 实现多个接口(在 C++ 中通过继承多个只包含纯虚函数的抽象类)
  • 当组合无法满足需求时

5.6 如何在派生类中调用基类的构造函数和成员函数?

  • 调用基类构造函数:在派生类构造函数的初始化列表中,例如 Derived(int x) : Base(x) {}
  • 调用基类成员函数:使用作用域解析运算符 ::,例如 Base::function();

6. 代码优化建议

6.1 避免深层继承层次

  • 问题:深层继承层次会增加代码的复杂性,降低可维护性
  • 建议:保持继承层次不超过 3-4 层,超过时考虑使用组合或接口

6.2 优先使用组合而非继承

  • 问题:过度使用继承会导致类之间耦合过紧,难以修改
  • 建议:当不需要多态行为时,优先使用组合而非继承

6.3 正确使用虚函数和纯虚函数

  • 问题:不必要的虚函数会增加运行时开销
  • 建议:只在需要多态行为时使用虚函数,只在需要创建抽象类时使用纯虚函数

6.4 避免使用多重继承,除非必要

  • 问题:多重继承增加了代码的复杂性,可能导致二义性
  • 建议:优先使用单一继承和接口,只有在确实需要时才使用多重继承

6.5 使用 override 关键字明确重写

  • 问题:隐式重写可能导致错误,例如拼写错误或参数不匹配
  • 建议:在重写基类虚函数时,始终使用 override 关键字

6.6 合理设计构造函数和析构函数

  • 问题:不正确的构造函数和析构函数设计可能导致资源泄漏
  • 建议
    • 基类析构函数应声明为虚函数
    • 在构造函数中正确初始化所有成员
    • 在析构函数中释放所有获取的资源

6.7 使用 C++11 特性改进代码

  • 建议
    • 使用 overridefinal 关键字提高代码可读性和安全性
    • 使用智能指针管理动态内存,减少内存泄漏的风险
    • 使用 using 声明替代繁琐的 typedef
    • 使用 nullptr 替代 NULL 宏

7. 总结

继承是 C++ 中实现代码重用和多态的重要机制,通过本文的学习,您应该掌握了:

  • 基本继承的实现和使用
  • 多态的原理和应用
  • 抽象类和纯虚函数的使用
  • 多重继承和虚继承的概念
  • 不同继承方式(public、protected、private)的区别
  • 继承的最佳实践和常见陷阱

合理使用继承可以使代码更加模块化、可维护和可扩展,但也应该注意避免过度使用继承带来的问题。在实际开发中,应根据具体需求选择合适的设计方案,平衡继承和组合的使用。

inheritance.cpp

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
#include <iostream>
#include <string>

// 1. 动物基类 - 演示基本继承
class Animal {
protected:
std::string name; // 名称
int age; // 年龄

public:
// 构造函数
Animal(const std::string& n, int a) : name(n), age(a) {}

// 虚函数 - 发出声音
virtual void makeSound() const {
std::cout << "一些通用的动物声音" << std::endl;
}

// 虚函数 - 进食
virtual void eat() const {
std::cout << name << " 在吃东西" << std::endl;
}

// 普通成员函数 - 显示信息
void display() const {
std::cout << "名称: " << name << ", 年龄: " << age << std::endl;
}

// 虚析构函数
virtual ~Animal() {
std::cout << "动物析构函数被调用" << std::endl;
}
};

// 狗类 - 继承自动物类
class Dog : public Animal {
private:
std::string breed; // 品种

public:
// 构造函数
Dog(const std::string& n, int a, const std::string& b)
: Animal(n, a), breed(b) {}

// 重写虚函数 - 发出声音
void makeSound() const override {
std::cout << "汪汪!汪汪!" << std::endl;
}

// 特有成员函数 - 取物
void fetch() const {
std::cout << name << " 在取球" << std::endl;
}

// 重写显示信息函数
void display() const {
Animal::display();
std::cout << "品种: " << breed << std::endl;
}

// 析构函数
~Dog() {
std::cout << "狗析构函数被调用" << std::endl;
}
};

// 猫类 - 继承自动物类
class Cat : public Animal {
private:
bool isIndoor; // 是否是室内猫

public:
// 构造函数
Cat(const std::string& n, int a, bool indoor)
: Animal(n, a), isIndoor(indoor) {}

// 重写虚函数 - 发出声音
void makeSound() const override {
std::cout << "喵喵!喵喵!" << std::endl;
}

// 特有成员函数 - 爬树
void climb() const {
std::cout << name << " 在爬树" << std::endl;
}

// 重写显示信息函数
void display() const {
Animal::display();
std::cout << "室内猫: " << (isIndoor ? "是" : "否") << std::endl;
}

// 析构函数
~Cat() {
std::cout << "猫析构函数被调用" << std::endl;
}
};

// 2. 形状抽象类 - 演示抽象类和纯虚函数
class Shape {
public:
// 纯虚函数 - 计算面积
virtual double area() const = 0;
// 纯虚函数 - 计算周长
virtual double perimeter() const = 0;
// 纯虚函数 - 显示信息
virtual void display() const = 0;

// 虚析构函数
virtual ~Shape() {}
};

// 圆形类 - 继承自形状类
class Circle : public Shape {
private:
double radius; // 半径

public:
// 构造函数
Circle(double r) : radius(r) {}

// 实现纯虚函数 - 计算面积
double area() const override {
return 3.14159 * radius * radius;
}

// 实现纯虚函数 - 计算周长
double perimeter() const override {
return 2 * 3.14159 * radius;
}

// 实现纯虚函数 - 显示信息
void display() const override {
std::cout << "圆形 - 半径: " << radius << std::endl;
std::cout << "面积: " << area() << std::endl;
std::cout << "周长: " << perimeter() << std::endl;
}
};

// 矩形类 - 继承自形状类
class Rectangle : public Shape {
private:
double width; // 宽度
double height; // 高度

public:
// 构造函数
Rectangle(double w, double h) : width(w), height(h) {}

// 实现纯虚函数 - 计算面积
double area() const override {
return width * height;
}

// 实现纯虚函数 - 计算周长
double perimeter() const override {
return 2 * (width + height);
}

// 实现纯虚函数 - 显示信息
void display() const override {
std::cout << "矩形 - 宽度: " << width << ", 高度: " << height << std::endl;
std::cout << "面积: " << area() << std::endl;
std::cout << "周长: " << perimeter() << std::endl;
}
};

// 3. 交通工具基类 - 用于多重继承示例
class Vehicle {
protected:
std::string brand; // 品牌
int year; // 年份

public:
// 构造函数
Vehicle(const std::string& b, int y) : brand(b), year(y) {}

// 虚函数 - 启动
virtual void start() const {
std::cout << "交通工具启动..." << std::endl;
}

// 虚函数 - 停止
virtual void stop() const {
std::cout << "交通工具停止..." << std::endl;
}

// 显示信息
void display() const {
std::cout << "品牌: " << brand << ", 年份: " << year << std::endl;
}

// 虚析构函数
virtual ~Vehicle() {}
};

// 汽车类 - 虚继承自交通工具类
class Car : virtual public Vehicle {
protected:
int numDoors; // 车门数量

public:
// 构造函数
Car(const std::string& b, int y, int doors)
: Vehicle(b, y), numDoors(doors) {}

// 重写虚函数 - 启动
void start() const override {
std::cout << "汽车引擎用钥匙启动" << std::endl;
}

// 特有成员函数 - 鸣笛
void honk() const {
std::cout << "嘟嘟!嘟嘟!" << std::endl;
}

// 重写显示信息函数
void display() const {
Vehicle::display();
std::cout << "车门数量: " << numDoors << std::endl;
}
};

// 电动类 - 虚继承自交通工具类
class Electric : virtual public Vehicle {
protected:
int batteryCapacity; // 电池容量(千瓦时)

public:
// 构造函数
Electric(const std::string& b, int y, int capacity)
: Vehicle(b, y), batteryCapacity(capacity) {}

// 特有成员函数 - 充电
void charge() const {
std::cout << "正在给电池充电..." << std::endl;
}

// 重写显示信息函数
void display() const {
Vehicle::display();
std::cout << "电池容量: " << batteryCapacity << " kWh" << std::endl;
}
};

// 电动汽车类 - 多重继承自汽车类和电动类
class ElectricCar : public Car, public Electric {
public:
// 构造函数
ElectricCar(const std::string& b, int y, int doors, int capacity)
: Vehicle(b, y), Car(b, y, doors), Electric(b, y, capacity) {}

// 重写虚函数 - 启动
void start() const override {
std::cout << "电动汽车安静地启动..." << std::endl;
}

// 重写显示信息函数
void display() const {
Car::display();
std::cout << "电池容量: " << batteryCapacity << " kWh" << std::endl;
}
};

// 4. 员工基类 - 演示 protected 继承
class Employee {
protected:
std::string name; // 姓名
int employeeId; // 员工ID

public:
// 构造函数
Employee(const std::string& n, int id) : name(n), employeeId(id) {}

// 显示信息
void display() const {
std::cout << "员工姓名: " << name << ", ID: " << employeeId << std::endl;
}

// 虚析构函数
virtual ~Employee() {}
};

// 经理类 - protected 继承自员工类
class Manager : protected Employee {
private:
std::string department; // 部门

public:
// 构造函数
Manager(const std::string& n, int id, const std::string& dept)
: Employee(n, id), department(dept) {}

// 显示信息
void display() const {
Employee::display();
std::cout << "部门: " << department << std::endl;
}

// 管理方法
void manage() const {
std::cout << name << " 正在管理 " << department << " 部门" << std::endl;
}
};

// 5. 学生基类 - 演示 private 继承
class Student {
protected:
std::string name; // 姓名
int studentId; // 学号

public:
// 构造函数
Student(const std::string& n, int id) : name(n), studentId(id) {}

// 显示信息
void display() const {
std::cout << "学生姓名: " << name << ", 学号: " << studentId << std::endl;
}

// 虚析构函数
virtual ~Student() {}
};

// 研究生类 - private 继承自学生类
class GraduateStudent : private Student {
private:
std::string researchArea; // 研究领域

public:
// 构造函数
GraduateStudent(const std::string& n, int id, const std::string& area)
: Student(n, id), researchArea(area) {}

// 显示信息
void display() const {
Student::display();
std::cout << "研究领域: " << researchArea << std::endl;
}

// 研究方法
void research() const {
std::cout << name << " 正在研究 " << researchArea << std::endl;
}
};

int main() {
std::cout << "=== 1. 动物继承 ===" << std::endl;
Dog dog("Buddy", 3, "金毛寻回犬");
Cat cat("Whiskers", 2, true);

dog.display();
dog.makeSound();
dog.eat();
dog.fetch();

std::cout << std::endl;

cat.display();
cat.makeSound();
cat.eat();
cat.climb();

std::cout << "\n=== 2. 使用动物指针的多态 ===" << std::endl;
Animal* animals[2];
animals[0] = new Dog("Max", 4, "拉布拉多");
animals[1] = new Cat("Mittens", 1, false);

for (int i = 0; i < 2; ++i) {
animals[i]->display();
animals[i]->makeSound();
delete animals[i];
}

std::cout << "\n=== 3. 抽象形状类 ===" << std::endl;
Shape* shapes[2];
shapes[0] = new Circle(5.0);
shapes[1] = new Rectangle(4.0, 6.0);

for (int i = 0; i < 2; ++i) {
shapes[i]->display();
delete shapes[i];
}

std::cout << "\n=== 4. 多重继承 ===" << std::endl;
ElectricCar tesla("Tesla", 2023, 4, 75);
tesla.display();
tesla.start();
tesla.honk();
tesla.charge();
tesla.stop();

std::cout << "\n=== 5. protected 继承 ===" << std::endl;
Manager manager("张三", 1001, "技术部");
manager.display();
manager.manage();

std::cout << "\n=== 6. private 继承 ===" << std::endl;
GraduateStudent gradStudent("李四", 2001, "人工智能");
gradStudent.display();
gradStudent.research();

return 0;
}

C++ 类与面向对象编程示例

功能描述

本示例程序展示了 C++ 中面向对象编程的核心概念,包括:

  • 类的定义和基本结构
  • 构造函数和析构函数
  • 成员变量和成员方法
  • 访问控制(public、private)
  • 拷贝构造函数
  • 静态成员
  • 运算符重载
  • 继承与多态
  • 虚函数和纯虚函数
  • 抽象类
  • 组合与聚合
  • 移动语义
  • 智能指针

代码解析

1. 基本类结构

1
2
3
4
5
6
7
8
9
class Rectangle {
private:
double width;
double height;

public:
// 构造函数和析构函数
// 成员方法
};

说明

  • 类是一种用户定义的数据类型,包含数据成员(变量)和成员函数(方法)。
  • private 部分包含只能在类内部访问的成员。
  • public 部分包含可以在类外部访问的成员。

2. 构造函数和析构函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 默认构造函数
Rectangle() : width(0), height(0) {
std::cout << "默认构造函数被调用" << std::endl;
}

// 参数化构造函数
Rectangle(double w, double h) : width(w), height(h) {
std::cout << "参数化构造函数被调用" << std::endl;
}

// 拷贝构造函数
Rectangle(const Rectangle& other) : width(other.width), height(other.height) {
std::cout << "拷贝构造函数被调用" << std::endl;
}

// 析构函数
~Rectangle() {
std::cout << "析构函数被调用" << std::endl;
}

说明

  • 构造函数在创建对象时被调用,用于初始化对象。
  • 默认构造函数没有参数。
  • 参数化构造函数接受参数,用于初始化对象的成员变量。
  • 拷贝构造函数用于从另一个对象创建新对象。
  • 析构函数在对象销毁时被调用,用于清理资源。

3. 成员方法

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
double getWidth() const {
return width;
}

double getHeight() const {
return height;
}

void setWidth(double w) {
width = w;
}

void setHeight(double h) {
height = h;
}

double area() const {
return width * height;
}

double perimeter() const {
return 2 * (width + height);
}

void display() const {
std::cout << "矩形: " << width << " x " << height << std::endl;
std::cout << "面积: " << area() << std::endl;
std::cout << "周长: " << perimeter() << std::endl;
}

说明

  • 成员方法是类的函数,用于操作对象的数据。
  • const 成员方法不能修改对象的状态。
  • getter 方法用于获取私有成员变量的值。
  • setter 方法用于设置私有成员变量的值。
  • 其他方法用于执行特定的操作,如计算面积和周长。

4. 静态成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class BankAccount {
private:
std::string accountNumber;
std::string accountHolder;
double balance;
static int accountCount; // 静态成员变量

public:
// 构造函数
BankAccount(const std::string& num, const std::string& holder, double initialBalance)
: accountNumber(num), accountHolder(holder), balance(initialBalance) {
accountCount++;
}

// 静态成员方法
static int getAccountCount() {
return accountCount;
}
};

// 静态成员变量的初始化
int BankAccount::accountCount = 0;

说明

  • 静态成员变量属于类,而不是对象,所有对象共享同一个静态成员变量。
  • 静态成员变量必须在类外部初始化。
  • 静态成员方法可以在没有对象的情况下调用,只能访问静态成员变量。

5. 运算符重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Point {
private:
int x;
int y;

public:
Point(int x = 0, int y = 0) : x(x), y(y) {}

// 重载加法运算符
Point operator+(const Point& other) const {
return Point(x + other.x, y + other.y);
}

// 重载减法运算符
Point operator-(const Point& other) const {
return Point(x - other.x, y - other.y);
}

// 重载相等运算符
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}
};

说明

  • 运算符重载允许自定义运算符在类对象上的行为。
  • 重载运算符的函数名是 operator 后跟运算符符号。
  • 运算符重载函数可以是成员函数或全局函数。

6. 继承与多态

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
// 基类
class Shape {
public:
virtual void draw() const {
std::cout << "绘制形状" << std::endl;
}

virtual double getArea() const {
return 0;
}

virtual ~Shape() {}
};

// 派生类
class Circle : public Shape {
private:
double radius;

public:
Circle(double r) : radius(r) {}

void draw() const override {
std::cout << "绘制圆形" << std::endl;
}

double getArea() const override {
return 3.14159 * radius * radius;
}
};

说明

  • 继承允许一个类(派生类)继承另一个类(基类)的成员。
  • virtual 关键字用于声明虚函数,支持运行时多态。
  • override 关键字表示重写基类的虚函数。
  • 虚析构函数确保派生类的析构函数被正确调用。

7. 抽象类和纯虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Animal {
public:
virtual void makeSound() const = 0; // 纯虚函数
virtual void eat() const {
std::cout << "动物在吃东西" << std::endl;
}

virtual ~Animal() {}
};

class Dog : public Animal {
public:
void makeSound() const override {
std::cout << "汪汪汪!" << std::endl;
}
};

说明

  • 包含纯虚函数的类是抽象类,不能直接实例化。
  • 纯虚函数是没有实现的虚函数,派生类必须提供实现。
  • 抽象类用于定义接口,派生类提供具体实现。

8. 组合与聚合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Engine {
public:
void start() const {
std::cout << "引擎启动" << std::endl;
}
};

class Car {
private:
Engine engine; // 组合:Car 拥有一个 Engine

public:
void start() const {
engine.start();
std::cout << "汽车启动" << std::endl;
}
};

说明

  • 组合是一种”拥有”关系,其中一个类包含另一个类的对象。
  • 聚合是一种”使用”关系,其中一个类引用另一个类的对象,但不拥有它。

9. 移动语义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyString {
private:
char* data;

public:
// 移动构造函数
MyString(MyString&& other) noexcept : data(other.data) {
other.data = nullptr;
std::cout << "移动构造函数被调用" << std::endl;
}

// 移动赋值运算符
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
other.data = nullptr;
std::cout << "移动赋值运算符被调用" << std::endl;
}
return *this;
}
};

说明

  • 移动语义允许资源从一个对象转移到另一个对象,避免不必要的复制。
  • 移动构造函数和移动赋值运算符接受右值引用(&&)。
  • noexcept 表示函数不会抛出异常。

10. 智能指针

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
#include <memory>

class Person {
private:
std::string name;

public:
Person(const std::string& n) : name(n) {
std::cout << "Person " << name << " 被创建" << std::endl;
}

~Person() {
std::cout << "Person " << name << " 被销毁" << std::endl;
}

void sayHello() const {
std::cout << "你好,我是 " << name << std::endl;
}
};

// 使用智能指针
std::unique_ptr<Person> p1 = std::make_unique<Person>("张三");
p1->sayHello();

std::shared_ptr<Person> p2 = std::make_shared<Person>("李四");
std::shared_ptr<Person> p3 = p2; // 共享所有权

说明

  • 智能指针是管理动态内存的工具,自动处理内存的分配和释放。
  • std::unique_ptr 拥有独占所有权,不能复制。
  • std::shared_ptr 共享所有权,使用引用计数。
  • std::make_uniquestd::make_shared 是创建智能指针的推荐方式。

编译和运行

在 Windows 上编译(使用 g++):

1
g++ -std=c++11 -fexec-charset=GBK -o classes classes.cpp

运行程序:

1
.\classes.exe

输出结果:

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
=== 1. 矩形类 ===
默认构造函数被调用
矩形: 0 x 0
面积: 0
周长: 0
参数化构造函数被调用
矩形: 5 x 3
面积: 15
周长: 16
拷贝构造函数被调用
矩形: 5 x 3
面积: 15
周长: 16
矩形: 7 x 4
面积: 28
周长: 22
析构函数被调用
析构函数被调用
析构函数被调用

=== 2. 银行账户类 ===
账户: 123456
持有人: John Doe
余额: $1000
存入: $500
取出: $200
账户: 123456
持有人: John Doe
余额: $1300
总账户数: 2

=== 3. 点类与运算符重载 ===
p1: (3, 4)
p2: (1, 2)
p1 + p2: (4, 6)
p1 - p2: (2, 2)
p1 == p5: true

=== 4. 继承与多态 ===
绘制形状
面积: 0
绘制圆形
面积: 78.5397
绘制矩形
面积: 28

=== 5. 抽象类与接口 ===
汪汪汪!
动物在吃东西
喵喵喵!
动物在吃东西

=== 6. 组合与聚合 ===
引擎启动
汽车启动

=== 7. 移动语义 ===
构造函数被调用
移动构造函数被调用
移动赋值运算符被调用
析构函数被调用
析构函数被调用

=== 8. 智能指针 ===
Person 张三 被创建
你好,我是 张三
Person 李四 被创建
你好,我是 李四
你好,我是 李四
Person 李四 被销毁
Person 张三 被销毁

技术要点

1. 面向对象编程的四大特性

  • 封装:将数据和操作数据的方法封装在一个类中,隐藏实现细节。
  • 继承:一个类可以继承另一个类的属性和方法,实现代码复用。
  • 多态:通过虚函数,不同的对象可以以不同的方式响应相同的消息。
  • 抽象:通过抽象类和接口,定义对象的行为而不实现具体细节。

2. 构造函数和析构函数的使用

  • 构造函数:用于初始化对象,确保对象在创建时处于有效状态。
  • 析构函数:用于清理对象占用的资源,如动态内存、文件句柄等。
  • 拷贝构造函数:用于创建一个新对象作为现有对象的副本。
  • 移动构造函数:用于将资源从一个对象转移到另一个对象,避免不必要的复制。

3. 访问控制

  • public:可以在任何地方访问。
  • private:只能在类内部访问。
  • protected:可以在类内部和派生类中访问。

4. 虚函数和多态

  • 虚函数:在基类中声明,派生类可以重写的函数。
  • 纯虚函数:没有实现的虚函数,派生类必须提供实现。
  • 抽象类:包含纯虚函数的类,不能直接实例化。
  • 多态:通过基类指针或引用调用虚函数时,会根据对象的实际类型调用相应的函数。

5. 运算符重载

  • 可以重载大多数 C++ 运算符,如 +-*/==!= 等。
  • 运算符重载函数可以是成员函数或全局函数。
  • 某些运算符必须作为成员函数重载,如 =[]()->

6. 静态成员

  • 静态成员变量:属于类,所有对象共享,必须在类外部初始化。
  • 静态成员方法:可以在没有对象的情况下调用,只能访问静态成员变量。
  • 静态成员:常用于计数、缓存、工具函数等。

7. 智能指针的使用

  • std::unique_ptr:独占所有权,不能复制,只能移动。
  • std::shared_ptr:共享所有权,使用引用计数。
  • std::weak_ptr:不增加引用计数的 shared_ptr 观察者。
  • 智能指针:可以避免内存泄漏,是现代 C++ 中管理动态内存的推荐方式。

8. 移动语义

  • 右值引用:使用 && 表示,指向临时对象。
  • 移动构造函数:将资源从一个对象转移到另一个对象。
  • 移动赋值运算符:将资源从一个对象转移到另一个已存在的对象。
  • 移动语义:可以提高性能,特别是对于大型对象。

9. 组合与继承的选择

  • 组合:”拥有”关系,一个类包含另一个类的对象。
  • 继承:”是”关系,一个类是另一个类的特殊类型。
  • 优先使用组合:组合比继承更灵活,减少了类之间的耦合。

10. 代码组织和最佳实践

  • 头文件和源文件分离:类声明放在头文件(.h)中,实现放在源文件(.cpp)中。
  • 命名约定:类名使用 PascalCase,成员变量使用 camelCase,常量使用全大写。
  • 初始化列表:使用构造函数初始化列表初始化成员变量,而不是在构造函数体中赋值。
  • const 正确性:对于不修改对象状态的成员函数,使用 const 修饰。
  • 避免使用原始指针:优先使用智能指针管理动态内存。

常见问题

1. 内存泄漏

问题:使用 new 分配内存后,没有使用 delete 释放内存。
解决方案:使用智能指针管理动态内存,或确保每次 new 都有对应的 delete

2. 构造函数和析构函数的顺序

问题:不了解构造函数和析构函数的调用顺序。
解决方案

  • 构造顺序:基类构造函数 → 成员变量构造函数 → 派生类构造函数。
  • 析构顺序:派生类析构函数 → 成员变量析构函数 → 基类析构函数。

3. 虚函数的使用

问题:没有将基类的析构函数声明为虚函数,导致派生类的析构函数不被调用。
解决方案:对于有虚函数的类,将析构函数声明为虚函数。

4. 运算符重载的返回类型

问题:运算符重载函数的返回类型不正确。
解决方案

  • 对于 operator=,返回 *this 的引用。
  • 对于 operator+operator- 等,返回新对象。

5. 静态成员的初始化

问题:静态成员变量没有在类外部初始化。
解决方案:在类外部使用 类型 类名::成员名 = 值; 的形式初始化静态成员变量。

6. 访问控制错误

问题:试图在类外部访问 private 成员。
解决方案:将需要在类外部访问的成员声明为 public,或提供 public 的 getter 和 setter 方法。

7. 继承中的作用域

问题:派生类中的成员与基类中的成员同名,导致基类成员被隐藏。
解决方案:使用 基类名::成员名 访问被隐藏的基类成员,或避免使用同名成员。

8. 智能指针的循环引用

问题:两个对象使用 shared_ptr 相互引用,导致内存泄漏。
解决方案:使用 weak_ptr 打破循环引用。

9. 移动语义的使用

问题:不知道何时使用移动语义,或如何实现移动构造函数。
解决方案

  • 对于包含动态资源的类,实现移动构造函数和移动赋值运算符。
  • 使用 std::move() 将左值转换为右值引用。

10. 代码组织

问题:类的声明和实现都放在一个文件中,导致代码难以维护。
解决方案:将类声明放在头文件中,实现放在源文件中,使用包含保护符防止头文件被重复包含。

代码优化建议

  1. 使用初始化列表

    • 优先使用构造函数初始化列表初始化成员变量,而不是在构造函数体中赋值。
    • 初始化列表可以提高性能,特别是对于大型对象。
  2. 使用 const 修饰符

    • 对于不修改对象状态的成员函数,使用 const 修饰。
    • 对于不修改参数的函数参数,使用 const 引用。
  3. 使用智能指针

    • 优先使用 std::unique_ptrstd::shared_ptr 管理动态内存。
    • 避免使用原始指针和手动内存管理。
  4. 实现移动语义

    • 对于包含动态资源的类,实现移动构造函数和移动赋值运算符。
    • 使用 std::move() 优化对象的传递。
  5. 使用 override 关键字

    • 在重写基类虚函数时,使用 override 关键字,让编译器检查是否正确重写。
  6. 避免使用宏

    • 优先使用 const 变量、枚举和内联函数,而不是宏。
  7. 使用命名空间

    • 将相关的类和函数放在一个命名空间中,避免命名冲突。
  8. 分离接口和实现

    • 将类声明放在头文件中,实现放在源文件中。
    • 使用 Pimpl 手法(指向实现的指针)减少编译依赖。
  9. 使用现代 C++ 特性

    • 使用 C++11 及以上的特性,如自动类型推导、范围 for 循环、lambda 表达式等。
    • 避免使用过时的特性,如 auto_ptrbind1st/bind2nd 等。
  10. 编写单元测试

    • 为类编写单元测试,确保类的行为符合预期。
    • 使用测试框架,如 Google Test、Catch2 等。

总结

C++ 的面向对象编程提供了强大的工具,用于创建模块化、可维护、可扩展的代码:

  • :封装数据和行为,提供了一种组织代码的方式。
  • 继承:实现代码复用,建立类型层次结构。
  • 多态:通过虚函数,实现运行时的动态行为。
  • 抽象:通过抽象类和接口,定义对象的行为契约。
  • 现代 C++ 特性:移动语义、智能指针等,提高代码的性能和安全性。

通过合理使用这些特性,可以创建高质量的 C++ 代码,解决复杂的问题。面向对象编程不仅是一种编程范式,更是一种思维方式,帮助开发者更好地理解和建模现实世界的问题。

classes.cpp

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <cstring>

// 1. 矩形类 - 基本类结构示例
class Rectangle {
private:
double width; // 宽度
double height; // 高度

public:
// 默认构造函数
Rectangle() : width(0), height(0) {
std::cout << "默认构造函数被调用" << std::endl;
}

// 参数化构造函数
Rectangle(double w, double h) : width(w), height(h) {
std::cout << "参数化构造函数被调用" << std::endl;
}

// 拷贝构造函数
Rectangle(const Rectangle& other) : width(other.width), height(other.height) {
std::cout << "拷贝构造函数被调用" << std::endl;
}

// 析构函数
~Rectangle() {
std::cout << "析构函数被调用" << std::endl;
}

// 获取宽度
double getWidth() const {
return width;
}

// 获取高度
double getHeight() const {
return height;
}

// 设置宽度
void setWidth(double w) {
width = w;
}

// 设置高度
void setHeight(double h) {
height = h;
}

// 计算面积
double area() const {
return width * height;
}

// 计算周长
double perimeter() const {
return 2 * (width + height);
}

// 显示信息
void display() const {
std::cout << "矩形: " << width << " x " << height << std::endl;
std::cout << "面积: " << area() << std::endl;
std::cout << "周长: " << perimeter() << std::endl;
}
};

// 2. 银行账户类 - 静态成员示例
class BankAccount {
private:
std::string accountNumber; // 账户号
std::string accountHolder; // 账户持有人
double balance; // 余额
static int accountCount; // 静态成员变量,用于计数

public:
// 构造函数
BankAccount(const std::string& num, const std::string& holder, double initialBalance)
: accountNumber(num), accountHolder(holder), balance(initialBalance) {
accountCount++;
}

// 存款
void deposit(double amount) {
if (amount > 0) {
balance += amount;
std::cout << "存入: $" << amount << std::endl;
}
}

// 取款
bool withdraw(double amount) {
if (amount > 0 && balance >= amount) {
balance -= amount;
std::cout << "取出: $" << amount << std::endl;
return true;
}
std::cout << "取款失败" << std::endl;
return false;
}

// 获取余额
double getBalance() const {
return balance;
}

// 显示信息
void display() const {
std::cout << "账户: " << accountNumber << std::endl;
std::cout << "持有人: " << accountHolder << std::endl;
std::cout << "余额: $" << balance << std::endl;
}

// 静态成员方法,获取账户总数
static int getAccountCount() {
return accountCount;
}
};

// 静态成员变量的初始化
int BankAccount::accountCount = 0;

// 3. 点类 - 运算符重载示例
class Point {
private:
int x; // x坐标
int y; // y坐标

public:
// 构造函数
Point(int x = 0, int y = 0) : x(x), y(y) {}

// 重载加法运算符
Point operator+(const Point& other) const {
return Point(x + other.x, y + other.y);
}

// 重载减法运算符
Point operator-(const Point& other) const {
return Point(x - other.x, y - other.y);
}

// 重载相等运算符
bool operator==(const Point& other) const {
return x == other.x && y == other.y;
}

// 显示信息
void display() const {
std::cout << "(" << x << ", " << y << ")" << std::endl;
}
};

// 4. 形状类 - 继承与多态示例
class Shape {
public:
// 虚函数,绘制形状
virtual void draw() const {
std::cout << "绘制形状" << std::endl;
}

// 虚函数,获取面积
virtual double getArea() const {
return 0;
}

// 虚析构函数
virtual ~Shape() {}
};

// 圆形类 - 继承自形状类
class Circle : public Shape {
private:
double radius; // 半径

public:
// 构造函数
Circle(double r) : radius(r) {}

// 重写绘制方法
void draw() const override {
std::cout << "绘制圆形" << std::endl;
}

// 重写获取面积方法
double getArea() const override {
return 3.14159 * radius * radius;
}
};

// 矩形类 - 继承自形状类(另一个版本)
class RectangleShape : public Shape {
private:
double width; // 宽度
double height; // 高度

public:
// 构造函数
RectangleShape(double w, double h) : width(w), height(h) {}

// 重写绘制方法
void draw() const override {
std::cout << "绘制矩形" << std::endl;
}

// 重写获取面积方法
double getArea() const override {
return width * height;
}
};

// 5. 动物类 - 抽象类示例
class Animal {
public:
// 纯虚函数,发出声音
virtual void makeSound() const = 0;

// 普通虚函数,吃东西
virtual void eat() const {
std::cout << "动物在吃东西" << std::endl;
}

// 虚析构函数
virtual ~Animal() {}
};

// 狗类 - 继承自动物类
class Dog : public Animal {
public:
// 实现发出声音方法
void makeSound() const override {
std::cout << "汪汪汪!" << std::endl;
}
};

// 猫类 - 继承自动物类
class Cat : public Animal {
public:
// 实现发出声音方法
void makeSound() const override {
std::cout << "喵喵喵!" << std::endl;
}
};

// 6. 引擎类 - 用于组合示例
class Engine {
public:
// 启动引擎
void start() const {
std::cout << "引擎启动" << std::endl;
}
};

// 汽车类 - 包含引擎(组合)
class Car {
private:
Engine engine; // 组合:Car 拥有一个 Engine

public:
// 启动汽车
void start() const {
engine.start();
std::cout << "汽车启动" << std::endl;
}
};

// 7. 字符串类 - 移动语义示例
class MyString {
private:
char* data; // 字符数据

public:
// 构造函数
MyString(const char* str) {
if (str) {
data = new char[strlen(str) + 1];
strcpy(data, str);
} else {
data = nullptr;
}
std::cout << "构造函数被调用" << std::endl;
}

// 移动构造函数
MyString(MyString&& other) noexcept : data(other.data) {
other.data = nullptr;
std::cout << "移动构造函数被调用" << std::endl;
}

// 移动赋值运算符
MyString& operator=(MyString&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
other.data = nullptr;
std::cout << "移动赋值运算符被调用" << std::endl;
}
return *this;
}

// 析构函数
~MyString() {
delete[] data;
std::cout << "析构函数被调用" << std::endl;
}

// 获取数据
const char* getData() const {
return data;
}
};

// 8. 人类 - 智能指针示例
class Person {
private:
std::string name; // 姓名

public:
// 构造函数
Person(const std::string& n) : name(n) {
std::cout << "Person " << name << " 被创建" << std::endl;
}

// 析构函数
~Person() {
std::cout << "Person " << name << " 被销毁" << std::endl;
}

// 打招呼
void sayHello() const {
std::cout << "你好,我是 " << name << std::endl;
}
};

int main() {
std::cout << "=== 1. 矩形类 ===" << std::endl;
{
Rectangle rect1;
rect1.display();

Rectangle rect2(5.0, 3.0);
rect2.display();

Rectangle rect3 = rect2;
rect3.display();

rect3.setWidth(7.0);
rect3.setHeight(4.0);
rect3.display();
} // 此处三个矩形对象会被销毁

std::cout << "\n=== 2. 银行账户类 ===" << std::endl;
{
BankAccount acc1("123456", "John Doe", 1000.0);
BankAccount acc2("789012", "Jane Smith", 500.0);

acc1.display();
acc1.deposit(500.0);
acc1.withdraw(200.0);
acc1.display();

std::cout << "总账户数: " << BankAccount::getAccountCount() << std::endl;
} // 此处两个账户对象会被销毁

std::cout << "\n=== 3. 点类与运算符重载 ===" << std::endl;
{
Point p1(3, 4);
Point p2(1, 2);

Point p3 = p1 + p2;
Point p4 = p1 - p2;

std::cout << "p1: ";
p1.display();
std::cout << "p2: ";
p2.display();
std::cout << "p1 + p2: ";
p3.display();
std::cout << "p1 - p2: ";
p4.display();

Point p5(3, 4);
std::cout << "p1 == p5: " << (p1 == p5 ? "true" : "false") << std::endl;
}

std::cout << "\n=== 4. 继承与多态 ===" << std::endl;
{
Shape* shape1 = new Shape();
Shape* shape2 = new Circle(5.0);
Shape* shape3 = new RectangleShape(7.0, 4.0);

shape1->draw();
std::cout << "面积: " << shape1->getArea() << std::endl;

shape2->draw();
std::cout << "面积: " << shape2->getArea() << std::endl;

shape3->draw();
std::cout << "面积: " << shape3->getArea() << std::endl;

delete shape1;
delete shape2;
delete shape3;
}

std::cout << "\n=== 5. 抽象类与接口 ===" << std::endl;
{
Animal* dog = new Dog();
Animal* cat = new Cat();

dog->makeSound();
dog->eat();

cat->makeSound();
cat->eat();

delete dog;
delete cat;
}

std::cout << "\n=== 6. 组合与聚合 ===" << std::endl;
{
Car car;
car.start();
}

std::cout << "\n=== 7. 移动语义 ===" << std::endl;
{
MyString s1("Hello");
MyString s2 = std::move(s1); // 使用移动构造函数
MyString s3("World");
s3 = std::move(s2); // 使用移动赋值运算符
}

std::cout << "\n=== 8. 智能指针 ===" << std::endl;
{
// 使用 unique_ptr
std::unique_ptr<Person> p1(new Person("张三"));
p1->sayHello();

// 使用 shared_ptr
std::shared_ptr<Person> p2 = std::make_shared<Person>("李四");
std::shared_ptr<Person> p3 = p2; // 共享所有权

p2->sayHello();
p3->sayHello();
} // 此处智能指针会自动释放内存

return 0;
}