Effective C++笔记

文章目录

    • 01 视 C++ 为一个语言联邦
    • 02 尽量以 const、enum、inline 替换 #define
    • 03 尽可能使用 const
    • 04 确定对象被使用前已被初始化
    • 05 了解 C++ 默默编写并调用哪些函数
    • 06 若不想使用编译器自动生成的函数,就应该明确拒绝
    • 07 为多态基类声明 virtual 析构函数
    • 08 别让异常逃离析构函数
    • 09 绝不在构造和析构过程中调用 virtual 函数
    • 10 令 operator= 返回一个 reference to *this
    • 12 复制对象时勿忘其每一个成分
    • 20 宁以 pass-by-reference-to-const 替换 pass-by-value
    • 22 将成员变量声明 private
    • 32 确定你的 public 继承塑模出 is-a 关系

01 视 C++ 为一个语言联邦

C++ 是一个同时支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式的语言。

  • C

  • Object-Oriented C++:C with classes

  • Template C++

  • STL

02 尽量以 const、enum、inline 替换 #define

#define 只是单纯的文本替换,可能会因为操作符优先级等因素出错。

(1)对于常量,用 const 代替 #define,例如

const int MAX = 20; 

而不是

#define MAX 20

(2)对于形似参数的宏,最好用 inline 函数代替 #define,如

#define CALL_WITH_MAX(a, b) f((a)>(b)) ? (a) : (b)

一定要加括号,否则可能因运算符优先级而出错(实际上加了括号也可能会出错)。所以改用更为方便的内联函数

template <typename T>
inline void callWithMax(const T& a, const T& b)
{
    f(a > b ? a : b);
}

关于内联函数:内联函数在调用时,是将调用表达式用内联函数体来替换。且只适用于 1~5 行的小程序。

03 尽可能使用 const

const 在星号左边,则被指物是常量;在星号右边,则指针本身是常量;出现在星号两边,则被指物和指针都是常量。

const int * ptr   // ptr 指向的是常量
int const * ptr   // 和上面等价
int* const ptr    // ptr 本身是常量,也就是他的指向不可改变
const int* const ptr   // 都是常量,不可改变

04 确定对象被使用前已被初始化

构造函数最好使用成员初始化列表,而不要在构造函数本体内使用赋值操作。初始化列表列出的成员变量,其排列次序应该和它们在 class 中的声明次序相同。

建议

ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list& phones)
: theName(neme),      // 列表初始化
  theAddress(address),
  thePhones(phones),
  numTimesConsulted(0)
{ }

而不是

ABEntry::ABEntry(const std::string& name, const std::string& address, const std::list& phones)
{
    theName = neme;      // 这是赋值而不是初始化
    theAddress = address;
    thePhones = phones;
    numTimesConsulted = 0;
}

05 了解 C++ 默默编写并调用哪些函数

如果自己没有定义,编译器就会自动为类创建默认构造函数、复制构造函数、copy assignment 操作符以及析构函数;自己定义之后,编译器就不再提供该函数了。

注意,是当被调用时,他们才会被创建出来。

例如,创建一个 Empty 类,

class Empty {};    

编译器会提供默认函数,相当于

class Empty 
{
public:
    Empty() {...}        // 默认构造函数
                         // 析构函数
    Empty(const Empty& rhs) {...}      // 拷贝构造函数
    ~Empty() {...}        // 析构函数
    Empty& operator=(const Empty& rhs) {...}   // copy assignment 操作符
}; 

当调用时,就会被创建出来

Empty e1;      // 默认构造函数
Empty e2(e1);  // 拷贝构造函数
e2 = e1;       // copy assignment 操作符

还有一点需要注意的是,当我们自己定义了一个有参构造函数时,例如

Empty::Empty(int a) {...}        // 有参构造函数

编译器就不再提供默认构造函数,这时语句

Empty e1;

就会报错。所以当类中有自定义构造函数时,还需要手动添加一个默认构造函数,以防初始化对象失败。

06 若不想使用编译器自动生成的函数,就应该明确拒绝

有时我们希望由类初始化的对象是唯一的,也就是禁止拷贝构造和 copy assignment 操作符,那么就可以将拷贝构造函数和 copy assignment 声明为 private,这样在外部就无法调用了。

例如,初始化一个 HomeForSale 对象,我们希望他是独一无二的,即

HomeForSale h1;
HomeForSale h2;    // 允许初始化
HomeForSale h3(h2);   // 不允许拷贝构造
h2 = h1;              // 不允许 copy assignment

这时,可如下声明

class HomeForSale
{
public:
  ...
private:
    HomeForSale(const HomeForSale&);
    HomeForSale& operator=(const HomeForSale&);
      // 声明为私有
};

还有一种情况,我们不希望直接创建基类 A 的对象,而是仅允许创建其子类 B 的对象,这时,可将类 A 的构造函数和析构函数声明为 private 或 protected,例如

class A     // 基类
{ 
protected: 
    A(){}    // 默认构造函数
	~A(){}   // 析构函数
public: 
    ...
};

calss B : public A    // 子类
{ 
public: 
    B(){}     // 构造函数
};

A a; // error,外部无法调用构造函数
B b; // ok

07 为多态基类声明 virtual 析构函数

关于虚函数和多态:

https://www.runoob.com/w3cnote/cpp-virtual-functions.html

http://c.biancheng.net/view/264.html

如果一个类拥有至少一个虚函数,那么这个类也应该要有一个虚析构函数。例如有一个基类 A 和派生类 B,定义一个 A 类型的指针,指向 B 对象

A* ptr = new(B);
delete ptr;           

如果 A 中的析构函数是非虚函数,当执行 delete ptr 时,销毁的仅是 B 中继承自 A 的部分,也就是说,B 对象派生部分未被销毁而发生内存泄露。当基类 A 中析构函数为虚函数时,delete ptr 调用 B 自身的析构函数,销毁的就是 B 对象。

反过来,如果一个类不想作为基类,那么不要为其声明任何成员函数做 virtual 声明

08 别让异常逃离析构函数

https://www.cnblogs.com/pandamohist/p/13889691.html

  • 析构函数不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕获任何异常,然后吞下他们(不传播)或结束程序。

09 绝不在构造和析构过程中调用 virtual 函数

  • 在构造函数和析构函数中不要调用 virtual 函数。

https://zhuanlan.zhihu.com/p/143167853

10 令 operator= 返回一个 reference to *this

class Widget {
public:
    Widget& operator=(const Widget& rhs) {
        ...
        return *this;
    }
 
    Widget& operator+=(const Widget& rhs) {
        ...
        return *this;
    }
}

再类中重载运算符时,建议返回 reference to *this

12 复制对象时勿忘其每一个成分

自己写拷贝构造函数和 copy assignment 操作符时,一定要确保复制“对象内的所有成员变量”。

20 宁以 pass-by-reference-to-const 替换 pass-by-value

  • 尽量以 pass-by-reference-to-const 替换 pass-by-value,省去了创建副本的时间,没有任何构造函数和析构函数被调用,更加高效。

  • 对于内置类型以及 STL 的迭代器和函数对象,使用 pass-by-value 效率更高。

  • 也就是说,如果是自己创建的比较复杂的数据类型,尽量用pass-by-reference-to-const;而自带的类型,建议pass-by-value。

22 将成员变量声明 private

将成员变量声明 private,在 public 中定义获取私有变量值的函数,从而避免外界随意修改私有变量。

32 确定你的 public 继承塑模出 is-a 关系

public 继承意味着 is-a ,也就说,适用于父类身上的每一件事情也适用于子类,二者是包含与被包含的关系。

你可能感兴趣的:(C++,c++)