无法利用#define 创建一个class 专属常量,因为#defines并不重视作用域。一旦宏被定义,它就在其后的编译过程中有效(除非在某处被#undef)。这意味着#defines不仅不能够用来定义class专属常量,也不能够提供任何封装性,也就是说没有任何private #define 这样的东西。而当然const成员变量是可以被封装的
#define ASPECT_RATIO 1.653
以下面的方式替代上面:
const double AspectRatio =1.653;
class CostEstimate{
private:
static const double FudgeFactor;//static class 常量声明位于头文件内
...
};
const CostEstimate::FudgeFactor =1.35;//static class 常量定义位于实现文件内
假如你的编译器不允许“static 整数型class常量”完成“in class初值设定”,可以改用“the enum hack”补偿做法。其理论基础是:“一个属于枚举类型的数值可权充ints被使用”,
于是GamePlayer可被定义如下:
class GamePlayer{
private:
enum{NumTurns=5};//"the enum hack " 令NumTurns成为5的一个记号名称
int scores[NumTurns];
...
};
当利用#define定义宏函数时:
//以a和b的比较值调用 f
#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
替换成,inline函数
template<typename T>//由于我们不知道
inline void callWithMax(const T& a,const T& b ){//T是什么,所以采用pass by reference to const
f( a > b ? a : b );
}
有了const,enum和inline,我们对预处理器(特别是#define)的需求降低了,但并非完全消除。#include仍然是必需品,而#ifdef/#ifndef也继续扮演控制编译的重要角色。
1、对于单纯常量,最好以const对象或enum替换#define。
2、对于形似函数的宏,最好改用inline函数替换#define。
如果关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。
1、被指物是常量
const Widget *pw;//这是一个指针,指向一个常量的(不变的)Widget对象。
Widget const *pw;//这个也是
2、重载操作符时
class Rational{...};
const Rational operator *(const Rational &lhs,const Rational &rhs);
禁止如下的操作
Rational a,b,c;
(a*b) =c;//在a*b的结果调用operator ,禁止乘积的结果被赋值
if(a*b=c);//这里其实想做一个比较的动作 把 == 写错成 = 了。
3、const成员函数
假设你需要定义const成员函数,但是也想要修改一些数据成员,可能写成如下的代码
class CTextBlock{
public:
std::size_t length() const;
private:
char *pText;
std::size_t textLength;//最近一次计算的文本区块长度
bool lengthIsValid;//目前长度是否有效
};
std::size_t CTextBlock::length() const
{
if(!lengthIsValid)
{
textLength=std::strlen(pText);//错误!在const 成员函数内
lengthIsValid=true; //不能赋值给 textLength和lengthIsValid
}
return textLength;
}
如上代码是会产生编译错误。因此修改成以下:
class CTextBlock{
public:
std::size_t length() const;
private:
char *pText;
mutable std::size_t textLength;//这些成员变量可能总是会被更改,即使在const成员函数内
mutable bool lengthIsValid;//
};
std::size_t CTextBlock::length() const
{
if(!lengthIsValid)
{
textLength=std::strlen(pText);//现在可以这样
lengthIsValid=true; //也可以这样 //不能赋值给 textLength和lengthIsValid
}
return textLength;
}
bitwise constness实际上就是const成员函数不可以更改对象内任何non-static成员变量。但是mutable可以释放这种约束。
在const和non-const成员函数中避免重复
在non-const operator[ ]调用其const兄弟,实施转型操作,将*this从其原始类型TextBlock&转型为const TextBlock& ,即使转型操作为其加上const,所以这里共有两次转型操作。第一次:为 * this加上const,第二次:从 const operator[ ]的返回值中移除const。
const成员函数承诺绝不更改其对象的逻辑状态,non-const 成员函数却没有这种承诺,
class TextBlock{
public:
...
...
const char& operator[](std::size_t position) const//一如既往
{
...
...
return text[position];
}
char& operator[](std::size_t position) //现在只调用const op[]
{
return const_cast<char&>(
static_cast<const TextBlock&>(*this)[position]);
//将op[]返回值的const转除为*this加上const调用const op[]
}
private:
std::string text;
};
请记住
1、将某些东西声明为const可以帮助编译器侦测出错误用法。const可以被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数个体。
2、编译器强制实施bitwise constness ,但你编写程序时应该使用“概念上的常量性(conceptual constness)”
3、当const 和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可以避免代码重复。
下面用一个表现通讯录的class,其构造函数如下:
class PhoneNumber{...};
class ABEntry{ //ABEntry =Address Book Entry
public:
ABEntry(const std::string &name,const std::string & address,
const std::list<PhoneNumber>& phones);
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber>& thePhones;
int numTimesConsulted;
};
ABEntry::ABEntry(const std::string &name,const std::string & address,
const std::list<PhoneNumber>& phones){
theName=name;//这些都是赋值,并非初始化
theAddress=address;
thePhones=phones;
numTimesConsulted=0;
}
C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。
下面使用成员初始化列表的方法替换赋值动作:
ABEntry::ABEntry(const std::string &name,const std::string & address,
const std::list<PhoneNumber>& phones)
:theName(name),//现在这些都是初始化
theAddress(address),
thePhones(phones),
numTimesConsulted(0){
}//现在构造函数本体不必有任何动作
这个构造函数和上一个的最终结果相同,但通常效率较高。
对于大多数类型而言,比起先调用默认构造函数然后调用拷贝赋值操作符,单调用一次复制构造函数的比较高效率的。
static对象,其寿命从被构造出来直到程序结束为止。
所谓编译单元是指产生单一目标文件的那些源码。
class FileSystem{
public:
std::size_t numDisk() const;//众多成员函数之一
};
extern FileSystem tfs;//预备给客户使用的对象 non-local对象
class Directory{
public:
Directory(params);
};
Directory::Directory(params){
std::size_t disks=tfs.numDisk();
}
Directory tempDir(params);//为临时文件而做出的目录
由上面的non-local static类型修改为如下的local static类型的对象。
class FileSystem{
public:
std::size_t numDisk() const;//众多成员函数之一
};
FileSystem &tfs(){//这个函数用来替换tfs对象;它在FileSystem class中可能是个static。
//定义并初始化一个local static对象,返回一个reference对象。
static FileSystem fs;
return fs;
}
class Directory{
public:
Directory(params);
};
Directory::Directory(params){
std::size_t disks=tfs.numDisk();
}
Directory &tempDir(){//这个函数用来替换 tempDir对象
static Directory td;
return td;
}
请记住
1、为内置型对象进行手工初始化,因为C++不保证初始它们。
2、构造函数最好使用成员初始化列表进行初始化,而不要在构造函数本体内使用赋值操作。成员初始化列表的成员变量,其排列次序应该和它们在class中的声明次序相同。
3、为了免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象(其实就是单例)
资料来源:Effective C++
仅供学习 侵删