条款01:视C++为一个语言联邦
最初的C++只是C加上一些面型对象特性,就像C++最初的名字“C with Classes”。但是现在C++已经是一个多重泛型编程语言。为了理解C++我们必须认识其主要的次语言
1、C
C++仍然是以C为基础,区块、语句、预处理器、内置数据类型、数组、指针等通通来自于C
2、Object-Oriented C++
这部分也就是C with Classes所追求的:classes,封装、继承、多态、虚函数……等等
3、Template C++
这是C++的泛型编程部分
4、STL
这是一个template程序库
条款02:尽量以const,enum,inline替换#define
#define是一个预处理宏定义命令,例:
#define TEST 10 // 大写名称通常用于宏定义
程序在预处理阶段就会将代码中所有的TEST替换成10,当我们运行此常量但获得一个编译错误信息时,可能会带来困惑,因为这个错误信息有可能提到10,而不是TEST。如果这个TEST还不在你写的头文件中,那么你肯定不知道这个10以及它从何而来抱有困惑,所以只能去调试追踪,这将会浪费时间。
所以我们可以利用一个常量去替换上面定义的宏
const int test = 10;
作为一个普通的常量,test肯定会被编译器看到,而且会进入记号表中。
当我们以常量替换#define,有两种特殊情况:
- 定义常量指针
- class专属常量
因为常量定义式通常被放在头文件中,所以有必要将指针声明为const
class专属常量,为了将常量的作用域限制在class内部,必须让它成为一个class的成员,所以要声明为static的。而且我们无法利用#define创建一个class的专属常量,因为#define并不重视作用域,一旦被定义它就在之后的编译过程中有效。这意味着#define不能够用来定义class专属常量也不能够提供任何封装性
enum和#define一样绝对不会导致非必要的内存分配
对于形似函数的宏,最好改用inline函数替换#define
条款03:尽可能使用const
const,可以用来在类外修饰全局或命名空间中的常量,或修饰文件、函数、或区块作用域中被声明为static的对象,也可以修饰类内部的static和普通成员变量。对于指针,也可以指出指针自身、指针所指物或两者都是const的,如下:
char str[] = "hello";
char* p = str;
const char* p = str; //修饰指针所指的值
char* const p = str; //修饰指针本身
const char* const p = str; //即修饰指针所指物也修饰本身
其实这很好理解,如果const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量
(注意:const char* p = str 《====》char const *p = str)
const成员函数:
- 使class接口比较容易被理解,可以得知哪个函数可以改动对象内容而哪个函数不行
- 使 “操作const对象” 称为可能,const成员函数可用来处理取得的const对象
如果我们需要在const成员函数内部去修改某些成员变量,那么声明的时候需要将成员变量声明为mutable的
运用const成员函数实现出该函数的non-const版本
const char& operator[](std::size_t pos)const
{
...
...
...
return text[pos];
}
char& operator[](std::size_t pos)
{
//先为*this加上const,然后调用const op[],最后将op[]返回值的const移除
return const_cast<char&>(static_cast<const TextBlock&>(*this)[pos]);
}
不要再const函数中去调用non-const版本的函数,这是一种错误行为。
条款04:确定对象被使用前已先被初始化
永远在使用对象之前先将它初始化,对于无任何成员的内置类型,你必须手动完成这件事。对于内置类型之外的对象,初始化责任落在构造函数身上,所以确保每一个构造函数都将对象的每一个成员初始化。
分清赋值和初始化:在构造函数初始化列表的叫初始化,而在构造函数体内部的叫赋值。
在初始化列表初始化比在构造函数内部赋值效率要高。因为在构造函数体内进行赋值的话,首先会调用默认构造函数为非内置类型赋初值,然后立刻再对它们赋予新值,默认构造函数的一切作为就因此浪费了。
有一些类型的成员函数必须在初始化列表被初始化,而不能被赋初值:
- const类型
- 引用类型
- 类类型成员
类的成员变量总是以其声明次序被初始化,即使它们在成员初始化列表中以不同的次序出现(这是不合法的),也不会有任何影响。
C++对于定义于不同编译单元内的全局静态对象的初始化相对次序并没有明确定义。可以将每个全局静态对象搬到自己的专属函数内,这些函数返回一个引用指向它所含的对象,然后用户调用这些函数而不直接去调用对象。
为了避免在对象初始化之前过早地使用它们,需要做三件事:
- 手工初始化内置类型对象
- 构造函数最好使用初始化列表初始化所有成员,并且排列次序应该和它们的声明次序相同
- 对于全局静态成员,初始化次序不确定的情况下加强你的设计(以局部静态成员替换全局静态成员)