条款23和条款24

2022/1/5 6:09:16

本文主要是介绍条款23和条款24,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

条款23:宁以non-member、non-friend替换member函数
为什么要用非成员函数、非友元函数替换成员函数呢?其实这是为了保证数据的封装性。而数据的封装性强弱是怎么体现的呢?一种粗糙的量测,我们认为越多的函数能访问它,数据的封装性就越低,因为如果数据发生改变,因它的改变牵扯到需要改变的太多,所以它的封装性较差。
而非成员函数、非友元函数较成员函数而言,访问不到private中的数据,数据的封装性自然就更好一些。也就是说,越多的函数可以访问,数据的封装性就越低。

需要注意的是,只因在意封装性而让函数“成为class的non-member”,并不意味它“不可以是另一个class的member”。该函数可以成为另一个类的static member函数,只要不影响原类中private成员的封装性。

看一下书中的例子:

class WebBrowser {
public:
	void clearCache();
	void clearHistory();
	void removeCookies();
}

现在有这样一个类,类中提供三种方法用来清除web浏览器的一些缓存。如果我们想一起清除Cache、History、Cookies,我们可以将这三个函数放在一个成员函数中去做,这样每次只需调用一个函数就行。

class WebBrowser{
public:
	void clearEverything(); //调用clearCache(),clearHistory(),removeCookies()
}

clearEverything()是一个成员函数,条款中不是说宁以non-member、non-friend替换member吗?因此,可以这样做:

void clearBrowser(WebBrowser &wb) {
	wb.clearCache();
	wb.clearHistory();
	wb.removeCookies();
}

让它成为一个非成员函数。
而在c++中我们通常可以将类和有关该类的非成员函数放在同一命名空间中,就像这样:

namespace WebBrowserStuff {
	class WebBrowser {...};
	void clearBrowser(WebBrowser& wb);
	...
}

同时,与WebBrowser类有关的便利函数,可能有多个,以及多个种类,比如,与cookie相关的,还有与书签相关的等等。为了减少没必要的依赖(比如,我现在只想用与cookie相关的函数,也就没必要引入与书签相关的函数),我们可以将他们分别声明在不同的头文件中,但都属于WebBrowserStuff这一命名空间,因为,他们都是与WebBrowser有关的。

//头文件“webbrowser.h”这个头文件针对class WebBrowser自身及WebBrowser核心机能。
namespace WebBrowserStuff {
	class WebBrowser {...};
	... 	//核心机能,例如几乎所有客户都需要的non-member函数
}

//头文件“webbrowserbookmarks.h”
namespace WebBrowserStuff {
	...		//与书签相关的便利函数
}

//头文件“webbrowsercookies.h”
namespace WebBrowserStuff {
	...		//与cookie相关的便利函数
}

将便利函数放在多个头文件中但都属于同一命名空间,意味着我们可以轻松的在此基础上增加其他的便利函数,这也是命名空间较class好的一点,class是一个整体,对于用户来说不能扩展,而命名空间可以。

条款24:若所有参数皆需类型转化,请为此采用non-member函数
现在我们定义一个有理数的类:

class Rational {
public:
	Rational(int numerator = 0, int denominator = 1);
	int numerator() const;
	int denominator() const;
private:
	...
}

有理数相乘似乎是很正常的事,为此,在类中重载一下乘法运算:

class Rational {
public:
	...
	const Rational operator* (const Rational &rhs) const;
}

这样便可支持两个有理数相乘:

Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneEighth;
result = result * oneEighth;

让有理数和整数相乘似乎也是很正常的事,当我们执行:

result = oneHalf * 2;//正确
result = 2 * oneHalf;//错误

可以发现,其中一个报错,这是因为,oneHalf是一个内含operator*函数的class的对象,所以编译器调用该函数。而整数2没有相应的class,也就没有operator*成员函数。编译器尝试非成员函数operator*(也就是在命名空间内或在global作用域内),然而其他地方也没有相关定义,因此报错。再来看result = oneHalf * 2,这个为什么正确呢?其实这里涉及到一个隐式的类型转换,即2通过构造函数转换为一个Rational类型,两个Rational类型相乘,自然没有报错。
只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者,正如result = oneHalf * 2一样。对于出错的情况,我们可以重载一个非成员函数的乘法运算:

class Rational {
	...
};
const Rational operator*(const Rational& lhs, const Rational& rhs) {
	return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}

其实上述两种重载方式称为类内重载和类外重载,当两种重载方式同时存在时,编译器会优先使用类内重载,因为类内重载对于类的使用者来说是优先可见的,因此它的优先级更高(对于类的使用者而言,优先看到的肯定是意图中想用的)。



这篇关于条款23和条款24的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程