【Effective C++读书笔记】篇二(条款02~条款04)

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

#define  PI    3.14

1)对于这样的宏定义,PI 在编译之前被预处理器全部换成了 3.14,所以 PI 也许并不会进入符号表(symbol table),当运用此常量发生编译错误时,错误信息也许会提到 3.14 而不是 PI ,此时不知道此宏定义的程序员就可能会产生困惑。

解决方法是用 const :

const dobule Pi = 3.14;

2)其次,定义不好的宏会让人痛苦不堪。例如:

#define max(a, b) a > b ? a : b

看似正确的写法实则漏洞百出,比如我们这样使用:

int a = 1, b = 3, c;
c = ++max(a, b);

本意是要a 和 b 中最大值加 1 后的结果赋给 c ,实际经过预处理之后,代码变成了:

c = ++ a > b ? a : b;

c 的最后结果是 3 ,而非我们想要的 4。

所以记得要给宏中的所有实参加上小括号!!!如:

#define max(a, b) (((a) > (b)) ? (a) : (b))

即是这样,还是会发生一些意想不到的问题。

比如:

int a = 5, b = 0;
max(++a, b);		//a被累加二次
max(++a, b + 10);	//a被累加一次

a 的累加次数竟然取决于“它被拿来和谁比较”。

此时,你需要 inline 来解决此问题:

template <typename T>
inline T max(const &T a, const &T b)    //由于我们不知道 T 是什么,所以用 pass by reference-to-const
{
     return (a > b ? a : b);
}

3)使用 #define 无法定义一个专属于一个 class 的常量或函数,而使用 const 和 inline 完全可以做到。

请记住:

1)对于单纯变量,最好以 const 对象或 enum 替换 #define;

2)对于形似函数的宏,最好改用 inline 函数替换 #define。

条款03:尽可能使用 const

关键字 const 可以指定一个“不该被改动的值” ,而编译器会强制实施这项约束。

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

此外还要注意迭代器的 const 使用:

const std::vector<int>::iterator iter;     //类似 T *const, 迭代器本身不能改变
std::vector<int>::const_iterator iter;     //类似 const T *,迭代器所指内容不变

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

#include <iostream>
using namespace std;

class TextBlock
{
	public:
		TextBlock(string _str)
		{
			str = _str;
		}
		const char& operator[](size_t pos) const                  //operator[] for const 对象
		{
			return str[pos];
		}
		char& operator[](size_t pos)                              //operator[] for non-const 对象
		{
			return str[pos];
		}

	private:
		string str;
};

int main()
{
	const class TextBlock ctb("hello");
	class TextBlock tb("world");
	cout << ctb[0] << endl;
	cout << tb[0] << endl;
	cin >> tb[0];	                                                   //由于是 non-const,还可以cin
	cout << tb[0] << endl;
	return 0;
}

不过,non-const 对象是可以调用 const operator[] 的,反过来 const 对象不能调用 non-const operator[](可以注释掉上面的 non-const operator[] 试试 ^_^)。因为 const operator[]保证函数不修改对象的成员变量,而 non-const operator[] 不能保证。考虑到 const operator[] 完全做掉了 non-const operator[]
该做的一切,唯一不同的是其返回类型多了一个 const 修饰,这种情况下将 const 移除是安全的,所以我们可以用 non-const operator[] 调用const operator[] ,来避免代码的重复。

做法如下:

class TextBlock
{
	public:
		TextBlock(string _str)
		{
			str = _str;
		}
		const char& operator[](size_t pos) const                  //operator[] for const 对象
		{
			return str[pos];
		}
		char& operator[](size_t pos)                              //operator[] for non-const 对象
		{
			return
			const_cast<char&>(
			static_cast<const TextBlock&>(*this)              //使用 static_cast 将当前对象(*this)强制转换为
			[pos]                                             //const类型然后调用 const operator[],最后使用
			);                                                //const_cast将返回值的 const 属性移除
		}

	private:
		string str;
};

请记住:

1)将某些东西声明为 const 可帮助编译器侦测出错误用法。const 可被施加与任何作用域内的对象、函数参数、函数返回类型、成员函数本体;

2)编译器强制实施 bitwise constness,但你编写程序是应该使用“概念上的常量性”(conceptual constness);

3)当 const 和 non-const 成员函数有着实质等价的实现时,令 non-const 版本调用 const 版本可避免代码重复。

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

始终记住这句话:读取未初始化的值会导致不明确的行为。永远在使用对象之前将其初始化。

1)对于内置类型,必须手工初始化;

2)对于内置类型以外的其他东西,初始化责任在构造函数身上,要确保每一个构造函数都将对象的每一个成员初始化。

使用构造函数初始化时,切记区别初始化(Initialization)与赋值(assignment)的区别。

构造函数初始化的地方在成员初始化列表那里,函数体里面其实并非初始化,而是赋值,所以对于未使用列表初始化的成员,编译器会自动调用其对应的 default 构造函数,然后紧接着在进入函数体后对它们赋予新值。

考虑到像 const 或 reference 这样的成员变量,一定要初始化,不能被赋值,所以我们最好总是使用成员初始化列表。

例如:

#include <iostream>
using namespace std;

class TextBlock
{
	public:
		TextBlock(const int &_a, int &_b):a(_a), b(_b){}    //注意此处引用的参数使用的是 int &,如果使用 const int&
	private:                                                    //那么我们的成员变量也必须是常引用才行,否则报错。
		const int a;
		int &b;
};

int main()
{
	int i = 1, j = 2;
	class TextBlock tb(i, j);

	return 0;
}

当我想把“初始化”的过程放在函数体内时,gcc 报错如下:

error: uninitialized member ‘TextBlock::a’ with ‘const’ type ‘const int’ [-fpermissive]
error: uninitialized reference member ‘TextBlock::b’ [-fpermissive]

再一次说明只有成员初始化列表才是成员正真初始化的地方,函数体里只是赋值。

请记住:

1)为内置型对象进行手工初始化,因为C++不保证初始化它们。;

2)构造函数最好使用成员初始化列表(member initialization list),而不要在构造函数体内使用赋值操作(assignment)。初始化列表的排序次序最好与它们在 class 中的声明顺序一致;

3)为免除“跨编译单元之初始化次序”问题,请以 local static 对象替换 non-local static 对象。

时间: 2024-08-29 08:57:34

【Effective C++读书笔记】篇二(条款02~条款04)的相关文章

Effective Java 读书笔记之二 对于所有对象都通用的方法

尽管Object是一个具体的类,但设计它主要是为了扩展.它的所有非final方法都有明确的通用约定.任何一个类在override时,必须遵守这些通用约定. 一.覆盖equals时请遵守通用的约定 1.Object中默认的equals方法约定是:类的每个实例都只与它自身相等.当类有自己特有的“逻辑相等”的概念时,就应该覆盖equals方法. 2.Timestamp对Date进行了扩展,Timestamp的equals实现确实违反了对称性.如果Timestamp和Date混合一起使用,可能导致不正确

[.NET] 《Effective C#》读书笔记(二)- .NET 资源托管

<Effective C#>读书笔记(二)- .NET 资源托管 简介 续 <Effective C#>读书笔记(一)- C# 语言习惯. .NET 中,GC 会帮助我们管理内存,我们并不需要去担心内存泄漏,资源分配和指针初始化等问题.不过,它也并非万能,因为非托管资源需要我们自己进行清理,如文件句柄.数据库连接.GDI+ 对象和COM 对象等. 目录 十二.推荐使用成员初始化器而不是赋值语句 十三.正确地初始化静态成员变量 十四.尽量减少重复的初始化逻辑 十五.使用 using

Effective C++读书笔记之十二:复制对象时勿忘其每一个成分

Item 12:Copy all parts of an object 如果你声明自己的copying函数,意思就是告诉编译器你并不喜欢缺省显示中的某些行为.而编译器会对"你自己写出copying函数"做出一种复仇的行为:既然你拒绝它们为你写出copying函数,如果你的代码不完全,它们也不会告诉你.结论很明显:如果你为class添加一个成员变量,你必须同时修改copying函数.如果你忘记,编译器不太可能提醒你. 一下提供一种正确的模版: class Date{...}; class

《Effective C++ 读书笔记》( 一 )

<Effective C++ 读书笔记> 条款01 : 视C++为一个语言联邦 将C++ 视为一个由相关语言组成的联邦而非单一语言. 我们可以将C ++ 看成四个部分 : 1. C 语言 . C ++ 中的区块 , 语句, 预处理器 , 内置数据类型 , 数组 , 指针等统统来自于C语言. 2. 面向对象的 C ++ . 类( 包括构造函数和析构函数 ) , 封装 , 继承 ,多态 , virtual 函数 ( 动态绑定 ) ... 等等 . 这一部分都是基于面向对象而设计的. 3. Temp

我编程,我快乐——读书笔记(二)

我编程,我快乐--读书笔记(二) 第二章 在产品上投资 1. 我之所以说自己有天赋,是因为我发现自己在工作中不断学习,并且不断进步. 2. 只有向这件产品中投入心血.汗水.眼泪和资金,才能使它真正具有价值. 3. 要主动问,不要等着别人来告诉你.授人以鱼不如授人以渔. 4. 如果你想要雇佣某人来为你工作,你会希望这个人总是要受那些专家的支配吗?我不愿意.我想要的是一个能够自立的员工. 5. 与客户的互动式非常重要的,清楚地了解客户的要求比似懂非懂然后自己填写细节要好得多. 6. 学习行业是如何运

《图解tcp/ip》读书笔记(二)

<图解tcp/ip>读书笔记(二) 本周主要阅读的是本书的第三章--数据链路. 当然了,从某些角度讲,我认为这一章就是计算机网络的最基本的内容之一.整章讲述了数据链路层的作用和相关技术,主要描述了以太网.无线通信.ppp.公共网络以及其他的一些数据链路一些细节性的技术. 由于之前已经学习过相关计算机网络的课程,因此,就不再详细的记录整个阅读内容了,很多让我突破以前思维定式的一些知识,我挑一些列在下面. 其实这些知识花几分钟.几个小时,就可以得到,或者查阅互联网会得到比这本书更新的技术,但是,有

Android编程权威指南-读书笔记(二)-第一个小程序

Android编程权威指南-读书笔记(二) -第一个小程序 第一个例子介绍 应用名为GeoQuiz.用户通过单击True或False按钮来回答屏幕上的问题,GeoQuiz可即时反馈答案正确与否. 这个例子为我们简单介绍了几个基本组件的使用,以及基本的事件监听.让我们对基本组件的使用和事件的监听有一个基本的了解. 这篇文章分为2个部分,第一部分就是创建简单的UI.第二个部分就是对这个UI增加代码来响应一些操作. (注:所有不明白或者不理解的东西其实都不重要,后面都会有更详细的介绍.) 本章的目标

MySQL技术内幕-InnoDB存储引擎-读书笔记(二)

MySQL技术内幕-InnoDB存储引擎-读书笔记(二) 作为php开发,使用mysql总是少不了的 系列文章博客链接 http://itsong.net/articles/466.html 第三章 文件 mysql与innodb几个类型的文件 参数文件,配置路径.初始化参数.内存大小等 日志文件,包括错误日志,二进制日志,慢查询日志,查询日志 socket文件,用unix域套接字,unix domain socket来进行连接时需要的文件,这一般是本机连接,比通常tcp快 pid文件,进程id

Effective Objective-C 读书笔记

一本不错的书,给出了52条建议来优化程序的性能,对初学者有不错的指导作用,但是对高级阶段的程序员可能帮助不是很大.这里贴出部分笔记: 第2条: 使用#improt导入头文件会把头文件的内容全部暴露到目标文件中,而且如果两个类之间存在循环引用则会出现编译错误,所以要尽量使用@class进行类声明. 如果需要实现一个协议,则必须#improt这个协议的头文件,所以可以将协议单独定义在一个.h文件当中.如果这个协议是代理模式协议的一部分,即需要与类捆绑使用才有实际意义,则建议定义在类当中,并以类名为前

Effective Java 读书笔记(2创建和销毁对象)

第一章是引言,所以这里不做笔记,总结一下书中第一章的主要内容是向我们解释了这本书所做的事情:指导Java程序员如何编写出清晰.正确.可用.健壮.灵活和可维护的程序. 2.1考虑用静态工厂方法代替构造器 静态工厂方法与构造器相比有四大优势: (1)静态工厂方法有名称,具有适当名称的静态工厂方法易于使用.易于阅读: (2)不必每次在调用它们的时候都创建一个新的对象: (3)可以返回原返回类型的任何子类型的对象: (4)在创建参数化类型实例的时候,它们使代码变得更加简洁. 同时静态工厂方法也有两大缺点