高效编写 C++ 代码的 100 个编程习惯-OOP
在 C++ 的世界中,良好的编程习惯不仅能提升代码质量,还能显著增强程序的性能、可维护性和安全性。随着 C++ 标准的不断演进,从 C++98 到 C++20,再到即将到来的 C++23,每一个版本都引入了新的特性和改进。这使得 C++ 编程实践不断丰富,也为程序员提供了更多优化和提高效率的工具。
在这篇博客中,我们将探索 100 个 C++ 编程的优秀习惯。这些习惯涵盖了从代码风格、性能优化、内存管理,到现代 C++ 特性的应用等各个方面。无论你是一个经验丰富的 C++ 开发者,还是一个刚刚踏入 C++ 世界的新手,这些习惯都能帮助你写出更加高效、健壮和可维护的代码。这些习惯不仅有助于减少潜在的错误,还能提升代码的可读性和维护性。
希望这篇博客能为你提供有价值的参考,帮助你在 C++ 编程的旅程中更进一步。
1. 构造函数尽量使用成员初始化列表
的方式而不是函数体实现的方式
解释:
使用成员初始化列表来初始化成员变量比在构造函数体内赋值更有效率。成员初始化列表可以直接初始化成员变量,而不是先调用默认构造函数,然后再在构造函数体内进行赋值操作。
例子:
class MyClass {
int x;
public:
MyClass(int value) : x(value) {
} // 成员初始化列表
// MyClass(int value) { x = value; } // 函数体内赋值
};
说明:
在使用成员初始化列表时,x
在对象创建时就被初始化了,而不是先调用默认构造函数再赋值,因此更高效。
2. 函数参数列表每一个参数值都要考虑能不能加const
解释:
为函数参数加上 const
限定符,可以防止在函数内修改该参数的值,并且还可以使代码更安全和更具可读性。如果参数不需要修改,应该加上 const
。
例子:
void printValue(const int value) {
std::cout << value << std::endl;
}
说明:
在这个例子中,value
不需要在函数内部被修改,因此将其声明为 const
。这样可以防止意外修改,并向函数的使用者表明该参数不会被改变。
3. 函数参数列表每一个参数值都要考虑传递方式是不是应该是pass by reference
解释:
对于较大的对象(如复杂类类型或结构体),传递引用而不是传递值,可以避免不必要的拷贝,提高效率。通过引用传递可以减少内存的使用,并且允许函数修改传递进来的参数。
例子:
void modifyValue(int& value) {
value = 42;
}
说明:
这里 value
作为引用传递,因此函数可以直接修改调用者传递进来的变量值。相比之下,传值方式会创建参数的副本,修改副本不会影响原变量,并且副本的创建也会产生额外的开销。
4. 函数的定义类型和函数返回类型要考虑是不是应该为return by reference
解释:
在函数返回大型对象或复杂类型时,使用引用返回可以避免对象拷贝,节省资源。如果返回的对象在函数外依然有效(如类成员),可以考虑返回引用。
例子:
class MyClass {
int value;
public:
int& getValue() {
return value; }
};
说明:
getValue
返回成员变量 value
的引用,因此调用者可以直接操作该成员变量,而不需要通过拷贝副本来实现。需注意返回引用时,要保证引用的对象在函数外依然有效,否则可能会导致悬挂引用问题。
5. 使用智能指针代替裸指针
解释:
C++ 中管理内存的一种更安全的方式是使用智能指针(如 std::unique_ptr
, std::shared_ptr
)而不是裸指针(raw pointers)。智能指针自动管理内存的释放,减少内存泄漏的风险。
例子:
#include <memory>
std::unique_ptr<int> ptr = std::make_unique<int>(10);
说明:
std::unique_ptr
是一种独占式所有权的智能指针,当它超出作用域时,会自动释放所管理的内存。
6. 避免在头文件中定义变量
解释:
在头文件中定义变量会导致变量的多次定义错误。变量应该在源文件中定义,在头文件中只声明。
例子:
// header.h
extern int globalVar; // 只声明,不定义
// source.cpp
int globalVar = 42; // 定义变量
说明:
通过 extern
关键字可以在头文件中声明全局变量,然后在源文件中进行定义。
7. 避免使用 using namespace
直接在头文件中
解释:
在头文件中使用 using namespace
可能会污染全局命名空间,导致命名冲突。建议在源文件中使用,或在局部范围内使用。
例子:
// 不推荐
#include <iostream>
using namespace std; // 可能引起命名冲突
// 推荐
namespace myspace {
void myFunction() {
std::cout << "Hello, World!" << std::endl;
}
}
8. 使用 nullptr
而不是 NULL
或 0
解释:
C++11 引入了 nullptr
,它是一个类型安全的空指针。相比于 NULL
或 0
,nullptr
更加明确且安全。
例子:
int* p = nullptr;
说明:
使用 nullptr
可以避免将指针误用为整数或其他类型,减少错误的发生。
9. 函数的签名应尽量保持一致
解释:
如果一个类有多个重载函数,这些函数的签名(参数顺序、类型等)应该保持一致,以减少混淆和潜在错误。
例子:
class MyClass {
public:
void set(int a) {
/* ... */ }
void set(double b) {
/* ... */ }
};
说明:
set(int)
和 set(double)
保持相同的参数顺序和函数名,使得 API 更加统一和易用。
10. 使用 enum class
代替传统的 enum
解释:
enum class
是 C++11 引入的一种强类型枚举,避免了传统枚举带来的隐式转换和作用域污染问题。
例子:
enum class Color {
Red, Green, Blue };
Color color = Color::Red;
说明:
enum class
避免了传统枚举中的问题,如不同枚举类型之间的隐式转换。
11. 使用 override
和 final
关键字
解释:
使用 override
表示你打算重写基类的虚函数,可以防止意外重载。final
用于防止进一步的重写或继承。
例子:
class Base {
public:
virtual void func() {
}
};
class Derived : public Base {
public:
void func() override {
} // 表明这是重写
void anotherFunc() final {
} // 不能再被重写
};
说明:
override
可以帮助编译器检测错误,例如基类函数名的更改导致的错误。final
则保护类结构不被滥用扩展。
12. 定义明确的类接口(Interface)和分离实现
解释:
保持类的接口和实现分离可以提升代码的可读性和可维护性。接口应包含在头文件中,而实现应放在源文件中。
例子:
// MyClass.h
class MyClass {
public:
void doSomething();
private:
int data;
};
// MyClass.cpp
#include "MyClass.h"
void MyClass::doSomething() {
// 实现
}
说明:
接口与实现分离使得代码结构更加清晰,修改实现时也不会影响接口的使用。
13. 优先使用std::array
和std::vector
代替C风格数组
解释:
C++ 标准库提供了更安全、更功能丰富的数组容器,如 std::array
和 std::vector
。它们提供了边界检查和其他有用的功能。
例子:
std::vector<int> v = {
1, 2, 3, 4, 5};
说明:
std::vector
自动管理其容量,提供边界检查,防止常见的数组越界问题。
14. 使用 RAII 管理资源
解释:
RAII(Resource Acquisition Is Initialization)是一种管理资源的惯用方法,资源的获取与对象的生命周期绑定,在对象销毁时自动释放资源。
例子:
class FileWrapper {
std::FILE* file;
public:
FileWrapper(const char* filename) {
file = std::fopen(filename, "r");
}
~FileWrapper() {
if (file) std::fclose(file);
}
};
说明:
通过 RAII,FileWrapper
类可以确保文件在作用域结束时自动关闭,无需显式调用关闭操作。
15. 使用 noexcept
声明不抛异常的函数
解释:
使用 noexcept
来声明不会抛出异常的函数,有助于编译器优化,并且明确表示该函数的异常安全性。
例子:
void process() noexcept {
// 不会抛出异常的代码
}
说明:
noexcept
提示编译器可以对函数进行更激进的优化,同时也帮助开发者理解函数的行为。
16. 谨慎使用多重继承
解释:
多重继承可能导致“菱形继承”问题,增加代码的复杂性和维护难度。如果必须使用多重继承,考虑使用虚继承(virtual inheritance
)来避免重复继承基类的问题。
例子:
class A {
/*...*/ };
class B : virtual public A {
/*...*/ };
class C : virtual public A {
/*...*/ };
class D : public B, public C {
/*...*/ }; // 虚继承
说明:
虚继承可以确保 D
只有一个 A
的实例,避免了菱形继承问题。
17. 优先使用范围 for
循环 (range-based for loop
)
解释:
范围 for
循环是 C++11 引入的一种简化的循环方式,适用于遍历数组、容器等类型的数据结构。
例子:
std::vector<int> v = {
1, 2, 3, 4, 5};
for (int n : v) {
std::cout << n << std::endl;
}
说明:
范围 for
循环比传统的 for
循环更简洁、可读性更高,且不容易出错。
18. 尽量避免使用全局变量
解释:
全局变量的使用容易导致代码的耦合性增加,并且可能引发难以追踪的 bug。尽量通过函数参数传递或类成员变量来代替全局变量。
例子:
// 不推荐使用全局变量
int globalCounter = 0;
// 推荐使用类成员变量
class Counter {
int counter = 0;
public:
void increment() {
counter++; }
int getCounter() const {
return counter; }
};
说明:
通过将变量封装在类中,可以更好地控制和管理状态,减少意外修改的可能性。
19. 使用 constexpr
定义常量
解释:
constexpr
可以用于定义在编译时计算的常量,确保常量在编译期间确定,提高程序的效率。
例子:
constexpr int factorial(int n) {
return (n <= 1) ? 1 : (n * factorial(n - 1));
}
说明:
constexpr
常量在编译期计算,因此不会影响运行时性能。
20. 避免使用 goto
语句
解释:
goto
语句会破坏代码的结构性,增加代码的复杂性和可读性差,应尽量避免使用。现代 C++ 提供了更好的控制流机制,如 break
、continue
、return
和异常处理。
例子:
// 不推荐
void func() {
if (condition1) goto label;
// ...
label:
// ...
}
// 推荐使用结构化控制流
void func() {
if (condition1) return;
// ...
}
说明:
goto
的使用容易导致“意大利面条”式的代码结构,难以维护和调试。
21. 避免在循环内频繁分配和释放内存
解释:
在循环内频繁分配和释放内存会导致性能下降和内存碎片化。应尽量在循环外部预先分配好所需的内存,然后在循环内重复使用。
例子:
// 不推荐
for (int i = 0; i < 1000; ++i) {
int* arr = new int[100];
// ...
delete[] arr;
}
// 推荐
int* arr = new int[100];
for (int i = 0; i < 1000; ++i) {
// 使用arr
}
delete[] arr;
说明:
通过在循环外预分配内存,可以显著减少动态内存分配的开销,提高性能。
22. 使用 std::move
和 std::forward
优化移动语义
解释:
C++11 引入了移动语义,通过使用 std::move
和 std::forward
可以避免不必要的深拷贝,提升性能。
例子:
std::vector<int> createVector() {
std::vector<int> v = {
1, 2, 3, 4, 5};
return std::move(v); // 移动v而不是拷贝
}
说明:
使用 std::move
将对象的所有权转移,而不是复制,从而减少不必要的性能开销。
23. 谨慎使用 inline
关键字
解释:
inline
关键字用于提示编译器将函数展开在调用点,以减少函数调用的开销。但应谨慎使用,因为滥用 inline
可能导致代码膨胀,反而降低性能。
例子:
inline int add(int a, int b) {
return a + b;
}
说明:
inline
适用于那些小而简单的函数,但不应滥用在复杂的函数上。
24. 使用异常(Exception)处理错误
解释:
异常处理是一种处理运行时错误的强大机制,比传统的错误代码返回方式更加灵活。应使用 try-catch
块捕获和处理异常,而不是依赖错误码。
例子:
void process() {
try {
// 可能抛出异常的代码
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
}
说明:
使用异常处理可以更好地管理错误情况,并将错误处理与正常逻辑分开,使代码更清晰。
25. 对类成员函数尽量使用 const
修饰符
解释:
如果一个类成员函数不会修改类的状态,应该将其声明为 const
成员函数,这样可以提高代码的自描述性,并确保类的不可变性。
例子:
class MyClass {
int data;
public:
int getData() const {
return data; } // const 成员函数
};
说明:
const
成员函数不会修改类成员变量,因此可以被 const
对象调用,确保其状态不被改变。
26. 使用 RAII 管理资源的分配和释放
解释:
RAII(Resource Acquisition Is Initialization)是一种设计原则,将资源的分配和释放绑定到对象的生命周期。这样可以避免资源泄漏问题。
例子:
class File {
FILE* file;
public:
File(const char* filename) : file(std::fopen(filename, "r")) {
}
~File() {
if (file) std::fclose(file);
}
};
说明:
通过 RAII,File
类可以确保文件在作用域结束时自动关闭,避免资源泄漏。
27. 使用 std::optional
处理可能不存在的返回值
解释:
std::optional
是 C++17 引入的一种类型,用于处理可能没有返回值的情况,而不需要依赖指针或特殊返回值。
例子:
std::optional<int> findValue(int key) {
if (/*找到key*/) return value;
return std::nullopt;
}
说明:
std::optional
明确表示一个函数可能没有返回值,代码更具表达性和安全性。
28. 使用命名空间(namespace
)避免命名冲突
解释:
使用命名空间将代码组织到逻辑单元中,避免不同模块之间的命名冲突。大型项目尤其应采用命名空间来组织代码。
例子:
namespace mylibrary {
void func() {
// ...
}
}
说明:
通过命名空间可以避免全局命名冲突,同时也使得代码结构更加清晰。
29. 尽量使用 std::string
而不是 C 风格的字符串
解释:
std::string
是 C++ 标准库提供的字符串类,它提供了更高层次的字符串操作功能,并且避免了 C 风格字符串常见的缓冲区溢出等问题。
例子:
std::string str = "Hello, World!";
说明:
std::string
的自动管理内存和丰富的接口函数使得字符串处理更加安全和方便。
30. 使用 decltype
和 auto
推导类型
解释:
C++11 引入了 auto
和 decltype
关键字,用于自动推导变量和表达式的类型,减少冗长的类型声明,使代码更简洁。
例子:
auto x = 10; // 自动推导为 int
decltype(x) y = 20; // y 的类型与 x 相同,为 int
说明:
auto
关键字使代码更简洁、可读性更高,decltype
允许获取表达式的类型而不需要显式书写类型。
31. 使用 enum class
代替传统 enum
解释:
C++11 引入的 enum class
是强类型枚举,避免了传统 enum
的隐式转换问题,并且提供了更好的作用域控制。
例子:
enum class Color {
Red, Green, Blue };
Color c = Color::Red;
说明:
enum class
的类型安全性和限定作用域特性避免了枚举值之间的冲突和误用。
32. 在构造函数中使用 explicit
关键字
解释:
explicit
关键字用于防止构造函数进行隐式类型转换,确保对象只能通过显式构造调用创建,避免意外的类型转换。
例子:
class MyClass {
public:
explicit MyClass(int x) {
}
};
MyClass obj = 10; // 错误,没有显式调用构造函数
MyClass obj2(10); // 正确
说明:
explicit
可以避免构造函数被意外地用于隐式类型转换,增强代码的安全性和可读性。
33. 避免重复的代码,遵循 DRY 原则
解释:
DRY(Don’t Repeat Yourself)原则要求尽量避免重复代码,通过抽象、函数重用和模板等方式减少重复,增加代码的可维护性。
例子:
// 不推荐
int add(int a, int b) {
return a + b; }
int add(double a, double b) {
return a + b; }
// 推荐
template <typename T>
T add(T a, T b) {
return a + b; }
说明:
通过模板函数可以避免重复代码,减少代码维护的负担。
34. 使用适当的容器类型
解释:
C++ 标准库提供了多种容器类型(如 std::vector
, std::list
, std::map
等),根据不同的需求选择合适的容器类型以提高代码性能和可读性。
例子:
std::vector<int> v = {
1, 2, 3, 4, 5}; // 对于动态数组的需求
std::map<int, std::string> m; // 对于键值对的需求
说明:
不同容器有不同的特性和性能,选择合适的容器可以显著提高程序的效率。
35. 尽量使用标准库而非自己实现常见功能
解释:
C++ 标准库提供了大量经过良好测试和优化的功能模块(如算法、容器、线程库等)。尽量使用标准库中的实现,而不是自己实现同样的功能,以避免错误和性能问题。
例子:
std::sort(v.begin(), v.end()); // 使用标准库的排序函数
说明:
标准库经过了广泛的测试和优化,使用它可以减少代码中的 bug 并提高程序的可靠性。
36. 使用 [[nodiscard]]
属性防止忽略重要的返回值
解释:
C++17 引入了 [[nodiscard]]
属性,用于提示调用者不要忽略函数的返回值,防止潜在的错误或未处理的情况。
例子:
[[nodiscard]] int calculate() {
return 42;
}
int main() {
calculate(); // 警告:返回值被忽略
int result = calculate(); // 正确用法
}
说明:
[[nodiscard]]
可以帮助开发者避免忽略关键的返回值,减少错误的发生。
37. 对类成员变量尽量使用 private
访问修饰符
解释:
将类的成员变量设置为 private
,并通过公共的成员函数(getter 和 setter)进行访问和修改。这种做法遵循了封装原则,增强了类的内聚性和安全性。
例子:
class MyClass {
private:
int data;
public:
int getData() const {
return data; }
void setData(int value) {
data = value; }
};
说明:
通过限制直接访问成员变量,可以更好地控制类的内部状态,确保其在合理范围内变化。
38. 谨慎使用全局状态(如全局变量、静态变量)
解释:
全局状态(包括全局变量和静态变量)容易引入隐式依赖,导致代码难以维护和调试。应尽量避免或将其封装到类中,并确保线程安全。
例子:
// 不推荐使用全局变量
int globalCounter = 0;
// 推荐使用类来封装状态
class Counter {
static int counter;
public:
static void increment() {
counter++; }
};
说明:
通过类封装全局状态可以减少隐式依赖,并提供更好的控制和管理机制。
39. 在合适的地方使用多态和虚函数
解释:
多态性是面向对象编程的重要特性,通过基类指针或引用调用派生类的重载函数,可以实现灵活的设计。虚函数使得这种调用机制成为可能,但应避免不必要的虚函数调用以提高性能。
例子:
class Base {
public:
virtual void func() {
std::cout << "Base" << std::endl; }
};
class Derived : public Base {
public:
void func() override {
std::cout << "Derived" << std::endl; }
};
void callFunc(Base* b) {
b->func(); // 动态绑定,调用派生类的 func()
}
说明:
使用多态和虚函数可以实现更灵活的接口设计,但要注意其带来的性能开销,在性能敏感的场合尽量避免不必要的虚函数。
40. 使用范围和作用域控制符(如 std::lock_guard
)
解释:
在多线程编程中,使用 std::lock_guard
等范围锁定机制可以确保在作用域结束时自动释放锁,避免死锁和资源泄露。
例子:
#include <mutex>
std::mutex mtx;
void threadSafeFunc() {
std::lock_guard<std::mutex> lock(mtx); // 自动锁定和解锁
// 临界区代码
}
说明:
std::lock_guard
确保了锁在函数作用域结束时自动释放,避免手动释放锁时可能引入的错误。
41. 使用 final
和 override
关键字
解释:
C++11 引入了 final
和 override
关键字,用于标记类或虚函数不允许进一步继承或重写,以及确保派生类正确地重写基类中的虚函数。
例子:
class Base {
public:
virtual void func() {
/* ... */ }
};
class Derived : public Base {
public:
void func() override {
/* 重写基类的 func */ }
};
class FinalClass final {
/* ... */ }; // 不允许继承
说明:
override
确保了函数确实重写了基类中的虚函数,避免因函数签名错误导致的问题;final
关键字防止类或函数被进一步继承或重写,增加设计的明确性。
42. 使用智能指针管理动态内存
解释:
C++11 引入了智能指针(如 std::unique_ptr
, std::shared_ptr
),用于自动管理动态内存的生命周期,避免手动管理内存带来的内存泄漏和悬空指针问题。
例子:
std::unique_ptr<int> ptr(new int(10)); // 自动管理内存
std::shared_ptr<int> ptr2 = std::make_shared<int>(20); // 共享所有权
说明:
智能指针自动释放内存,避免了手动 delete
可能引发的错误,同时可以清晰地表达所有权关系。
43. 使用 noexcept
声明不抛出异常的函数
解释:
noexcept
关键字用于声明函数不会抛出异常。这可以使编译器进行更好的优化,并且在某些情况下可以避免不必要的异常处理机制。
例子:
void safeFunction() noexcept {
// 此函数不会抛出异常
}
说明:
noexcept
明确地告诉编译器和调用者函数是安全的,不会抛出异常,从而使得编译器可以进行更激进的优化。
44. 适当使用类型推导(auto
)但不滥用
解释:
虽然 auto
关键字可以让代码更简洁,但在某些情况下直接指定类型可以使代码更清晰。因此,应在适当的情况下使用 auto
,而不应滥用。
例子:
auto x = 42; // 合适使用,类型为 int
auto iter = myContainer.begin(); // 迭代器类型复杂,适合使用 auto
// 不推荐在复杂类型或非直观的情况下使用 auto
std::map<int, std::vector<std::string>> myMap;
for (const auto& pair : myMap) {
// 对于复杂类型的 auto 可能使代码难以理解
}
说明:
auto
可以减少冗长的类型声明,但有时直接指定类型可以使代码更清晰。因此,在使用时应权衡代码的简洁性与可读性。
45. 使用 std::array
替代 C 风格的数组
解释:
std::array
是 C++11 引入的固定大小的数组类型,提供了与标准库容器类似的接口,且能更好地与标准库的算法配合使用,替代传统的 C 风格数组。
例子:
std::array<int, 5> arr = {
1, 2, 3, 4, 5}; // 定义一个大小为5的数组
说明:
std::array
提供了更好的类型安全和接口,避免了 C 风格数组中的常见问题,如边界溢出。
46. 尽量使用范围 try-catch
(RAII 异常安全)
解释:
在进行资源分配时,尽量使用 RAII 原则(资源获取即初始化),将资源的分配和释放绑定到对象的生命周期中,从而确保异常发生时资源能够被正确释放。
例子:
class FileHandler {
std::fstream file;
public:
FileHandler(const std::string& filename) : file(filename) {
if (!file) throw std::runtime_error("Failed to open file");
}
~FileHandler() {
file.close(); }
};
说明:
通过 RAII 原则,FileHandler
的析构函数会在对象作用域结束时自动关闭文件,无论是否发生异常。
47. 适当使用 mutable
关键字
解释:
mutable
关键字允许在 const
成员函数中修改特定的成员变量。这在某些情况下,如缓存计算结果或统计函数调用次数时非常有用。
例子:
class MyClass {
private:
mutable int cacheValue; // 缓存值
public:
int getValue() const {
if (cacheValue == -1) {
cacheValue = computeValue(); // 可以修改 cacheValue
}
return cacheValue;
}
};
说明:
mutable
关键字允许在 const
成员函数中修改缓存或统计类数据,同时保持接口的 constness。
48. 避免使用 reinterpret_cast
,慎用 static_cast
解释:
reinterpret_cast
是一种非常危险的类型转换方式,它可以将一种指针类型强制转换为另一种完全无关的类型,这可能导致未定义行为。static_cast
允许安全的、受控的类型转换,但在涉及底层类型转换时要小心使用。
例子:
// 避免使用 reinterpret_cast
int* p = reinterpret_cast<int*>(0xDEADBEEF); // 可能导致未定义行为
// 谨慎使用 static_cast
double d = 3.14;
int i = static_cast<int>(d); // 安全的类型转换
说明:
reinterpret_cast
和 static_cast
都有其特定的用途,但滥用可能导致严重的错误,使用时应非常谨慎。
49. 使用 nullptr
而非 NULL
或 0
表示空指针
解释:
C++11 引入了 nullptr
,用来取代 NULL
和 0
作为空指针的标志,nullptr
是类型安全的,避免了将整数 0
与指针混淆的问题。
例子:
void func(int* ptr) {
if (ptr == nullptr) {
// 空指针处理
}
}
说明:
nullptr
明确地表示指针类型,并且不会引起类型混淆,应该总是使用 nullptr
而非 NULL
或 0
。
50. 使用线程安全的容器和原子操作
解释:
在多线程编程中,确保容器和变量的操作是线程安全的。C++ 标准库提供了一些线程安全的工具和原子操作(如 std::atomic
),应优先使用这些工具来避免数据竞争。
例子:
std::atomic<int> atomicCounter(0);
void increment() {
atomicCounter++; // 线程安全的操作
}
说明:
使用 std::atomic
和其他线程安全工具,可以避免在多线程环境下发生数据竞争和不确定行为。
51. 使用 std::chrono
处理时间和日期
解释:
C++11 引入了 std::chrono
,提供了一套处理时间和日期的标准化工具,替代传统的 C 风格时间函数。std::chrono
提供了更强大的功能和类型安全性。
例子:
#include <chrono>
using namespace std::chrono;
auto start = high_resolution_clock::now();
// ...代码运行...
auto end = high_resolution_clock::now();
auto duration = duration_cast<milliseconds>(end - start);
std::cout << "Time taken: " << duration.count() << "ms" << std::endl;
说明:
std::chrono
提供了精确的时间测量功能,并且可以避免传统时间处理函数中的很多陷阱。
52. 使用 std::variant
和 std::visit
处理多种类型的数据 (继续)
解释:
std::variant
是 C++17 引入的类型安全的联合体(union),它可以存储多个可能的类型之一。std::visit
可以方便地访问 std::variant
中的数据,并执行相应的操作。
例子:
std::variant<int, std::string> data;
data = 10;
data = "Hello";
std::visit([](auto&& arg) {
std::cout << arg << std::endl; // 自动识别类型并处理
}, data);
说明:
std::variant
提供了灵活性,可以在不确定数据类型时使用,同时 std::visit
确保了类型安全的访问和操作。
53. 使用 std::optional
处理可能不存在的值
解释:
C++17 引入了 std::optional
,用于表示一个可能为空的值。std::optional
可以避免使用指针或特殊的标志值来表示值的缺失,从而提高代码的安全性和可读性。
例子:
std::optional<int> findValue(bool found) {
if (found) {
return 42;
} else {
return std::nullopt; // 表示值不存在
}
}
auto result = findValue(true);
if (result) {
std::cout << "Found: " << *result << std::endl;
}
说明:
std::optional
提供了一种更清晰和安全的方式来处理可能不存在的值,避免了空指针和不明确的标志值。
54. 使用 std::any
存储任意类型的值
解释:
C++17 引入了 std::any
,用于存储任意类型的值,提供了类型安全的存储和提取功能。与 std::variant
不同,std::any
可以存储任何类型,但你需要知道其类型才能提取值。
例子:
std::any data = 10;
data = std::string("Hello");
try {
std::string str = std::any_cast<std::string>(data);
std::cout << str << std::endl;
} catch (const std::bad_any_cast& e) {
std::cerr << "Bad cast: " << e.what() << std::endl;
}
说明:
std::any
提供了一种灵活的方法来存储和处理任意类型的数据,但需要谨慎处理类型转换。
55. 使用 constexpr
优化编译时计算
解释:
constexpr
关键字允许你在编译时进行常量表达式计算。通过 constexpr
,你可以编写出更高效的代码,减少运行时的计算。
例子:
constexpr int square(int x) {
return x * x;
}
constexpr int result = square(10); // 编译时计算
说明:
使用 constexpr
可以将一些计算推迟到编译时进行,提高程序的运行时性能,并且帮助捕捉逻辑错误。
56. 使用范围 for
循环替代传统循环
解释:
C++11 引入的范围 for
循环使得迭代容器和数组变得更加简单和直观。相比传统的 for
循环,范围 for
循环减少了手动管理迭代器的需求,并提高了代码的可读性。
例子:
std::vector<int> v = {
1, 2, 3, 4, 5};
for (int n : v) {
std::cout << n << std::endl;
}
说明:
范围 for
循环减少了迭代器相关的错误,并且使代码更简洁、可读。
57. 使用 std::move
和 std::forward
实现移动语义和完美转发
解释:
C++11 引入了移动语义,通过 std::move
,你可以显式地将资源从一个对象转移到另一个对象,而不进行昂贵的拷贝。std::forward
则用于完美转发函数模板中的参数,以保持其左值或右值属性。
例子:
class MyClass {
public:
MyClass(std::string&& str) : data(std::move(str)) {
} // 移动语义
private:
std::string data;
};
template<typename T>
void wrapper(T&& arg) {
func(std::forward<T>(arg)); // 完美转发
}
说明:
std::move
和 std::forward
是 C++11 中的重要特性,可以显著提高程序的性能,特别是在处理资源密集型对象时。
58. 避免不必要的动态内存分配
解释:
虽然 C++ 提供了灵活的动态内存分配机制,但频繁的动态内存分配会导致性能问题,尤其是在实时系统中。应尽量使用栈分配或使用 std::vector
等自动管理内存的容器来减少不必要的动态内存分配。
例子:
void func() {
int arr[100]; // 栈分配,性能高
std::vector<int> vec(100); // 堆分配,但自动管理内存
}
说明:
栈分配的对象生命周期受控于作用域,性能更高,且避免了内存泄漏的风险。应根据需求选择合适的内存分配方式。
59. 使用 std::tuple
和 std::pair
简化多返回值
解释:
C++ 标准库提供了 std::pair
和 std::tuple
,用于将多个值组合在一起返回。它们提供了一种方便的方法来处理和返回多个值,避免定义额外的结构体或类。
例子:
std::tuple<int, std::string, double> getData() {
return std::make_tuple(1, "hello", 3.14);
}
auto [id, name, value] = getData(); // C++17 结构化绑定
说明:
std::tuple
和 std::pair
提供了简洁的方式来处理多个返回值,使代码更紧凑并减少了额外的数据结构定义。
60. 通过 std::regex
处理正则表达式
解释:
C++11 引入了 std::regex
,用于处理正则表达式匹配和搜索。std::regex
提供了强大的字符串处理功能,避免了依赖外部库的需求。
例子:
#include <regex>
std::string s = "abc123xyz";
std::regex re("\\d+");
std::smatch match;
if (std::regex_search(s, match, re)) {
std::cout << "Found number: " << match.str() << std::endl;
}
说明:
std::regex
提供了丰富的字符串处理能力,使得在 C++ 中使用正则表达式更加直观和方便。
61. 使用 std::scoped_lock
进行线程同步
解释:
C++17 引入了 std::scoped_lock
,提供了一种同时锁定多个互斥体的方式,防止死锁。它是 std::lock_guard
的改进版,特别适用于需要同时锁定多个资源的场景。
例子:
std::mutex m1, m2;
void func() {
std::scoped_lock lock(m1, m2); // 同时锁定 m1 和 m2
// 临界区代码
}
说明:
std::scoped_lock
确保了锁的获取顺序,避免了死锁问题,并且可以在作用域结束时自动释放锁。
62. 避免使用原始的 new
和 delete
操作符
解释:
尽量避免直接使用 new
和 delete
来进行内存管理。相反,应使用智能指针或容器来管理动态分配的内存,以避免内存泄漏和其他内存管理问题。
例子:
auto ptr = std::make_unique<int>(10); // 使用智能指针管理内存
说明:
智能指针和标准容器会自动管理内存,减少手动内存管理带来的错误风险。
63. 使用 std::lock_guard
自动管理互斥锁
解释:
std::lock_guard
是一个简单的锁管理器,能够在构造时自动锁定互斥锁,并在析构时自动解锁。这确保了在函数退出时,无论是正常退出还是异常退出,互斥锁都能正确释放。
例子:
std::mutex mtx;
void safeFunction() {
std::lock_guard<std::mutex> lock(mtx);
// 临界区代码
} // 离开作用域时自动释放锁
说明:
std::lock_guard
确保了线程安全,减少了忘记释放锁的风险。
64. 使用 std::atomic
进行原子操作
解释:
在多线程环境下,使用 std::atomic
来进行原子操作,保证数据一致性。std::atomic
提供了轻量级的锁机制,适用于基本数据类型。
例子:
std::atomic<int> counter(0);
void increment() {
counter++; // 原子操作
}
说明:
std::atomic
确保了在多线程环境下操作的原子性,避免数据竞争。
65. 使用 std::unique_lock
进行灵活的锁管理
解释:
std::unique_lock
提供了比 std::lock_guard
更加灵活的锁管理功能,支持延迟锁定、提前解锁、条件变量等操作。
例子:
std::mutex mtx;
void func() {
std::unique_lock<std::mutex> lock(mtx);
// 临界区代码
lock.unlock(); // 提前解锁
// 非临界区代码
}
说明:
std::unique_lock
提供了更加灵活的锁管理方式,适用于复杂的同步场景。
66. 使用 std::condition_variable
实现线程间的协调
解释:
std::condition_variable
用于在线程间进行同步,允许一个线程等待条件的满足,另一个线程通知条件已经满足。
例子:
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void waitFunction() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] {
return ready; }); // 等待 ready 变为 true
// 临界区代码
}
void notifyFunction() {
std::lock_guard<std::mutex> lock(mtx);
ready = true;
cv.notify_one(); // 通知等待线程
}
说明:
std::condition_variable
提供了一种高效的线程间同步方式,避免了忙等待。
67. 使用 std::filesystem
处理文件系统操作
解释:
C++17 引入了 std::filesystem
,提供了一套标准化的文件系统操作 API,替代了传统的 C 风格文件操作方法。
例子:
#include <filesystem>
namespace fs = std::filesystem;
void listFiles(const fs::path& directory) {
for (const auto& entry : fs::directory_iterator(directory)) {
std::cout << entry.path() << std::endl;
}
}
说明:
std::filesystem
提供了跨平台的文件系统操作接口,简化了文件和目录的操作。
68. 使用 std::string_view
避免不必要的字符串拷贝
解释:
C++17 引入了 std::string_view
,它是对字符串的一种轻量级的非拥有(non-owning)视图,可以避免不必要的字符串拷贝,提高性能。
例子:
void printString(std::string_view str) {
std::cout << str << std::endl;
}
std::string myString = "Hello, world!";
printString(myString); // 不会拷贝字符串
说明:
std::string_view
提供了一种高效的方式来处理字符串,特别适合在只读场景下使用。
69. 使用 std::any_of
和 std::all_of
进行集合判断
解释:
C++11 标准库提供了 std::any_of
和 std::all_of
算法,用于判断集合中是否存在满足特定条件的元素,或者所有元素是否都满足条件。
例子:
std::vector<int> numbers = {
1, 2, 3, 4, 5};
bool hasEven = std::any_of(numbers.begin(), numbers.end(), [](int i) {
return i % 2 == 0;
});
bool allPositive = std::all_of(numbers.begin(), numbers.end(), [](int i) {
return i > 0;
});
说明:
std::any_of
和 std::all_of
提供了简洁的集合判断方式,减少了循环的使用,提高代码的可读性。
70. 使用 std::unique_ptr
管理动态资源
解释:
std::unique_ptr
是 C++11 引入的智能指针,用于管理动态资源。它确保资源在离开作用域时自动释放,避免了内存泄漏。
例子:
std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>();
说明:
std::unique_ptr
提供了独占所有权的语义,适用于独占资源的管理。
71. 使用 std::shared_ptr
共享资源的所有权
解释:
std::shared_ptr
是一种智能指针,允许多个所有者共享资源的所有权。当最后一个所有者被销毁时,资源会被自动释放。
例子:
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> ptr2 = ptr1; // 共享所有权
说明:
std::shared_ptr
提供了引用计数机制,确保资源在不再需要时自动释放。
72. 使用 std::weak_ptr
打破循环引用
解释:
std::weak_ptr
是一种不参与引用计数的智能指针,常用于打破 std::shared_ptr
之间的循环引用,避免内存泄漏。
例子:
std::shared_ptr<Node> ptr1 = std::make_shared<Node>();
std::weak_ptr<Node> ptr2 = ptr1; // 弱引用,不影响引用计数
说明:
std::weak_ptr
允许访问 std::shared_ptr
所指向的对象,但不会影响其生命周期,适合用于解决循环引用的问题。
73. 使用 enum class
代替传统的 enum
解释:
C++11 引入了 enum class
,它提供了更强的类型安全性,避免了传统 enum
的命名冲突问题。
例子:
enum class Color {
Red, Green, Blue };
Color c = Color::Red;
说明:
enum class
提供了更严格的类型检查,避免了不同枚举类型之间的不当转换。
74. 使用 constexpr
和 consteval
进行编译时计算
解释:
constexpr
允许函数在编译时进行计算,而 C++20 引入的 consteval
关键字确保函数只能在编译时求值。这可以提高代码的性能,并且在编译时捕捉错误。
例子:
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
consteval int square(int x) {
return x * x;
}
constexpr int result1 = factorial(5); // 编译时计算
consteval int result2 = square(10); // 强制编译时计算
说明:
constexpr
和 consteval
可以帮助你优化代码,确保某些计算在编译时完成,从而减少运行时的开销。
75. 使用 [[nodiscard]]
标记重要的返回值
解释:
C++17 引入了 [[nodiscard]]
属性,用于标记那些返回值应被使用的函数,以防止开发者忽略重要的返回值。
例子:
[[nodiscard]] int importantFunction() {
return 42;
}
int main() {
importantFunction(); // 编译器会发出警告:返回值未使用
}
说明:
使用 [[nodiscard]]
可以减少因忽略返回值而导致的潜在错误,特别是在处理资源或错误码时。
当然,再加一个:
76. 使用 std::string_view
处理字符串切片
解释:
std::string_view
是 C++17 引入的轻量级非拥有字符串视图,它提供了对字符串的一种视图而不需要复制实际的字符串数据。适用于需要读取字符串的部分内容而无需修改或复制原始字符串的情况。
例子:
#include <string>
#include <string_view>
#include <iostream>
void printSubstring(std::string_view strView) {
std::cout << strView << std::endl;
}
int main() {
std::string str = "Hello, World!";
printSubstring(str.substr(0, 5)); // 传递字符串视图,避免额外拷贝
}
说明:
std::string_view
提供了高效的字符串处理方法,避免了不必要的字符串拷贝,适合用于需要传递或处理字符串切片的场景。它不会管理底层数据的生命周期,因此使用时需要确保原始数据在视图使用期间有效。
77. 使用 std::chrono
进行时间测量和操作
解释:
C++11 引入了 std::chrono
,提供了跨平台的时间测量和操作工具,替代了传统的 C 风格时间处理函数。
例子:
#include <chrono>
#include <thread>
void func() {
auto start = std::chrono::high_resolution_clock::now();
std::this_thread::sleep_for(std::chrono::seconds(1));
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = end - start;
std::cout << "Elapsed time: " << elapsed.count() << " seconds" << std::endl;
}
说明:
std::chrono
提供了高精度的时间测量工具,适用于性能测量、延时操作等场景。
78. 使用 std::async
和 std::future
进行异步任务处理
解释:
C++11 引入了 std::async
和 std::future
,提供了一种简便的方法来启动异步任务并获取其结果。
例子:
#include <future>
int compute() {
return 42;
}
int main() {
std::future<int> result = std::async(compute);
std::cout << "Result: " << result.get() << std::endl;
}
说明:
std::async
和 std::future
提供了简单的异步编程接口,适合用于并发计算或 I/O 操作。
79. 使用 std::accumulate
进行集合的累计操作
解释:
std::accumulate
是一个标准算法,用于在集合上执行累加操作。它能够简化循环代码,提高代码的可读性和维护性。
例子:
#include <numeric>
#include <vector>
int sum = std::accumulate(v.begin(), v.end(), 0);
说明:
std::accumulate
提供了简洁的累加操作方式,适用于集合中的数值汇总。
80. 使用 std::transform
进行元素转换
解释:
std::transform
是标准算法库中的一个算法,用于将一个集合中的元素转换为另一个集合中的元素。
例子:
std::vector<int> v = {
1, 2, 3, 4, 5};
std::vector<int> results(v.size());
std::transform(v.begin(), v.end(), results.begin(), [](int i) {
return i * i;
});
说明:
std::transform
可以简化集合元素转换的过程,提高代码的简洁性和可读性。
81. 使用 std::invoke
简化函数调用
解释:
C++17 引入的 std::invoke
提供了一种统一的方式来调用函数、成员函数或函数对象。它简化了函数调用的语法,尤其是对于复杂的函数对象和成员函数。
例子:
struct Foo {
void print(int i) {
std::cout << "Foo: " << i << std::endl; }
};
Foo foo;
std::invoke(&Foo::print, foo, 42); // 调用成员函数
说明:
std::invoke
提供了统一的函数调用方式,适用于模板编程和泛型编程。
82. 使用 std::remove_if
过滤集合中的元素
解释:
std::remove_if
是一个标准算法,用于移除集合中满足特定条件的元素。它不会改变集合的大小,而是将符合条件的元素移到末尾,返回新末尾的迭代器。
例子:
std::vector<int> v = {
1, 2, 3, 4, 5, 6};
auto new_end = std::remove_if(v.begin(), v.end(), [](int i) {
return i % 2 == 0;
});
v.erase(new_end, v.end());
说明:
std::remove_if
提供了一种高效的元素过滤方式,减少了手动处理的复杂度。
83. 使用 std::partial_sort
实现部分排序
解释:
std::partial_sort
是标准算法,用于对集合中的一部分元素进行排序,使得部分元素有序,而其余元素未定义。
例子:
std::vector<int> v = {
3, 1, 4, 1, 5, 9, 2};
std::partial_sort(v.begin(), v.begin() + 3, v.end());
说明:
std::partial_sort
提供了一种高效的部分排序方式,适用于仅需要部分元素有序的场景。
84. 使用 std::any
存储和处理不同类型的数据
解释:
std::any
是 C++17 引入的类型安全的容器,可以存储任意类型的值。与 std::variant
不同的是,std::any
可以存储任意类型,而不需要事先定义类型列表。
例子:
std::any a = 42;
a = std::string("Hello");
说明:
std::any
提供了灵活的数据存储方式,适用于需要存储多种不同类型数据的场景。
85. 使用 std::visit
访问 std::variant
中的值
解释:
std::visit
是 C++17 引入的工具,用于访问 std::variant
中存储的值。它提供了类型安全的访问方式,避免了类型错误。
例子:
std::variant<int, std::string> data = 10;
std::visit([](auto&& arg) {
std::cout << arg << std::endl;
}, data);
说明:
std::visit
提供了类型安全的方式来处理 std::variant
,确保了正确的类型匹配。
86. 使用 std::shared_mutex
实现读写锁
解释:
C++17 引入了 std::shared_mutex
,提供了读写锁的功能。它允许多个线程同时进行读操作,而写操作则是互斥的。
例子:
std::shared_mutex mtx;
void readData() {
std::shared_lock lock(mtx);
// 读取操作
}
void writeData() {
std::unique_lock lock(mtx);
// 写入操作
}
说明:
std::shared_mutex
提供了更高效的并发控制方式,适用于读多写少的场景。
87. 使用 [[deprecated]]
标记不推荐使用的代码
解释:
C++14 引入了 [[deprecated]]
属性,用于标记不推荐使用的代码或函数。编译器会在使用时发出警告,提醒开发者避免使用这些代码。
例子:
[[deprecated("Use newFunction instead")]]
void oldFunction() {
// 不推荐使用的代码
}
说明:
[[deprecated]]
帮助你逐步淘汰旧代码,提醒团队成员迁移到新的实现。
88. 使用 [[nodiscard]]
强制检查函数返回值
解释:
C++17 引入的 [[nodiscard]]
属性可以用来标记一个函数的返回值必须被使用。这样可以避免重要的返回值被忽略,例如错误码、指针等。
例子:
[[nodiscard]] int compute() {
return 42;
}
int main() {
compute(); // 编译器将发出警告:返回值未使用
}
说明:
使用 [[nodiscard]]
可以有效减少因忽略返回值而导致的潜在错误,确保返回值在调用者代码中得到适当处理。
89. 使用 std::optional
处理可能为空的值
解释:
C++17 引入了 std::optional
,用于表示一个值可能存在也可能不存在的情况。它提供了一种类型安全的方式来处理可能为空的返回值,而不是使用指针或特殊值来表示。
例子:
std::optional<int> findValue(bool condition) {
if (condition) {
return 10;
} else {
return std::nullopt; // 返回空值
}
}
int main() {
auto value = findValue(true);
if (value) {
std::cout << "Found: " << *value << std::endl;
}
}
说明:
std::optional
提供了处理可能为空的值的优雅方式,减少了空指针异常和不明确的返回值处理。
90. 使用 std::variant
处理多种可能类型的值
解释:
C++17 引入了 std::variant
,它可以存储一个由多种类型之一表示的值,并且通过 std::visit
可以安全地访问该值。
例子:
std::variant<int, std::string> data;
data = 10;
std::visit([](auto&& arg) {
std::cout << arg << std::endl;
}, data);
说明:
std::variant
提供了一种处理多态性的类型安全方法,特别适用于需要存储多种可能类型的场景。
91. 使用 noexcept
声明不会抛出异常的函数
解释:
noexcept
关键字可以用来声明一个函数不会抛出异常,这不仅能提高代码的可读性,还能让编译器对其进行优化。
例子:
void safeFunction() noexcept {
// 确保不抛出异常的代码
}
说明:
noexcept
提供了更好的错误处理和代码优化的保证,适用于那些不会或不应该抛出异常的函数。
92. 使用 final
防止类被继承
解释:
final
关键字可以用来标记一个类或虚函数不能被继承或重写,从而防止意外的继承层级扩展。
例子:
class Base {
virtual void func() final; // 不能在派生类中重写
};
class Derived final : public Base {
}; // 不能再派生
说明:
final
关键字确保了设计意图的清晰表达,防止类和函数被意外地继承或重写。
93. 使用 override
明确重写虚函数
解释:
C++11 引入了 override
关键字,用于明确标记一个虚函数是重写基类的虚函数,这有助于编译器进行检查,防止意外的函数签名错误。
例子:
class Base {
virtual void func();
};
class Derived : public Base {
void func() override; // 明确重写基类函数
};
说明:
override
提供了重写虚函数的安全性,减少了重写时的签名错误风险。
94. 使用 [[maybe_unused]]
标记未使用的变量
解释:
[[maybe_unused]]
属性可以用来标记那些在某些编译单元中未使用的变量或函数,防止编译器发出未使用警告。
例子:
void func() {
[[maybe_unused]] int x = 10; // 标记为可能未使用
}
说明:
[[maybe_unused]]
提供了一种避免编译警告的方式,同时保持代码的清晰性。
95. 使用 inline
优化小函数的调用
解释:
inline
关键字用于建议编译器将小函数的代码内联展开,从而减少函数调用的开销。
例子:
inline int square(int x) {
return x * x;
}
说明:
inline
在合适的场景下可以提升性能,但应谨慎使用以避免代码膨胀。
96. 避免使用 using namespace
在全局作用域中引入命名空间
解释:
在全局作用域中使用 using namespace
可能导致命名冲突,尤其是在大型项目或库中。
例子:
// 不推荐在全局作用域中使用 using namespace
using namespace std; // 可能引发命名冲突
int main() {
std::cout << "Hello, World!" << std::endl; // 明确使用命名空间
}
说明:
尽量在局部作用域或特定场景中使用 using
声明,以减少命名冲突的风险。
97. 使用 static_assert
进行编译时断言
解释:
C++11 引入了 static_assert
,允许你在编译时检查某些条件,并在条件不满足时产生编译错误。
例子:
static_assert(sizeof(int) == 4, "This code requires 4-byte integers");
说明:
static_assert
提供了一种在编译时捕捉错误的手段,确保代码的某些假设在编译时就能验证。
98. 使用 std::array
代替 C 风格数组
解释:
std::array
是 C++11 引入的模板类,提供了对静态数组的封装,支持 STL 算法和迭代器,避免了 C 风格数组的诸多问题。
例子:
std::array<int, 3> arr = {
1, 2, 3};
说明:
std::array
提供了安全和方便的数组操作接口,建议优先使用。
99. 使用 std::tuple
处理多个返回值
解释:
std::tuple
是 C++11 引入的类模板,允许将多个不同类型的值组合在一起,特别适合用于返回多个值。
例子:
std::tuple<int, std::string> getPair() {
return std::make_tuple(1, "Hello");
}
int main() {
auto [num, str] = getPair(); // 解构赋值
}
说明:
std::tuple
提供了一种简洁的方式来处理多个返回值,提高了代码的可读性和灵活性。
100. 使用 std::move
移动资源,避免不必要的拷贝
解释:
C++11 引入了 std::move
,它允许你显式地将资源从一个对象移动到另一个对象,避免不必要的拷贝操作,提高性能。
例子:
std::string str = "Hello";
std::vector<std::string> v;
v.push_back(std::move(str)); // 移动而不是拷贝
说明:
std::move
提供了优化资源管理的手段,特别是在处理大对象或复杂资源时能够显著提升性能。
至此,我们列出了 100 个 C++ 编程的优秀习惯和注意事项。这些实践涵盖了代码风格、性能优化、内存管理、并发处理等各个方面,旨在帮助你写出更安全、高效、可维护的 C++ 代码。