写在前面:之前一直有在写博客,但是总是没坚持下来,感觉博客作为记录自己学习过程的工具还是十分有意义的,故而决定在这儿开始我博客之路的第一步,以C++的学习为始,从得到大量好评的Effective C++ 开始入手,记录自己学习的路程和对一些问题的思考。
effective C++给出了55个改善C++程序的具体建议,并且提出了一些在C++程序设计中经常出现的坑。
建议 01 : 视C++为一个语言集
View C++ as a federation of languages
早期的C++只是简单的在C上做了面向对象的拓展,发展到今天,C++已经是一个多重泛型编程语言(multiparadigm programming),它支持:
- 过程形式(procedural)
- 面向对象形式(object-oriented)
- 函数形式(functional)
- 泛型形式(generic)
- 元编程形式(metaprogramming)
它由4个子集组成,在书中将其称为”次语言”(sublanguage), 我觉得这个描述不太好,特别是对于其中的泛型编程和STL来说,称为语言总是有些牵强, 我更愿意将它理解为子集的概念, 这4个子集分别为:
- C,C是C++的基础,所以C++是完全兼容C语言的,当然,有部分的关键字和结构还是有一些区别的,如static关键字C++有了更为复杂的含义。如struct,c++拓展了结构体类型的面向对象特性,等等还有一些其他的,但是主要是拓展性的区别。简而言之,C++就是提供了更为高可用的高效编程,如模板(templates)、异常(exception)、重载(overload)等等,这些在c里面都是没有的。
- Object-Oriented,这部分就是C++的语言特性相关,包括面向对象的内容: classes(构造函数、析构函数)、封装(encapsulation)、继承(inheritance)、多态(polymorphism)、virtual函数(dynamic binding)、等等…这些就是OO的在C++上的具体实现。
- Template C++。C++的泛型编程(generic programming)部分,就是C++的函数模板(function template)、类模板(classes template),它们使得泛型编程成为可能
- STL。STL是C++的标准模板库(Standard Template Library),它实现了一系列通用的容器(containers)、迭代器(itrators)、算法(algorithms)、以及函数对象(function objects),并且以巧妙并且不失高效的设计模式将它们连贯在一起,程序员可以通过使用STL来更为快速的开发高效的程序。
所以不能简单的讲C++认为是一个语言,而是时刻注意它4个部分不同的规则,并且利用它们来完成更好的设计。
建议 02 :尽量用const, enum, inline来替换 #define
Prefer consts, enums, and inlines to #defines
这条建议是所有像我一样习惯C开发转向C++开发的程序员,在C程序中,为了让程序更简单修改,减少简易函数的栈开销,常常会使用#define来声明宏变量或者宏函数,但是在C++中,这是不被提倡的,因为#define定义的宏在预编译阶段展开,不会将它描述的变量生成在符号表(symbol table)中,同时,如果使用宏来描述一个浮点型常量,预编译处理器可能会盲目替换代码中对应的浮点数宏变量,虽然这样就不需要为其分配内存,但是事实上代码存储也是需要内存的,反而得不偿失。
另外, 需要注意的是在类中对const参数的初始化,一般来说,常量值使用声明式在头文件中完成声明。
可以在声明时初始化:
class GamePlayer {
private:
static const int NumTurms = 5; //为了使得常量只有一份实体
...
}
或者在类中声明,类外初始化
struct GamePlayer {
private:
static const int Numturms; //声明式 头文件(.h)声明
...
}
const int GamePlayer::Numturms = 5; //实现文件初始化(cpp/cc)
一个比较有趣的操作是emum补偿,编译器必须要在编译期间获得数组的大小,有的编译器不允许“static int class” 常量” 完成 “int class 初始化操作” ,就是不能使用class内的static int 常量来作为数组大小,此时就可以曲线救国,使用所谓的“the enum hack”补偿做法,其理论基础是,一个属于枚举类型(enumerated type)的数值可被当做int使用,所以可以这样声明数组:
class GamePlayer {
private:
enum { Numturms = 5 }; //"the enum hack" 讲NUmturms 作为5的记号
int scores[Numturms]; //声明数组
}
enum hack有多种用途,认识它是十分有必要的。enum hack的行为比较类似与#define 而不是const,即它在内存中没有实体,只是作替换功能,所以不能对它取地址,这样也从某种角度实现了一种约束,指针无法指向它,根据编译器的不同,有的编译器会对const对象设定另外的存储空间(没有指针和引用指向的情况下),有的编译器则会,但是使用enum hack却可以保证不会有这部分的额外开销。
enum hack还是template metaprogramning(模板元编程)的基础技术。
在什么情况下替换
对于单纯的常量,最好使用const 对象或者enum来替换#define
对于类似与函数的宏(macros),最好使用inline函数替换#define,虽然这样编译器会选择是否内联,但是这也是我们想要的。