目录
用const,enum,inline去替换#define
尽可能去使用const
确保对象使用前已被初始化
这是effective C++中的第一大章节:让自己习惯c++
用const,enum,inline去替换#define
当用使用这样的代码:
#define ASPECT RATIO 1.653
记号名称ASPECT RATIO可能未被编译器看见;也许在编译器开始处理源码之前它就被预处理器移走了。于是记号名称ASPECT RATIO有可能没进入记号表(symbol table)内。若使用了这个常量但获得一个编译错误信息时,你将很难追踪错误所在。这个错误信息也许会提到1.653而不是ASPECT RATIO。
1.ASPECT RATIO被定义在一个非你所写的头文件内,你肯定对1.653以及它来自何
处毫无概念2.你所使用的名称可能并未进入记号表,在记号式调试器也造成很大困扰。
最好使用一个常量来替代上面的宏。
const double Puioa=1.653
这里在补充两点:
1.定义常量指针
常量定义式通常被放在头文件内(以便被不同的源码含
入),因此有必要将指针(而不只是指针所指之物)声明为const.例如若要在头
文件内定义一个常量的(不变的)char*-based字符串,你必须写const两次:
const char*const authorName ="Scott Meyers";
2.class内的专属常量
为了将常量的作用域(scope)限制于class内,你必须让它成为class的一个成员(member);而为确保此常量至多只有一份实体,你必须让它成为-一个static成员。
例如:
class GamePlayer
private:
static const int NumTurns =5;//常量声明式
int scores [NumTurns]; //使用该常量
};
上面的只是声明,不是定义,只要不取它的地址,可以直接声明不定义的去使用它。但若要取地址或者编译器要看到它的定义式的话,就要在外面去定义它。
const int GamePlayer::NuTurns;
//上面声明时以获得初值,定义时可不设初值
补充:
1.class的专属常量不能用#define去弄,宏在编译后,后面都有效,除非(#undef)。
2.若编译器不支类内static const 成员在声明时获得初值,可在类外定义时附上初值。
3.若编译器错误的不允许static整型class常量完成类内初值设定,可用“the enum hack"补偿做法。
例如:
class GamePlayer { private: //static const int NumTurns=5;//常量声明式,若这边语句编译器出错 int scores[NumTurns]; //使用该常量 };
解决办法:
class GamePlayer { private: //static const int NumTurns=5;//常量声明式 enum count { NumTurns = 5 }; int scores[NumTurns]; //使用该常量 };
理由:枚举与#define更像,例如取const 修饰的变量地址是合法的,但取一个enum地址是不允许,取一个#define的地址也不允许。若不想别人用一个指针或者引用去关联它,enum可以做到。enum和#define一样,绝对不会导致非必要的内存分配。
当使用类似于函数的宏时,例如:
发现第一次A99变到了101,第二次从101变到了102,即使为所有参数加上括号也是一样的。
用template inline 函数
template<typename T>
inline void call(const T& a, const T& b)
{
f(a > b ? a : b);
}
这里不需要在函数本体中为参数加上括号,也不需要操心参数被核算(求值)多次……等等。此外由于call是个真正的函数,它遵守作用域(scope)和访问规则。
尽可能去使用const
const修饰指针:
const出现在*左侧,说明被指物是常量,const出现在*右侧,说明指针是常量。若出现在两侧,表明两 者都是常量。
const成员函数:
将成员函数用const去修饰,是为了确认该成员函数可作用于const对象上。1.可以使class接口更容易理解,可以知道哪个函数可以改变对象内容而哪个函数不可以。2.使操作 const对象 成为可能。
看一段代码:
cosnt对象调用的是const版本。注意,若operator[] 返回的是char,不是char&,那么tb[0]='a',这样就无法编译通过,因为返回的是一个内置类型,改变它就不合法。即使可以改变,你也没有改变text的内容。
const的两个流派概念:bitwise constness(physical constness)与logical constness。
bitwise const认为成员函数只有在不更改对象之任何成员变量( static除外)时才可以说是const。不幸的是许多成员函数虽然不具备const性质却能通过bitwise测试。
例如:
再看如下的操作:
其中不含错误,可以编译通过,但创建一个常量对象并设以某值,而且只对它调用const成员函数,终究还是改变了它的值。这种情况就有了logical constness阵营,一个const成员函数可以修改它所处理的对象内的某些bits,但只有在客户端侦测不出的情况下才得如此。
再看下面一个例子:
这个length函数内的this指针是被const修饰的,但他的成员在这函数内是要被修改,这种情况肯定不属于bitwise constness,但现在却无法编译通过。
解决办法:利用mutable释放掉non-static成员变量的bitwise constness约束。
例如:
在const与non-const成员中避免重复
上面的代码用mutable去解决问题,但它不能解决所有问题。假设TextBlock(和CTextBlock)内的operator[]不单只是返回一个reference指向某字符,还要执行其它功能。把所有这些同时放进const和non-const operator[]中,可能会成为一个长度很长的隐喻式inline函数。
例如:
里面可能包含大量的代码重复,以及伴随的编译时间、维护、代码膨胀等问题。应该实operator[]的机能-一次并使用它两次。也就是说,你必须令其中一个调用另一个。这促使我们将常量性转除(casting away constness)。合理的做法是用non-constness函数调用const成员函数,其中需要转型动作。
例如:
对第二个operator[]解释:
首先是要明白是用non-constness函数调用constness函数,首先这要把this指针用const去修饰,那么它就会去调用constness版本函数,当constness函数的返回值又是const char&,所以再把返回值得数据类型改为char&。
确保对象使用前已被初始化
这里关于类成员通过构造函数赋值,在初始化列表进行初始化不再细谈。只简单介绍几点:
C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前,发生于这些成员的default构造函数被自动调用之前。但对于内置类型不保证。
最好在初值列表中列出所有成员变量,以免遗漏。
成员变量是const或者reference,不能被赋值,只能通过初始化列表。
当class有许多构造函数,每个构造函数有自己的成员初值列。可能会导致许多重复的代码操作,这种情况下可以合理地在初值列中遗漏那些“赋值表现像初始化一样好”的成员变量,改用它们的赋值操作,并将那些赋值操作移往某个函数(通常是private),供所有构造函数调用。
class的成员变量是按声明顺序初始化。
但要重点关注non-local static对象”的初始化次序。
看下面一段代码:
除非tfs在tempDir之前先被初始化,否则tempDir的构造函数会用到尚未初始化的tfs。,C++对“定义于不同的编译单元内的non-local static对象”的初始化相对次序并无明确定义。
解决办法:
C++保证,函数内的local static对象会在“该函数被调用期间”“首次遇上该对象之定义式”时被初始化。如果你以“函数调用”(返回一个reference指向local static对象)替换“直接访问non-local static对象”,你就能保证你所获得的那个reference将指向一个历经初始化的对象。