《Effective C++》总结篇(让自己习惯C++)

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

C++可以视为由相关次语言组成的联邦,每个次语言都有自己的规约。
大致分为四个:
C:C++仍然是以C为基础。区块,语句,预处理,内置数据类型,数组,指针都是来自C

Object-Oriented C++:面向对象设计。封装,继承,多态,虚函数,析构构造。

Template C++:泛型编程。templates威力强大,可以带来崭新的编程范型。

STL:标准模板程序库。他对容器,迭代器,算法以及函数对象的规约有极佳的紧密配合与协调。
请记住:C++高效编程守则视状况而改变,取决于你使用c++的哪一部分

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

因为#define不能被视为语言,可能会导致以下问题:
比如有#define PI 3.14

1.PI也许从未被编译器看见;也许在编译器开始处理源码之前它就被预处理器移走了。于是PI有可能没进入符号表(symbol table)

[symbol table:通常,C 或 C++ 编译器将单个源文件编译为扩展名为 .obj 或 .o 的目标文件。目标文件中有一个称为符号表的数据结构,它将目标文件中的不同项目映射到链接器可以理解的名称。]

2.用#dfine会产生大量的码,因为预处理器“盲目地将宏名称PI替换为3.14”导致目标码出现多份3.14. 可以通过使用常量解决这个问题[const double PI=3.14;]

3.#define 无法构建class的专属常量,也不提供任何封装性,因为#define不重视作用域(scope)。

[
你可以使用enum hack技术实现在类中定义匿名枚举,达到常量的作用。
如:

class GamePlayer
{
private:
	enum { NUM = 5 };
	int socres[NUM];
};

enum hack的行为比较像#define而不像const,因为不能对enum取地址。这样可以不让别人获得一个指针或引用指向你的某个整数常量。
]

4.使用#define实现函数,这样不会招致函数调用的额外开销,但是会有很多问题。

比如如下函数:

#include <iostream>
using namespace std;
template<class T>
void f(T x)
{
	cout << "max is " << x << endl;
}
#define MAX(a,b) f((a)>(b)?(a):(b));

int main()
{
	int a = 5, b = 0;
	MAX(++a, b);     //max is 7
	a = 5, b = 0;
	MAX(++a, b + 10);//max is 10
	return 0;;
}

调用f之前,a的递增次数竟然取决于”它被拿来和谁比较”。
[
你可以使用inline关键字替代这种宏定义函数,(注意内联函数只是对编译器的建议,实际是否内联需要看编译器心情)
内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。

#include <iostream>
using namespace std;
template<class T>
void f(T x)
{
	cout << "max is " << x << endl;
}
template<class T>
void MAX(const T &a,const T &b)
{
	f(a > b ? a : b);
}
int main()
{
	int a = 5, b = 0;
	MAX(++a, b);     //max is 6
	a = 5, b = 0;
	MAX(++a, b + 10);//max is 10
	return 0;;
}

]
请记住:对于单纯的常量最好以const对象或enums替换#define
对于形似函数的宏,最好改用inline函数替换#define

条款三:尽可能使用const

const允许你指定语义约束,而编译器会强制实施这项约束。它允许你告诉编译器和其他程序员某值应该保持不变。

1.常量指针&指针常量&&常量指针常量

const出现在左边,表示指针自身是常量,即常量指针。int * const p;
如果出现在
右边,表示被指物是常量,即指针常量。int const * p;
如果都出现,则被指物和指针都是常量,即常量指针常量。const int *const

#include <iostream>
using namespace std;
int main()
{
	int a = 10;
	int b = 20;
	int* const p1 = &a;//指针是常量不能更改。指针常量。
	*p1 = 10;//正确
	p1 = &b;//错误

	const int* p2 = &a;//被指物是常量不能更改。常量指针。
	*p2 = 10;//错误
	p2 = &b;//正确

	int const* p3 = &a;//被指物是常量不能更改。常量指针。
	*p3 = 10;//错误
	p3 = &b;//正确

	const int* const p4 = &a;//指针和被指物都是常量不能更改。常量指针常量。
	*p4 = 10;//错误
	p4 = &b;//错误

	return 0;
}

[
个人记忆方法:哪个是哪个就顺着读就行,const * 常量指针/* const指针常量/const * const 常量指针常量
然后用具体哪个是常量的话,
指针常量:指针常量就是指针本身是常量
常量指针:指向常量的指针

]

2.const和函数产生的关联

一,令函数返回一个常量值,可以降低因客户错误而造成的意外。

#include <iostream>
using namespace std;
class  A
{
public:
	friend A operator*(const A& a, const A& b)
	{
		A res;
		res.x = a.x * b.x;
		return res;
	}
	int x;
};

int main()
{
	A a, b, c;
	a.x = 2;
	b.x = 4;
	c = a * b;
	cout << c.x << endl;//8
	(a * b) = c;//允许
	return 0;
}

客户可以实现(a * b) = c;对两个乘积然后赋值的操作这种骚操作。
但如果写成friend const A operator*(const A& a, const A& b)
就能避免这种问题的出现。

二,const成员函数
将const实施于成员函数的目的是,为了确认该成员可以作用于const对象身上。
它可以使得接口比较容易理解。甚至可以使得”操作const对象”成为可能(等下说)

许多人漠视一件实事:两个成员函数只是常量性不同,可以被重载。
这是一个重要的C++特性。

#include <iostream>
using namespace std;
class  A
{
public:
	A(string res):text(res)
	{
	}
	const char& operator[](int pos)const
	{
		cout << "call const operator[]" << endl;
		return text[pos];
	}
	char& operator[](int pos)
	{
		cout << "call non-const operator[]" << endl;
		return text[pos];
	}
private:
	string text;
};

int main()
{
	A a("123");
	cout << a[0] << endl;//call non-const operator[]
	const A a1("456");
	cout << a1[0] << endl;//call const operator[]
	a[0] = 'c';
	return 0;
}

真实程序const对象大多用于通过指针转常量或者通过引用转常量的传递结果。

插播一条关于bitwise const和logical constness的讨论。

bitwise constness:理论上 const成员函数 表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变。但是bitwise constness机制产生的漏洞可以使我们改变成员 通过获得静态成员对象的指针,并修改指针内容做到。

logical constness:成员变量加上mutable关键字就能使其在const成员函数内被修改

三,使用non-const成员函数调用const成员函数可以避免代码重复。
假设operator[]不单只返回一个引用指向某字符,而且执行边界检验,日志访问信息,等等代码。这样的话你在两个函数都要写大量重复的代码。

const char& operator[](int pos)const
{
	//执行边界检验,日志访问信息。。。
	return text[pos];
}
char& operator[](int pos)
{
	//执行边界检验,日志访问信息。。。
	return text[pos];
}

所以避免重复,我们可以使用non-const调用const的

char& operator[](int pos)
{
	return const_cast<char&>(static_cast<const A&>(*this)[pos]);
}

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

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

int x;
1.读取未初始化的值会导致不明确的行为。在某些平台上,仅仅只是读取未初始化的值,就可能让你程序终止。所以最佳的处理办法就是:永远在使用对象之前先将它初始化

2.还有别混淆了赋值和初始化。
C++规定对象的成员变量的初始化动作发生在进入构造函数本体之前。所以,尽可能使用成员初值列(member initialization list)替换赋值操作。而且这样效率较高。
class A
{
public:
A(string res):text(res)//初始化
{
}
A(string res)
{
text = res;//赋值
}

private:
string text;
};

3.default构造一个成员变量
class A
{
public:
A():text()//初始化
{
}

private:
string text;
};

如果成员变量在”成员初值列”中没有被指定初值的话,那么编译器会为用户自定义类型的成员变量自动调用default构造函数。

4.如果成员变量是const或reference那么一定需要初值,而且不能被赋值。

5.static对象。
当我们想使用一个静态对象,它却还未构造完成就使用了,则会造成惨重的灾难。但是我们无法确定non-local static对象(全局)的初始化相对次序,因为它无明确定义,解决方案是使用每个non-local static对象搬到自己专属的函数内。这些函数返回一个引用指向它所含的对象。
换句话说non-local static对象变成了local-static对象,这其实就是单例的运用。
这个手法的基础在于:C++保证,函数内的local static对象会在“该函数调用期间””首次遇上该对象的定义式”时被初始化。

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

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值