高效C++:从C到C++
### 高效C++:从C到C++ #### 第一章:从C转向C++ **条款1:尽量用const和inline而不用#define** - **背景**:在C语言中,`#define`宏定义被广泛用于常量的定义。然而,在C++中,我们有更好的替代方案——`const`关键字和`inline`函数。 - **原因**:`#define`在预处理阶段被替换,因此不会被编译器检查。这意味着如果在宏定义中出现错误(如拼写错误),可能直到运行时才会发现。此外,宏定义在整个程序中展开,可能导致代码膨胀。相比之下,`const`和`inline`提供了更好的错误检测能力,并且减少了代码冗余。 - **示例**:用`#define`定义常量的方式: ```cpp #define ASPECT_RATIO 1.653 ``` 使用`const`定义常量: ```cpp const double aspect_ratio = 1.653; ``` 这种方式使得编译器能够进行类型检查,提高代码的安全性和可读性。 **条款3:尽量用new和delete而不用malloc和free** - **背景**:C语言中使用`malloc`和`free`进行内存分配和释放。C++提供了`new`和`delete`运算符,不仅分配内存,还可以调用构造函数和析构函数。 - **原因**:`new`和`delete`不仅分配内存,还负责调用对象的构造函数和析构函数,这对于管理具有复杂生命周期的对象至关重要。 - **示例**:使用`malloc`和`free`: ```cpp int *p = (int*)malloc(sizeof(int)); free(p); ``` 使用`new`和`delete`: ```cpp int *p = new int; delete p; ``` 在C++中,`new`和`delete`的使用更加安全和方便。 **条款4:尽量使用C++风格的注释** - **背景**:C++支持两种注释风格:C风格的`/* ... */`和C++风格的`//`。 - **原因**:C++风格的注释更容易阅读,不会像C风格那样容易嵌套不当。此外,C++风格的注释只允许单行注释,避免了嵌套问题。 - **示例**:C风格的注释: ```cpp /* 这是一个 多行注释 */ ``` C++风格的注释: ```cpp // 这是一个单行注释 ``` 使用C++风格的注释可以使代码更加清晰。 #### 第二章:内存管理 **条款5:对应的new和delete要采用相同的形式** - **背景**:在C++中,`new`和`delete`有不同的形式:`new`和`delete[]`用于数组,而单独的`new`和`delete`用于单个对象。 - **原因**:使用不匹配的`new`和`delete`形式会导致内存泄漏或者程序崩溃。 - **示例**:正确的使用: ```cpp int *p = new int[10]; delete[] p; ``` 错误的使用: ```cpp int *p = new int[10]; delete p; // 错误:应该使用delete[] ``` **条款6:析构函数里对指针成员调用delete** - **背景**:在类的析构函数中释放动态分配的内存是非常重要的。 - **原因**:如果不释放动态分配的内存,会导致内存泄漏。 - **示例**: ```cpp class MyClass { public: MyClass() { data = new int; } ~MyClass() { delete data; } // 释放内存 private: int *data; }; ``` **条款7:预先准备好内存不够的情况** - **背景**:在动态分配内存时,应考虑到内存不足的情况。 - **原因**:内存不足可能会导致`new`失败并抛出异常。 - **示例**: ```cpp try { int *p = new int[1000000]; // 分配大量内存 } catch (std::bad_alloc &e) { std::cout << "Memory allocation failed: " << e.what() << std::endl; } ``` #### 第三章:构造函数,析构函数和赋值操作符 **条款11:为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符** - **背景**:当类中包含动态分配的资源时,拷贝构造函数和赋值操作符需要正确实现以避免浅拷贝问题。 - **原因**:如果不正确实现这些操作符,会导致资源未正确复制或释放,从而引发内存泄漏或其他问题。 - **示例**: ```cpp class MyClass { public: MyClass(const MyClass &other) { data = new int(*other.data); // 深拷贝 } MyClass &operator=(const MyClass &other) { if (this != &other) { delete data; data = new int(*other.data); // 深拷贝 } return *this; } private: int *data; }; ``` **条款12:尽量使用初始化而不要在构造函数里赋值** - **背景**:构造函数可以使用初始化列表来初始化成员变量。 - **原因**:使用初始化列表比在构造函数体中赋值更高效,因为前者直接调用成员变量的构造函数,后者则可能需要额外的复制操作。 - **示例**: ```cpp class MyClass { public: MyClass(int value) : data(value) {} // 初始化列表 private: int data; }; ``` **条款14:确定基类有虚析构函数** - **背景**:当一个类作为其他类的基类时,应确保其析构函数为虚函数。 - **原因**:如果没有虚析构函数,通过基类指针删除派生类对象时会导致资源泄露。 - **示例**: ```cpp class Base { public: virtual ~Base() {} // 虚析构函数 }; class Derived : public Base { public: ~Derived() { delete data; } private: int *data; }; ``` **条款16:在operator=中对所有数据成员赋值** - **背景**:实现赋值操作符时需要对所有数据成员进行赋值。 - **原因**:确保赋值操作符正确执行深拷贝,避免浅拷贝带来的问题。 - **示例**: ```cpp class MyClass { public: MyClass &operator=(const MyClass &other) { if (this != &other) { delete data; data = new int(*other.data); } return *this; } private: int *data; }; ``` **条款17:在operator=中检查给自己赋值的情况** - **背景**:赋值操作符应检查是否是对自己进行赋值。 - **原因**:自我赋值检查可以避免不必要的内存分配和释放操作,提高效率。 - **示例**: ```cpp class MyClass { public: MyClass &operator=(const MyClass &other) { if (this != &other) { // 自我赋值检查 delete data; data = new int(*other.data); } return *this; } private: int *data; }; ``` #### 第四章:类和函数:设计与声明 **条款18:争取使类的接口完整并且最小** - **背景**:类的设计应遵循最小接口原则。 - **原因**:最小化接口可以减少耦合度,提高代码的灵活性和可维护性。 - **示例**:仅提供必要的公共接口: ```cpp class MyClass { public: void doSomething(); int getData() const; private: int data; }; ``` **条款21:尽可能使用const** - **背景**:`const`关键字用于标记不可修改的数据成员或函数参数。 - **原因**:使用`const`可以提高代码的健壮性和可读性。 - **示例**: ```cpp class MyClass { public: int getData() const; // 声明为const成员函数 private: int data; }; ``` **条款22:尽量用“传引用”而不用“传值”** - **背景**:传递对象时应尽量使用引用而非值传递。 - **原因**:引用传递可以避免复制开销,提高性能。 - **示例**: ```cpp void process(MyClass &obj); // 引用传递 ``` **条款23:必须返回一个对象时不要试图返回一个引用** - **背景**:当函数返回一个临时对象时,不应返回该对象的引用。 - **原因**:返回一个临时对象的引用会导致不确定的行为。 - **示例**: ```cpp MyClass createObject() { MyClass obj; return obj; // 返回对象,而不是引用 } ``` **条款25:避免对指针和数字类型重载** - **背景**:重载指针和整型的操作符可能带来混淆。 - **原因**:重载这些类型可能会导致意料之外的行为,尤其是在其他程序员阅读代码时。 - **示例**: ```cpp class MyClass { public: MyClass operator+(const MyClass &other) const; }; ``` **条款27:如果不想使用隐式生成的函数就要显式地禁止它** - **背景**:默认情况下,C++编译器会为类生成默认的构造函数、拷贝构造函数等。 - **原因**:如果类包含了一些特殊的资源管理逻辑,可能需要禁止默认函数的生成。 - **示例**: ```cpp class MyClass { public: MyClass(const MyClass &) = delete; // 禁止拷贝构造函数 MyClass &operator=(const MyClass &) = delete; // 禁止赋值操作符 }; ``` #### 第五章:类和函数: 实现 **条款29:避免返回内部数据的句柄** - **背景**:直接暴露内部数据可能导致外部代码破坏类的状态。 - **原因**:返回内部数据的引用或指针可能会导致外部代码意外地修改内部状态。 - **示例**: ```cpp class MyClass { public: const int *getData() const { return &data; } // 返回常量指针 private: int data; }; ``` **条款31:千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针的引用** - **背景**:返回局部对象的引用会导致悬空引用。 - **原因**:局部对象在函数退出后会被销毁,因此返回的引用将变得无效。 - **示例**: ```cpp class MyClass { public: MyClass *create() { MyClass *p = new MyClass(); // 动态分配 return p; } }; ``` **条款33:明智地使用内联** - **背景**:`inline`关键字用于指示编译器尝试内联函数调用。 - **原因**:过度使用`inline`可能会导致代码膨胀。 - **示例**: ```cpp inline int add(int a, int b) { return a + b; } ``` **条款34:将文件间的编译依赖性降至最低** - **背景**:减少文件间的依赖可以提高编译速度和模块化。 - **原因**:过多的依赖关系会导致编译时间增加,并降低代码的可维护性。 - **示例**:使用头文件保护: ```cpp // myclass.h class MyClass { public: void doSomething(OtherClass &other); }; // myclass.cpp #include "otherclass.h" void MyClass::doSomething(OtherClass &other) { // ... } ``` #### 第六章:继承和面向对象设计 **条款35:使公有继承体现"是一个"的含义** - **背景**:公有继承表示派生类是基类的一种。 - **原因**:错误的继承关系会导致设计上的混乱。 - **示例**:正确使用公有继承: ```cpp class Animal { public: virtual void makeSound() const = 0; }; class Dog : public Animal { public: void makeSound() const override { std::cout << "Woof!" << std::endl; } }; ``` **条款37:决不要重新定义继承而来的非虚函数** - **背景**:重定义非虚函数违反了Liskov替换原则。 - **原因**:这种做法可能会导致不可预期的行为。 - **示例**: ```cpp class Base { public: void doSomething(); }; class Derived : public Base { public: void doSomething() override { // 错误:doSomething()不是虚函数 // ... } }; ``` **条款39:避免"向下转换"继承层次** - **背景**:向下转换(cast down the hierarchy)可能会导致类型安全问题。 - **原因**:如果基类指针指向的是派生类对象,将其转换为派生类指针可能引起问题。 - **示例**: ```cpp class Base { public: virtual void doSomething(); }; class Derived : public Base { public: void doSomething() override { // ... } }; void process(Base *base) { Derived *derived = static_cast<Derived*>(base); // 可能抛出异常 derived->doSomething(); } ``` **条款43:明智地使用多继承** - **背景**:多继承使得一个类可以从多个基类继承。 - **原因**:多继承可能导致菱形继承等问题。 - **示例**: ```cpp class Interface1 { public: virtual void doSomething(); }; class Interface2 { public: virtual void doAnotherThing(); }; class MyClass : public Interface1, public Interface2 { public: void doSomething() override { // ... } void doAnotherThing() override { // ... } }; ``` #### 第七章:杂项 **条款45:弄清C++在幕后为你所写、所调用的函数** - **背景**:了解编译器如何处理代码有助于更好地编写高效的C++程序。 - **原因**:掌握编译器的行为可以帮助开发者避免陷阱。 - **示例**:了解编译器生成的默认构造函数和析构函数。 **条款46:宁可编译和链接时出错,也不要运行时出错** - **背景**:通过静态检查工具可以在编译或链接阶段捕获错误。 - **原因**:运行时错误通常更难诊断和修复。 - **示例**:使用现代C++特性如`std::optional`来避免运行时错误。 **条款47:确保非局部静态对象在使用前被初始化** - **背景**:非局部静态对象的初始化顺序不确定。 - **原因**:如果两个或多个非局部静态对象相互依赖,可能会导致未初始化的问题。 - **示例**:使用局部静态对象来避免此类问题。 **条款48:重视编译器警告** - **背景**:编译器警告通常指示潜在的错误。 - **原因**:忽略警告可能导致难以发现的bug。 - **示例**:配置编译器以显示所有警告,并在构建过程中解决它们。 **条款49:熟悉标准库** - **背景**:C++标准库提供了丰富的功能。 - **原因**:使用标准库可以提高代码质量,减少开发时间。 - **示例**:了解并使用`std::vector`代替自定义数组管理。 **条款50:提高对C++的认识** - **背景**:持续学习新的C++特性和最佳实践。 - **原因**:随着C++的发展,新的特性不断被引入,保持更新有助于编写更高效的代码。 - **示例**:定期阅读C++书籍、参加在线课程和研讨会。
剩余63页未读,继续阅读
- ffxffxffx2017-07-07内容还不错。
- 粉丝: 161
- 资源: 34
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助