1.尽量以const,enum,inline替换#define
当你写出这样的代码:
#define ASPECT_RATIO 1.62
记号名称ASPECT_RATIO也许从未被编译器看见;也许在编译器开始处理源码之前它就被预处理器移走了。于是记号名称ASPECT_RATIO有可能没进入记号表内,于是当你运用此常量获得一个编译错误信息时可能会带来困惑,因为这个错误信息也许会提到1.62而不是ASPECT_RATIO。如果ASPECT_RATIO被定义在一个非你所写的头文件内,你肯定对1.62以及它来自何处毫无概念。解决之道是以一个常量替换上面的宏(#define):
const double AspectRatio = 1.62;
作为一个语言常量,AspectRatio 肯定会被编译器看到,当然就会进入记号表内。
当我们以常量替换#define,有两种特殊情况值得说说。第一是定义常量指针,由于常量定义式通常被放在头文件内(以便被不同的源码含入),因此有必要将指针(而不是指针所指之物)声明为const。例如若要在头文件内定义一个常量的char*型指针,你必须写const两次:
const char* const authorName = "Scott Meyers";
string对象通常比其前辈char*合宜,所以上述authorName 往往定义成这样更好些:
const std::string authorName = "Scott Meyers";
第二个值得注意的是class专属常量,为了将常量的作用域限制于class内,你必须让它成为class的一个成员;而为确保此常量至多只有一份实体,你必须让它成为一个static成员:
class A{
private:
static const int num = 5;//常量声明
int scores[num];//使用该常量
};
然而你所看到的是num的声明式而非定义式,通常c++要求你对你所使用的任何东西提供一个定义式或你的编译期坚持要看到一个定义式,你就必须另外提供定义式如下:
const int A::num;
该定义写在一个实现文件内而非头文件,由于class常量已在声明时获得初值5,因此定义时不可再设初值。
旧式编译器也许不支持上述语法,它们不允许static成员在其声明式上获得初值。因此你可以将初值放在定义式:
//.h
class A{
private:
static const int num;//常量声明位于头文件
};
//.cpp
const int A::num = 5;//常量定义位于实现文件内
唯一例外的是当你在class编译期间需要一个常量值,例如在上述的A::scores的数组声明中(编译器必须在编译期间知道数组的大小)。这时候万一你的编译器不允许static整数型class常量完成声明时初值设定,可改用枚举补偿做法,其理论基础是:“一个属于枚举类型的数值可权充int被使用”,于是class A可定义如下:
class A{
private:
enum {num = 5};
int scores[num];
我们再回到预处理器,另一个常见的#define误用情况是以它实现宏。宏看起来像函数,但不会招致函数调用带来的额外开销,下面这个宏夹带着宏实参,调用函数f:
//以a和b的较大值调用f
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))
这般写法有着太多缺点,光是想到她们就让人痛苦不堪。
无论何时当你写出这种宏,你必须记住为宏中的所有实参加上小括号,否则某些人再表达式中调用这个宏可能会遇到麻烦。但纵使你为所有实参加上了小括号,看看下面不可思议的事情:
int a=5,b-0;
CALL_WITH_MAX(++a,b); //a被累加二次
CALL_WITH_MAX(++a,b+10); //a被累加一次
在这里,调用f之前,a的递增次数竟然取决于它被拿来和谁比较!
我们可以用模板来实现该方法:
template<typename T>
inline void callWithMax(const T& a,const T& b)
{
f(a > b ? a : b);
}
2.尽量使用const
char str[]="hello";
const char* p = str;// 声明一个指向字符或字符串常量的指针(p所指向的内容不可修改)
char* const p = str;//声明一个指向字符或字符串的指针常量,也就是p指向的地址无法修改。
const char* const p = str;// 声明一个指向字符或字符串常量的指针常量
如果const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。
const最具威力的用法是面对函数声明时的应用,令函数返回一个常量值,往往可以减低因错误而造成的意外,而又不至于放弃安全性和高效性。例如:
class Rational{...};
const Rational operator* (const Rational& lhs,const Rational& rhs);
为什么返回一个const对象?原因是如果不这样程序员就可能实现这些的代码:
Rational a,b,c;
...
if(a*b = c)
...
如果a和b都是内置类型,这样的代码直截了当就是不合法。而一个“良好的用户自定义类型”的特征是它们避免无端地与内置类型不兼容,因此允许对两值乘积做赋值动作也就没什么意思了,将operator*的回传值声明为const可以预防那个“没意思的赋值动作”,这就是该那么做的原因。
class TextBlock{
public:
...
const char& operator[](std::size_t position) const//operator[] for const对象
{return text[position];}
char& operator[](std::size_t position)//operator[] for non-const对象
{return text[position];}
private:
std::string text;
};
TextBlock的operator[]可以被这么使用:
TextBlock tb("hello");
std::cout<<tb[0];//调用non-const TextBlock::operator[]
tb[0] = 'x';//没问题
const TextBlock ctb("world");
std::cout<<ctb[0];//调用const TextBlock::operator[]
ctb[0] = 'x';//错误! 尝试写一个const TextBlock
注意,上述错误只因operator[]的返回类型导致,至于operator[]调用动作自身没问题。错误起因于企图对一个“const版之operator[]返回”的const char&施行赋值动作。如果operator[]只返回一个char,下面这样的句子就无法通过编译:
tb[0] = 'x';
那是因为被改动的其实是tb.text[0]的一个副本,不是其本身。