条款02 尽量以const,enum, inline替换#define
如把#define ASPECT_RATIO 1.653
替换为const double AspectRatio = 1.653
以常量替换#define
特殊情况
1. 定义常量指针。
将指针(而不是指针所指之物)声明为const
例如,若在头文件内定义一个常量的(不变的)char*-based
字符串,则必须写两次const
const char* const authorName = "Scott Meyers";
const std::string suthorName("Scott Meyers");//下面的更好一些
2. class专属常量
为了将常量的作用域限制于class内部,且此常量至多只有一份实体,必须使其成为class的一个static成员
class GamePlayer{
private:
static const int NumTurns = 5;//常量声明式
int scores[NumTurns]; //使用该常量
...
};
//定义式,放在实现文件里,且由于声明时获得了初值,因此定义时不可再赋初值
const int GamePlayer::NUmTurns;
//旧编译器不允许static成员在其声明时获得初值
class GamePlayer{
private:
static const double FudgeFactor;//常量声明式
...
};
//定义式,放在实现文件里
const double GamePlayer::FudgeFactor = 1.35;
//当在编译时需要一个class常量值,则可用一个属于枚举类型代替
class GamePlayer{
private:
enum{NumTurns = 5};//常量声明式
int scores[NumTurns]; //使用该常量
...
};
//注意:取const地址合法,enum地址和#define地址都不合法
条款03 尽可能使用const
1.const出现在*左边,表示被指物是常量
2.如果出现在*右边,表示指针自身是常量
3.如果出现在*两边,表示被指物和指针都是常量
4.如果被指物是常量,const写在类型之前或者类型之后、*之前都可
char greeting[] = "Hello";
char* p = greeting; //非常量指针,非常量数据
const char* p = greeting; //非常量指针,常量数据
char* const p = greeting; //常量指针,非常量数据
const char* const p = greeting; //常量指针,常量数据
//以下两者等价
void f1(const Widget* pw);
void f2(Widget const * pw);
STL迭代器(作用类似于T*指针)里的const
- 迭代器为const等同于声明指针为const,表明这个迭代器不得指向不同的东西,但它所指的东西的值是可以改变的
- 如果希望迭代器所指的东西的值不可改变,需要const_iterator
std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin();
*iter = 10;//正确
iter++;//错误
std::vector<int>::const_iterator citer = vec.begin();
*citer = 10;//错误
citer++;//正确
const成员函数
- 目的:确认该成员函数可用于const对象身上
- 两个成员函数如果只是常量性不同,则可以被重载
class TextBlock{
public:
...
const char& operator[](std::size_t position) const//用于const对象,返回一个const值
{return text[position];}
char& operator[](std::size_t position) //用于非const对象
{return text[position];}
private:
std::string text;
};
TextBlock tb("Hello");
cout<<tb[0];//调用非const成员函数operator[]
const TextBlock ctb("Hello");
cout<<ctb[0];//调用const成员函数operator[]
void print(const TextBlock &ctb)
{
std::cout<<ctb[0];//调用const TextBlock::operator[]
...
}
cout<<tb[0];
tb[0] = 'x';//对于非const,两个操作都没问题
cout<<ctb[0];//正确
ctb[0] = 'x';//对于const,操作错误,错误原因:对一个const版的operator[]返回的一个const char& 施行赋值动作
//non-const operator[]返回类型是一个char的地址,不是char;
//如果对一个返回值是char而不是char&的赋值。则是错误的
- const成员函数不可更改对象内任何non-static成员变量
- 如果想在const函数里改变non-static,则应使用mutable释放掉non-static成员变量
class CTextBlock{
public:
...
std::size_t length() const;
private:
char* pText;
mutable std::size_t textLength;
mutable bool lengthIsValid;
};
std::size_t CTextBlock::length() const{
if(!lengthIsValid){
textLength = std::strlen(pText);
lengthIsValid = True;
}
return textLength;
}
- 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复,const不能调用non-const
class TextBlock{
public:
...
const char& operator[](std::size_t position) const//用于const对象,返回一个const值
{
...
...
...
return text[position];}
char& operator[](std::size_t position) //用于非const对象
{
return
const_cast<char&>(//解除const operator[]的返回值中移除const
static_cast<const TextBlock&>(*this)//将*this从TextBlock&转型为const TextBlock&,使得调用operator[]时得以调用const版本
[position]);
}
private:
std::string text;
};
条款04 确定对象被使用前以先被初始化
1. 对于无任何成员的内置类型(int, bool等),必须手工完成初始化
2. 对于内置类型以外的,则靠构造函数:确保每一个构造函数都将对象的每一个成员初始化
初始化和赋值
class PhoneNumber{...};
class ABEntry{
public:
ABEntry(const std::string& name, const atd::string& address,
const std::list<PhoneNumber>& phones);
private:
std::string theName;
std::string theAddress;
std::list<PhoneNumber> thePhones;
int numTimesCounsulted;.
};
ABEntry::ABEntry(const std::string& name, const atd::string& address,
const std::list<PhoneNumber>& phones)
{
theName = name;
theAddress = address;
thePhones = phones;
numTimesCounsulted = 0;//赋值,而不是初始化
//首先调用default构造函数为theName,theAddress,thePhones设新值,然后立刻再对它们赋予新值
}
//使用成员初始列替换赋值动作
ABEntry::ABEntry(const std::string& name, const atd::string& address,
const std::list<PhoneNumber>& phones)
:theName(name),
theAddress(address),
thePhones(phones),
numTimesCounsulted(0)//初始化
{
}
//使用default构造一个成员变量,可以使用成员初值列,只要指定无物作为初始化实参即可
ABEntry::ABEntry(const std::string& name, const atd::string& address,
const std::list<PhoneNumber>& phones)
:theName(),
theAddress(),
thePhones(),
numTimesCounsulted(0)//初始化
{
}
如果成员变量是const或references,就一定需要初值,不能被赋值
C++有着十分固定的 “成员初始化次序” ,次序总是相同,base classes更早于其derived classes被初始化,而class的成员变量总是以其声明次序被初始化
为免除“跨编译单元之初始化次序”问题,以local static对象替换non-local static对象
static对象:在函数内部的static对象被称为local static对象(因为它们对函数而言是local),其他的static对象称为non-local static对象
编译单元:单一源码文件加上其所含入的头文件(#include files)
至少有两个源码文件,每一个至少有一个non-local static对象(即该对象是global或者位于namespace作用域内,抑或在class内或file作用域内被声明为static
问题:如果某编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化
例如
class FileSystem{
publie:
...
std::size_t numDisks()const;
...
};
extern FileSystem tfs;//位于golbal或者namespace作用域内
class Directory{
public:
Directory(params);
...
};
Directory::Directory(params)
{
...
std::size_t disks = tfs.numDisks();//使用tfs对象
...
}
Directory tempDir(params);
//以上是错误的
//正确:将non-local换为local,使用函数返回的“指向static对象”的references而不再使用static对象自身
class FileSystem{
publie:
...
std::size_t numDisks()const;
...
};
FileSystem& tfs()
{
static FileSystem fs;//定义并初始化一个local static对象
return fs;//返回它
}
class Directory{
public:
Directory(params);
...
};
Directory::Directory(params)
{
...
std::size_t disks = tfs().numDisks();//使用tfs对象
...
}
Directory& tempDir()
{
static Directory td;
return td;
}