《Effective C++》读书笔记(一)
此文用于记录学习时的重点和 comments.
Accounting yourself to C++
条款 1:视 C++ 为一个语言联邦
C++ 统共由四部分组成:
- C
- Object
- STL
- template
据我目前所知,STL 属于其中唯一一个通过封装形成的,官方的 STL 虽然够泛用了,但在特定场景可能有问题,因此也出现过一些优化版本,例如 EASTL.
条款 2:使用 const, enum, inline 替换 #define
个人经常 #define 来处理变量, 看过很多别人的代码也是这样做的,用的多了,就会发现 define 的内容括号打好很重要,因为毕竟只是简单的文本替换,很容易出现优先级导致的语义错误,例如:
1 |
|
这样是会报错的.
当然该条目好像讨论的是全局变量赋值.
define 和 const, enum 等的区别
让我们先来复习一下 CSAPP 中 C 语言生成程序的过程:
- 预处理器
- 编译器
- 汇编器
- 链接器
#define
显然是在预处理阶段完成的,这一阶段所做的全部都是文本编辑,其中#define
的所有宏定义被全部转换.
它的问题有啥呢?
在程序链接过程中,
#define
的内容早就被替换了,其所指代的符号自然不可能被添加到符号表中,因此如果这部分的内容出错了,是不会有提示的,单步调试观察变量也无法直接发现该处的问题.define 也可能导致内存滥用. 如果你使用
#define
定义常量,那么多个位置出现的该量虽然值相同,但在内存中可能使用了多份(它们没有名字,编译器没法负责任地把它们联系起来. 但使用const
就不可能出现这个问题.
条款 3:尽可能使用 const
为什么
让编译器替你找错误。
(除此以外,全局常量还可以节约一点空间. 它们可能不会被分配内存——有地址相关操作时,会分配只读内存给变量)
但是,const 有很多注意事项
注意一下 const 指针:
1 | const char* p = greeting; // non-const pointer, const data |
那么,int const
和 const int
有什么区别呢?
没区别
迭代器的本质
1 | const std::vector<int>::iterator iter = vec.begin(); // 迭代器本质上是一个指针,而 const iterator<T> 相当于 T* const. |
1 | // 如果要指向一个 const 内容,则使用 const_iterator |
常被忽略的: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 | // #1 |
通常管第二种叫做列表初始化。
其实这不对!
第一种并不能叫做初始化,而是对未初始化的值进行赋值,第二个才算初始化。
Constructors, Destructors, and Assignment Operators
条款 5:了解 C++ 默默编写并调用哪些函数
如果你的 class
没有写/重载以下内容,编译器会隐式地自己编写:
default 构造函数(只要写了任何形式的构造函数,该函数就不会被创建)
copy 构造函数
- 析构函数
- 赋值操作符
=
但,编译器不总是帮你写这些,只有在你没有写,且程序中用到它们时,才会创建。
除此之外,以下两种情况也不会创建 assignment 操作符重载:
class 的成员变量不可以修改。例如 reference 和 const.
父类将
operator =
设置为private
,则子类不会创建。
条款 6:若不想使用编译器自动生成的函数,就该明确拒绝
编译器可能会自动生成条款 5 中的各种函数,要想拒绝自动生成,只需要将他们声明为 private
.
这样还会有一个问题:成员函数和 friend
友元函数还是可以访问它们。那么,你只需要不定义,而是只给声明即可。这样链接器找不到定义,就会得到一个 linkage error.
如果你想让这个 linkage error 提前至编译错误,你可以专门写一个 base class,来将不想被使用的函数放入,再让工作的 class 继承它即可。