前段时间开始看《Effective C++》这本书,感觉上面一些东西确实挺有用的,所以把它整理出来。
条款01:视C++为一个语言联邦
将C++视为一个由相关语言组成的联邦,总共有4个次语言组成:
(1)C
(2)Object-Oriented C++(面向对象):包括:类、封装、继承、多态、virtual函数
(3)Template C++(泛型)
(4)STL
条款02:尽量以const enum inline 替换 #define
#define ASPECT_RATIO 1.653
以上使用的名称可能未进入记号表,解决之道是以常量代替宏(#define):
const double AspectRatio=1.653
使用常量替换宏,有两种特殊的情况:
(1)定义常量指针
由于常量定义通常放在头文件内,因此有必要将指针声明为const
const char* const authorname="hello";
const std::string authorName("Hello");
(2)class专属常量
为了将常量的作用域限制于class内,必须让它成为class的成员;
确保常量最多只有一份实体,必须让它成为static
class GamePlayer{
private:
static const int Num=5; //声明式
int scores[Num];
};
C++要求对所使用的的任何东西提供定义式。以上情况(static且为整数类)只要不取Num的地址,就无需提供定义式。
但如果要取class专属常量的地址,需提供以下定义式:
const int GamePlayer::Num;
#define不仅不能用来定义class专属常量,也不提供任何封装
某些编译器不支持上述语法:不允许static成员在其声明式上获得初值。只能将初值放在定义式:
class CostEstimate{
private:
static const double fudge;
};
const double CostEstimate::fudge=1.35
class在编译期间需要一个class常量值,若编译器不允许static成员在其声明式上获得初值,则可以用enum:
class GamePlayer{
private:
enum{Num=5};
int scores[Num];
};
另一个常见#define误用:
//a和b的较大值
#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b)) //宏中所有参数需加小括号
可以获得宏带来的效率以及一般函数的所有可预料行为和类型安全性(template inline):
template<typename T>
inline void callWithMax(const T &a,const T &b)
{
f(a>b?a:b);
}
- 对于单纯常量,最好使用const或enum代替#define
- 对于型似函数的宏,最好使用inline函数代替#define
条款03:尽可能使用const
char greeting[]="hello";
char* p=greeting;
const char* p=greeting;
char* const p=greeting;
const char* const p=greeting;
const在*左边,表示被指物是常量:
std::vector<int> vec;
...
const std::vector<int>::iterator iter=vec.begin();
*iter=10;
const在*右边,表示指针自身是常量:
std::vector<int> vec;
...
std::vector<int>::const_iterator iter=vec.begin();
iter++;
const成员函数
目的:确认该成员函数可作用于const对象上
class TextBlock{
public:
const char& operator[](std::size_t position) const
{return text[position];}
char& operator[](std::size_t position)
{return text[position];}
private:
std::string text;
};
以上TextBlock的operator[]可被这么使用:
TextBlock tb("hello"); //调用non-const
std::cout<<tb[0];
void print(const TextBlock& ctb)
{
std::cout<<ctb[0]; //调用const
}
std::cout<<tb[0]; //读non-const
tb[0]='x'; //写non-const
std::cout<<ctb[0];//读const
ctb[0]='x'; //error
注意:non-const返回类型是char&,不是char。如果返回是一个char,下面就无法通过编译:
tb[0]='x';
因为如果函数的返回类型为内置类型,那么改动函数的返回值就不合法。
*********************
const char& operator[](std::size_t position) const
第一个const指返回值不可变,第二个const指函数内成员变量不可改变
需要后一个const修饰的函数成员变量可更改,需要加上mutable:
class CTextBlock{
public:
std::size_t lenth() 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成员函数中避免重复
class TextBlock{
public:
const char& operator[](std::size_t position) const
{
... //边界检验
... //日志数据访问
... //检验数据完整性
return text[position];
}
char& operator[](std::size_t position)
{
... //边界检验
... //日志数据访问
... //检验数据完整性
return text[position];
}
private:
std::string text;
};
operator[]使用了两次,只是修饰符const不一样。
真正要实现常量性转除,让non-const operator[]调用其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]);
}
private:
std::string text;
};
条款04:确定对象被使用前已经初始化
确保每一个构造函数都将对象的每个成员初始化
重要的是别混淆了赋值和初始化
class PhoneNumber{...}
class ABEntry{
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;
}
ABEntry构造函数的较佳写法是,使用成员初值列替换赋值动作:
ABEntry::ABEntry(const std::string& name, const std::string& address,
const std::list<PhoneNumber>& phones)
:theName(name),
theAddress(address),
thePhones(phones),
numTimesConsulted(0)
{ }
不同编译单元内定义的non-local static对象
函数内的对象称为local static对象,其他static对象称为non-local static对象。
问题:某个编译单元内的non-local static对象的初始化使用了另一个编译单元内的non-local static对象,但该对象还未初始化。
解决:将每个non-local static 对象搬到自己的专属函数内(该对象在此函数内被声明为static)
使用函数返回的“指向static对象”的引用,而不再使用static对象自身
class FileSystem{
public:
...
std::size_t numDisks() const;
...
};
FileSystem& tfs()
{
static FileSystem fs; //定义并初始化一个local static对象
return fs; //返回一个引用指向FileSystem
}
class Directory{...}
Directory::Directory(params)
{
std::size_t disks=tfs().numDisks();
}
Directory& tempDir()
{
ststic Directory td;
return td;
}
- 为内置对象进行手工初始化,C++不保证初始化他们。
- 构造函数最好使用成员初值列。
- 为避免跨编译单元的初始化次序问题,用local static对象代替non-local static对象。