【effective c++读书笔记】【第1章】让自己习惯C++

条款01:视C++为一个语言联邦

1、C++可以分为四个主要的次语言:

1)C。说到底C++仍是以C为基础。区块,语句,预处理器,内置数据类型,数组,指针统统来自C。

2)Object-Oreinted C++。这一部分是面向对象设计之古典守则在C++上的最直接实施。类,封装,继承,多态,virtual函数......等等

3)Template C++。这是C++泛型编程部分。

4)STL。STL是个template程序库。主要包括容器(containers),迭代器(iterators),算法(algorithms)以及函数对象(function objects)...

请记住:

  • C++高效编程守则视状况而变化,取决于你使用C++的哪一部分。

条款02:尽量以const,enum,inline替换#define

1、const替换#define

#define定义的记号名称可能在编译器处理源码之前就被预处理器移走了,于是记号名称有可能没进入记号表内,从而就没有机会被编译器看见。这样会给调试工作带来不便。解决方法是用一个常量替换上述的宏。好处有如下几点:a、语言常量肯定会被编译器看到,会进入符号表内。b、使用常量可能比使用#define导致较小量的码,因为预处理器盲目的将宏名称替换会导致目标码出现多份替换后的内容。

常量替换#defines要注意两种特殊情况:a、常量指针的定义,如:const char * const authorName =“Scott Meyers”。b、class专属常量,即一个static const member,要在实现文件中定义它。

2、enum替换#define

enum hack

class GamePlayer{
private:
	static const int NumTurns = 5;
	int scores[NumTurns];
};

对于支持“static整数型class常量”的类内初始化的编译器,上述代码能通过编译,但不支持的上述代码会出现编译错误。于是可以用enum hack的补偿做法,理论基础是:一个属于枚举类型的数值可权充ints被使用,GamePlayer可定义如下:

class GamePlayer{
private:
	enum{ NumTurns = 5 };
	int scores[NumTurns];
};

好处是:a、enum hack的行为更像#define而不是const,如果你不希望别人得到你的常量成员的指针或引用,可以用enum hack替代之。b、使用enum hack不会导致非必要的内存分配。c、enum hack是模板元编程的基本技术,许多代码用了它。看到它时你要认识它。

3、inline替换#define
#define CALL_WITH_MAX(a,b)    f((a) > (b)) ? (a) : (b))

写出上述宏时,要为宏中的所有实参加上小括号,否则可能会在表达式调用这个宏时遭遇麻烦。

替换:

template<typename T>
inline void callWithMax(cosnt T &a, cosnt T &b){
	f(a > b ? a : b);
}

不需要再函数本题中为参数加上括号,也不需要操心参数被核算多次……等等。callWithMax是个真正的函数,它遵循作用于和访问规则。

请记住:

  • 对于单纯常量,最好以const对象或enums替换#defines。
  • 对于形似函数的宏,最好改用inline函数替换#defines。

条款03:尽可能使用const

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

char greeting[] = "hello"
char* p = greeting; 
const char* p = greeting;//非常量指针,指针可变,指针指向内容不能变
char* const p = greeting;//常量指针,指针不能变,指针指向内容可变
const char* const p = greeting;//指针和指针指向内容都不能变

2、STL中例子

std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin();
*iter = 10; //没问题,改变iter所指物
++iter; //错误,iter是const

std::vector<int>::const_iterator cIter = vec.begin();
*cIter = 10;//错误,*cIter是const
++cIter;//没问题,改变cIter

3、const成员函数

const实施于成员函数的目的,是为了确认该成员函数可作用于const对象身上。这类成员函数之所以重要,有以下两个理由:第一,const成员函数使class接口比较容易被理解。第二,它们使“操作const对象”称为可能。

两个成员函数如果只是常量性不同,可以被重载。

mutable(可变的)关键字可以释放掉non-static成员变量的bitwise constness约束,被mutable修饰的成员变量可能总是会被更改,即使在const成员函数内。

non-const成员函数调用const成员函数是一个避免代码重复的安全做法。相反,const成员函数调用non-const成员函数是一种错误行为,因为对象有可能因此被改动。

non-const成员函数调用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&>( //将op[]返回值的const转除
			static_cast<const TextBlock&>(*this)//为*this加上const
			[position]);//调用const op[]
	}
private:
	std::string text
};

请记住:

  • 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域的对象,函数参数,函数返回类型,成员函数本体。
  • 编译器强制实施bitwise constness,但你编写程序时应该使用"概念上的常量性"(conceptual constness)。
  • 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

条款04:确定对象被使用前已先被初始化

1、永远在使用对象之前先将它初始化。对于无任何成员的内置类型,必须手工完成。对于内置类型以外的任何东西,初始化责任落在构造函数上,确保每一个构造函数都将对象的每一个成员初始化。

2、c++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。

3、编译单元是指产出单一目标文件的那些源码。基本上它是单一源码文件加上其所含入的头文件。C++对于“定义于不同的编译单元内的non-localstatic对象”的初始化相对次序并无明确定义。

class FileSystem{
public:
	...
	std::size_t numDisks() const;
	...
};
extern FileSystem tfs;
class Directory{
public:
	Directory(params);
	...
};
Directory::Directory(params){
	...
	std::size_t disks = tfs.numDisks();
	...
}
Directory tempDir(params);

现在初始化次序显得很重要:出发tfs在tempDir之前被初始化,否则tempDir的构造函数会用到尚未初始化的tfs。但是上述程序无法确定初始化顺序。

4、消除第3点说到的问题的方法:将每个non-local static对象搬到自己的专属函数内,该对象在此函数内被声明为static,此函数返回一个引用指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。这个方法的基础在于:c++保证函数内的local static对象会在该函数被调用期间首次遇上该对象之定义式时被初始化。

class FileSystem{
public:
	...
	std::size_t numDisks() const;
	...
};
FileSystem& tfs(){
	static FileSystem fs;
	return fs;
}
class Directory{
public:
	Directory(params);
	...
};
Directory::Directory(params){
	...
	std::size_t disks = tfs().numDisks();
	...
}
Directory& tempDir(){
	static Directory td;
	return td;
}

请记住:

  • 为内置对象进行手工初始化,因为C++不保证初始化它们。
  • 构造函数最好使用成员初始化列表,而不要在构造函数本体内使用赋值操作。初始化列表列出的成员变量,其排列次序应该和它们在类中的声明次序相同。
  • 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象。

版权声明:本文为博主原创文章,未经博主允许不得转载。

原文地址:https://www.cnblogs.com/ruan875417/p/4785449.html