Conclusion for Accustoming Yourself to C++

条款01:

1.C++是一个同时支持过程形式、面向对象形式、函数形式、泛型形式、元编程形式的语言。



条款02:

1.宁可以编译器替换预处理器

#define ASPECT_RATIO 1.653
记号名称ASPECT_RATIO也许从未被编译器看见,在编译器开始处理源码之前它就被移走了。于是ASPECT_RATIO可能没有记号表内。当运行此常量获得错误信息时,错误信息提到1.653而不是ASPECT_RATIO。

解决方法是用常量替换宏:

const double AspectRatio = 1.653;
2.关于指针为const和指针所指之物为const

const char* const authorName="Scott Meyers";
第一个const修饰指针所指物,第二个const修饰指针。

3.为了将常量的作用域限制于class内,你必须让它成为class的一个成员;而为确保此常量至多只有一份实体,必须让它成为一个static成员

class GamePlayer{
private:
	static const int NumTurns = 5;   //常量声明式
	int scores[NumTurns];            //使用该常量
};
然而上面的NumTurns是声明式而不是定义式。通常C++要求对所使用的任何东西提供一个定义式,但如果是个class专属常量又是static且为整数类型,则需特殊处理。

只要不取他们的地址,你可以声明并使用它们而无需提供定义式。但如果取某个class专属常量的地址,就必须另外提供定义式如下:

const int GamePlayer::NumTurns;
由于class常量已在声明时获得初值,因此定义时不可以再设初值。

不用#define创建一个class专属常量,因为其不重视作用域。一旦宏被定义,它在其后的编译过程中有效。

4.如果class内部有non-static const常量,不能在声明时赋值。

class GamePlayer{
private:
	const int NumTurns = 5;   //错误,企图在类中初始化const数据成员
	int scores[NumTurns];            //使用该常量
};
const数据成员初始化只能在类构造函数的初始化成员列表中进行。
5.也可以在class类中只声明,不赋初值,在定义式中赋初值:

class GamePlayer{
private:
	static const int NumTurns;   //
	//int scores[NumTurns];            //使用该常量
};
const int GamePlayer::NumTurns = 5;
int main()
{
	GamePlayer g;
	return EXIT_SUCCESS;
}
此时,就不能再在类中使用NumTurns来设置数组大小了。

6.一个属于枚举类型的数值可权充ints被使用。

class GamePlayer{
private:
	enum{NumTurns=5};                //令NumTurns成为5的记号
	int scores[NumTurns];            //使用该常量
};
取一个const的地址是合法的,但取一个enum或#define的地址通常不合法。

如果不想别人获得一个pointer或reference指向你的某个整数常量,enum可以实现这个约束。

7.#define的另一个误用情况是以它实现宏,宏看起来像函数,但不会招致函数调用带来的额外开销。

#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))
//以a和b的较大值调用f
如果做下面的调用,就会出问题:
int a=5,b=0;
CALL_WITH_MAX(++a,b);         //a被累加二次
CALL_WITH_MAX(++a,b+10);  //a被累加一次
上面这种问题,用inline函数就可以解决,也不会招致函数调用带来的额外开销。




条款03:

1.关键字const出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在两边,表示被指物和指针两者都是常量。

下面两种写法等价:

void f1(const Widget* pw);  //指针指向对象为const
void f1(Widget const* pw);
2.声明STL迭代器为const就像声明指针为const一样,表示这个迭代器不得指向不同的东西,不能自增和自减,但是其所指的对象可以改变。
如果希望迭代器所指的东西不可被改动,需要的是const_iterator:

const std::vector<int>::iterator iter=vec.begin();   //相当于T* const
std::vector<int>::const_iterator cIter=vec.begin(); //相当于const T*   
3.两个成员函数如果只是常量性不同(一个有const,一个没有),则可以被重载。
#include<iostream>  
#include <string>
using namespace std;


class TextBlock{
public:
	TextBlock(std::string _text) :text(_text){}
	const char& operator[](std::size_t position) const  //const
	{
		return text[position];
	}
	char& operator[](std::size_t position)              //non-const
	{
		return text[position];
	}
private:
	std::string text;
};

int main()
{
	TextBlock tb("Hello");
	std::cout << tb[0];           //调用non-const
	const TextBlock ctb("World");
	std::cout << ctb[0];           //调用const
	return EXIT_SUCCESS;
}

4.有const修饰的成员函数,只能读取数据或成员,不能改变数据成员(non-static);没有const修饰的成员函数,对数据成员则是可读可写的。

const对象只能调用const成员函数,非const对象可以调用const成员函数和非const成员函数。

上面

char& operator[](std::size_t position)              //non-const

返回的是reference to char,所以可以执行:tb[0]='x';

如果返回的只是一个char,则上面的句子就无法通过编译。因为如果函数的返回类型是个内置类型,那么改动函数返回值从来就不合法。即使合法,C++以by value返回对象意味着被改动的其实是tb.tetx[0]的一个副本,不是其本身。

5.如果有成员变量是指针,其所指物不属于对象,也就是说,const成员函数可以修改该指针所指物。

#include<iostream>  
#include <string>
using namespace std;


class CTextBlock{
public:
	CTextBlock(char* _text) :pText(_text){}
	char& operator[](std::size_t position)  const            
	{
		return pText[position];
	}
private:
	char* pText;
};

int main()
{ 
	const CTextBlock cctb("Hello");
	char *pc = &cctb[0];
	*pc = 'J';
	cout << cctb[0] << endl;
	return EXIT_SUCCESS;
}
上面这段程序照理说是可以改变值的,可是运行到8pc='J'那句是会出现写内存错误。

6.mutable释放掉non-static成员变量的bitwise constness约束:成员函数只有在不更改对象之任何成员变量(static除外)时才可以说是const。

class CTextBlock{
public:
	CTextBlock(char* _text) :pText(_text){}
	std::size_t length() const;
private:
	char* pText;
	mutable std::size_t textLength;   //这些成员即使在const函数内也可能被修改
	mutable bool lengthIsValid;
};
7.如果有一个const成员函数和一个对应的非const成员函数(如3中operator函数),为了避免重复,可以使用常量性转移:令其中一个调用另外一个。

可以用non-const调用const函数,但是中间需要一个转型动作。

class TextBlock{
public:
	TextBlock(std::string _text) :text(_text){}
	const char& operator[](std::size_t position) const  //const
	{
		return text[position];
	}
	char& operator[](std::size_t position)              //non-const
	{
		return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
	}
private:
	std::string text;
};
其中const TextBlock指明调用的是const operator[],否则调用operator[],会递归调用自己。这是第一次转型(安全转型,使用static_cast)。

第二次是从const operator[]的返回值中移除const(只能由const_cast完成)。

不能反过来:在const成员函数内调用非const函数,这样曾经承诺不改变的那个对象被改动了。




条款04:
1.使用初始化成员列表完成初始化。

2.当在成员初值列中条列各个成员时,最好总是以其声明次序为次序。 

3.编译单元:当一个C或CPP文件在编译时,预处理器首先递归包含头文件,形成一个含有所有信息的单个源文件,这个源文件就是一个编译单元。

4.问题:至少含有两个源码文件,每一个内含至少一个non-local static对象,如果某编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,它所用到的这个对象可能尚未初始化,这样会出问题。

class FileSystem{
public:
	std::size_t numDisks() const;
};
extern FileSystem tfs;
现在客户建立一个class用以处理文件系统内的目录:

class Directory{
public:
	Directory(params);
};
Directory::Directory(params)
{
	std::size_t disks = tfs.numDisks();  //使用tfs对象
}
现在客户决定创建一个Directory对象:

Directory tempDir(params);
初始化次序的重要性出来了:除非tfs在tempDir之前先被初始化,否则tempDir的构造函数会用到未初始化的tfs。
5.用单例模式来解决上面问题:将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。(non-local static对象被local static对象替换了。)
class Filesystem{};
Filesystem& tfs()
{
	static Filesystem fs;
	return fs;
}
class Directory{};
Directory::Directory(params)
{
	std::size_t disks = tfs.numDisks();  //使用tfs对象
}
Directory& tempDir()
{
	static Directory td;
	return td;
}
6.竞速形式:如果一个函数

void func()
{
	static A a;
}
在多线程情况下编译实际会变成类似:

void func()
{
	if (第一次进入函数func)
	{
		初始化a;
		注册退出程序时调用a的析构;
		标记为非第一次进入;
	}
}
但这段代码不是安全的,两个线程可能同时进入导致都认为是第一次进入。

7.函数内含static对象的事实使他们在多线程系统中带有不确定性。处理这个麻烦的一种做法是:在程序的单线程启动阶段手工调用所有reference-returnning函数,这可消除与初始化有关的竞速形式(在单线程下初始化,就不会同时有两个线程进入且都认为自己是第一次进入了)。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值