条款1 视C++为一个语言联邦
今天的C++已经是个多重范型编程语言,一个支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式的语言。为了理解C++,必须认识其主要的次语言,总共4个:
1.C
2.Object-Oriented C++
3.Template C++
4.STL
条款2 尽量以const, enum, inline替换#define(即宁可以编译器替换预处理器)
1.
#define ASPECT_RATIO 1.653
ASPECT_RATIO没有进入编译器的symbol table,错误信息会提到1.653而不是ASPECT_RATIO,这样查找这个符号就会花费时间,所以应该这样:
const double AspectRatio = 1.653
有两种特殊情况:
一。第一常量指针。应该写为:const char* const authorName = "Scott Meyers";
二。class专属常量。为了确保次常量最多只有一份实体,必须让它成为一个static成员。通常C++要求你对所使用的任何东西提供一个定义式,但如果它是class专属常量又是static且为整数类型(int, char, bool),则需要特殊处理:只要不去它们的地址,可以声明并使用它们而无需提供定义式。
class GamePlayer{
private:
static const int Num = 5;//常量声明式
int scores[Num];
}
class TestClass{ private: static const double d = 1.2;//这里居然可以初始化double类型的,但是在公司电脑的eclipse上就不行,奇怪,同是cdt,版本也相同 static const int i = 22; }; //const double TestClass::d = 1.2; //此时不能加static,static只用于声明,此处是定义;同理,不能在定义静态函数时加上static;但是改法则不适用于全局static 函数 如果编译器不支持static整型常量在class内的初值设定,可改用所谓的“the enum hack”补偿做法。
2.#define常用来实现宏,看起来像函数,但是不会导致函数调用的开销class GamePlayer{ private: enum {NumTurns = 5}; int scores[NumTruns]; };
#define CALL_WITH_MAX(a,b) f((a)>(b)? (a):(b)) //以a和b中的最大值调用函数f
int a = 5, b = 0;
CALL_WITH_MAX(++a, b); //a被累加两次 CALL_WITH_MAX(++a, b+10); //a被累加一次 可以使用template inline来替代上述表示://template可以实现和参数类型无关,inline可以消除函数调用的开销
template<typename T> inline void CallWithMax(const T&a, const T& b) { f(a>b? a: b) }
有了const, enum 和inline,只能降低对预处理器的需求,但不能完全消除,#include是必需品,而#ifdef/#ifndef在控制编译中扮演重要角色
条款3 尽可能使用const
1. 注意: 如果被指物为常量,有两种写法: const int* cpi; int const* cpi;注意不是int *const cpi; 2. const成员函数 将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象。该类函数比较重要,因为:一,得知哪个函数可以改动对象内容哪个不行很重要;二,使操作const对象成为可能(条款20说,改善C++程序效率的一个根本方法是以pass by reference-to-const方式传递对象,而此技术可行的前提是,我们有const成员函数可用来处理取得的const对象)许多人漠视一件事实:两个成员函数如果只是常量性不同,可以被重载,这是一个重要的C++特性:
class TestBlock{ public: const char& operator[](size_t position) const{ return text[position]; } char& operator[](size_t position){ return text[position]; } private: string text; };
void print(const TextBlock* ctb){ std::cout << ctb[0];//此处调用的是const operator[]版本的:不太明白,函数的参数个数和类型完全相同,编译器是如何判断该调用哪个版本的operator[]的呢? }
void print(){ TextBlock tb; tb[0] = 'x';//注意:如果non-const operator[]返回类型不是reference-to-char,而不是char,那么这里将会出错,因为如果函数返回值是内置类型,那么改动函数返回值从来都是不合法的。貌似有点说不通,为什么不能修改函数反悔值,却能修改函数返回的reference?? } const成员函数的两个阵营: 一是bitwise(又称为physical constness),认为成员函数不能更改对象内的任何一个bit,不幸的是许多成员函数虽然不十足具备const性质却能通过bitwise测试,也就是说,一个更改了指针所指物的成员函数虽然不能算是const,但如果只有指针(而非其所指物)隶属于对象,那么称此函数为bitwise const不会引发编译器异议。
class CTextBlock{ public: char& operator[] (std::size_t position) const{ return pText[position]; } private: char* pText; } const CTextBlock cctb("hello"); char* pc = &cctb[0]; *pc='j' //虽然调用了const函数,没有改变内部成员变量,但返回的指针最终还是导致改变了成员的值 二是logical constness,主张一个const成员函数可以修改它所处理对象内的某些bits:
class CTextBlock{ public: std::size_t length() const; private: char* pText; mutable std::size_t testLength; mutable bool lengthIsValid; }; std::size_t CTextBlock::length() const{ if(!lengthIsValid){ testLength = std::strlen(pText);//可以修改成员变量 lengthIsValid = true;//可以修改成员变量 } }; 2.在const和non-const成员函数中避免重复 上述的实现中两个相互重载的函数造成了代码的重复,为了实现代码复用,可以使用non-const版本调用const版本:class TextBlock{ public: const char& operator[] (std::size_t position) const { //... //.... //.... return text[position]; } char& operator[] (std::size_t position){ return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);//为*this加上const,调用const op[]:这里不是很理解,为什么一定需要把*this转为const TextBlock&???? } }; 上述代码有两次类型转换,第一次用来为*this添加const(这使接下来条用operator[]时得以调用const版本);第二次则是从const operator[]返回值中移除const;更值得了解的是, 反向做法------令const版本调用non-const版本以避免重复是不应该的。const成员函数承诺绝不改变其对象的逻辑状态,non-const成员函数却没有这般承诺。如果在const函数 调用non-const函数:你曾经承诺不改动的那个对象被改动了。实际上若要令这样的代码通过编译,你必须使用一个const_cast将*this身上的const性质解放掉(????)