此文用于记录学习时的重点和 comments.

Accounting yourself to C++

条款 1:视 C++ 为一个语言联邦

C++ 统共由四部分组成:

  • C
  • Object
  • STL
  • template

据我目前所知,STL 属于其中唯一一个通过封装形成的,官方的 STL 虽然够泛用了,但在特定场景可能有问题,因此也出现过一些优化版本,例如 EASTL.

条款 2:使用 const, enum, inline 替换 #define

个人经常 #define 来处理变量, 看过很多别人的代码也是这样做的,用的多了,就会发现 define 的内容括号打好很重要,因为毕竟只是简单的文本替换,很容易出现优先级导致的语义错误,例如:

1
2
3
4
#define lson k << 1
...
int k;
cout << lson << endl;

这样是会报错的.

当然该条目好像讨论的是全局变量赋值.

define 和 const, enum 等的区别

让我们先来复习一下 CSAPP 中 C 语言生成程序的过程:

  • 预处理器
  • 编译器
  • 汇编器
  • 链接器

#define 显然是在预处理阶段完成的,这一阶段所做的全部都是文本编辑,其中#define的所有宏定义被全部转换.

它的问题有啥呢?

  1. 在程序链接过程中,#define 的内容早就被替换了,其所指代的符号自然不可能被添加到符号表中,因此如果这部分的内容出错了,是不会有提示的,单步调试观察变量也无法直接发现该处的问题.

  2. define 也可能导致内存滥用. 如果你使用 #define 定义常量,那么多个位置出现的该量虽然值相同,但在内存中可能使用了多份(它们没有名字,编译器没法负责任地把它们联系起来. 但使用 const 就不可能出现这个问题.

条款 3:尽可能使用 const

为什么

让编译器替你找错误。

(除此以外,全局常量还可以节约一点空间. 它们可能不会被分配内存——有地址相关操作时,会分配只读内存给变量)

但是,const 有很多注意事项


注意一下 const 指针:

1
2
const char* p = greeting;	// non-const pointer, const data
char* const p = greeting; // const pointer, non-const data

那么,int constconst int 有什么区别呢?

没区别


迭代器的本质

1
const std::vector<int>::iterator iter = vec.begin(); //	迭代器本质上是一个指针,而 const iterator<T> 相当于 T* const.
1
2
// 如果要指向一个 const 内容,则使用 const_iterator
std::vector<int>const_iterator cIter = vec.begin();

常被忽略的:const 修饰原函数返回值的函数,是可以重载的. 这不是只有返回值不同,而是对象不同,一个是普通 class,一个是 const class.


关于 func() const,该 const 修饰该函数. 如果某个类的成员函数只有 func() 而没有 func() const,那么对于一个 const class 对象,调用 func() 就会出错.

这是因为函数的实际参数是有一个隐藏的 this 指针的,func() 中的 this 没有 const 修饰符,所以参数列表不匹配,就会报错. 而 func() const 传入的是 const this 指针,自然就可用了。

所以,func() const 中的 const 相当于修饰 this 指针。


关于 logical constness

使用 mutable 关键字,可以使得 const 类中的指定 non-static 变量可修改.

条款 4:确定对象被使用前已经被初始化

内置类型需要自己留意,写手工初始化。

class 就得写构造函数了。但是很多书教初始化有两种写法:

1
2
3
4
5
// #1
A::A(type p) { x = p; }

// #2
A::A(type p) : x(p) { }

通常管第二种叫做列表初始化。

其实这不对

第一种并不能叫做初始化,而是对未初始化的值进行赋值,第二个才算初始化。

Constructors, Destructors, and Assignment Operators

条款 5:了解 C++ 默默编写并调用哪些函数

如果你的 class 没有写/重载以下内容,编译器会隐式地自己编写:

  • default 构造函数(只要写了任何形式的构造函数,该函数就不会被创建)

  • copy 构造函数

  • 析构函数
  • 赋值操作符 =

但,编译器不总是帮你写这些,只有在你没有写,且程序中用到它们时,才会创建。

除此之外,以下两种情况也不会创建 assignment 操作符重载:

  1. class 的成员变量不可以修改。例如 reference 和 const.

  2. 父类将 operator = 设置为 private,则子类不会创建。

条款 6:若不想使用编译器自动生成的函数,就该明确拒绝

编译器可能会自动生成条款 5 中的各种函数,要想拒绝自动生成,只需要将他们声明为 private.

这样还会有一个问题:成员函数和 friend 友元函数还是可以访问它们。那么,你只需要不定义,而是只给声明即可。这样链接器找不到定义,就会得到一个 linkage error.

如果你想让这个 linkage error 提前至编译错误,你可以专门写一个 base class,来将不想被使用的函数放入,再让工作的 class 继承它即可。