Item22:将成员变量声明为private
成员变量应该是private的
为什么不采用public成员变量?
- 一致性。所有的成员变量都提供访问接口,一个类对外提供的形式就只有函数形式
- 使用函数可以让你对成员变量处理有更加精确的控制。可读、可写、读写等
- 封装性。当我们有了这个类更好的实现时,而且成员变量都是private的,我们就可以在不破坏客户代码的前提下更换为更好的实现;成员变量在被读写时轻松的通知其他对象;可以验证class的约束条件;可以在多线程中提供同步控制(加锁)等
某种东西的封装性与“当其内容改变时可能造成的代码破坏量”呈反比
protected变量的封装性是不是要比public的要好呢?答案是不一定。当public成员变量被修改,所有使用了它的客户代码破坏;当protected成员变量被修改,所有使用了它的derived class代码被破坏,而后进一步延伸
成员变量只有两种权限:private(封装)和非private(不封装)
总结:
- 切记将成员变量声明为private。这可以提供客户访问数据的一致性、可细微划分访问控制、允许约束条件获得保证、提供class作者以充分的弹性实现
- protected并不比public更具有封装性
Item23:宁以non-member、non-friend函数替换member函数
//一个class用来表示网页浏览器 有的函数清理缓存 有的清理记录 有的清理cookie
class WebBrowser{
public:
...
void clearCache();
void clearHistory();
void removeCookie();
};
//很多用户想要一整个执行上述操作 两种方法member、non-member
class WebBrowser{
public:
...
void clearCache();
void clearHistory();
void removeCookie();
void clearEverything();
};
void clearEverything(WebBrowser &wb){
wb.clearCache();
wb.clearHistory();
wb.removeCookie();
}
//哪种方法好呢?
non-member、non-friend函数更好,member函数带来的封装性要比non-member、non-friend函数低:
-
封装性:愈少的人看到他我们就有愈大的弹性去变化它,这是我们推崇封装性的原因,它使我们能够改变事物而只影响有限客户。考虑对象内的数据,越多的函数能够访问到它,说明它的封装性越差。增加non-member、non-friend函数不会降低对象内数据的封装性!!
- 这个论述只适用于non-member且non-friend函数 member和friend函数对class的private成员访问的权力是一样的
- non-member、non-friend函数可以变成另一个类的member函数
-
编译相依
C++的做法是让class WebBrowser和non-member函数位于同一个namespace中
namespace WebBrowserStuff{ class WebBrowser{ public: ... void clearCache(); void clearHistory(); void removeCookie(); }; void clearEverything(WebBrowser &wb){ wb.clearCache(); wb.clearHistory(); wb.removeCookie(); } }
namespace和class不同,namespace可以跨文件而class不能
我们的namespace std不只有一个头文件<C++ standard library>,而是一共有几十个头文件,因为我们想用到vector就不需要queue等库,这样降低了编译相依
一个WebBrowser这样的class可能有大量的便利函数clearEverything,有些与书签相关、有些与cookie相关等,一个只与书签相关的便利函数不需要与cookie相关的便利函数发生编译相依。分离他们的直接做法就是放在不同的头文件中
//头文件webbrowser.h namespace WebBrowser{ class WebBrowser{ public: ... void clearCache(); void clearHistory(); void removeCookie(); }; } //头文件webbrowserbookmarks.h 书签相关的便利函数 namespace WebBrowser{ } //头文件webbrowsercookies.h cookies相关的便利函数 namespace WebBrowser{ } //把不同的non-member便利函数放在不同的头文件中可以降低编译相依
-
机能扩充性
这种方法切割不能用于class中的member函数,因为class是一个整体,不能被切割放在不同的头文件
将所有便利函数放在同一个命名空间中,意味着客户可以轻松扩展这一组便利函数,他们要做的就是在同一命名空间下建一个不同的头文件。比如WebBrowser中放一个下载相关的便利函数,只需要在WebBrowser的命名空间中新建一个下载相关的头文件。这样做对于class是不行的,因为class对于客户是不能修改的!!
总结:
- 宁可拿non-member non-friend函数代替member函数。这样做可以增加封装性、包裹弹性和机能扩充性
Item24:若所有参数皆需要类型转换,请为此采用non-member函数
当我们想要设计一个class表示有理数, 显然ints转化为这个有理数的隐性转换是合理的,所以构造函数是non-explicit的
//一个表现有理数的class
class Rational{
private:
int n, d;//n分子 d分母
public:
Rational(int numerator = 0, int denominator = 1) : n(numerator), d(denominator){}
int n() const;
int d() const;
};
现在我们想要实现operator*的方法,应该是member函数还是non-member呢?首先我们假定为member函数
//一个表现有理数的class
class Rational{
private:
int n, d;//n分子 d分母
public:
Rational(int numerator = 0, int denominator = 1) : n(numerator), d(denominator){}
int n() const;
int d() const;
const Rational operator*(const Rational &rhs) const;
};
Rational oneEight(1, 8);
Rational oneHalf(1, 2);
Rational result = oneEight * oneHalf;
result = result * oneEight;//正确
result = oneHalf * 2;//正确
result = 2 * oneHalf;//错误
上述语句等同于
result = oneHalf.operator*(2);//正确
//实际执行
//const Rational temp(2); //隐式转换
//result = oneHalf * temp;
result = 2.operator*(oneHalf);//错误
结论:只有当参数被列于参数列中,这个参数才是隐式转换类型的合格参与者 ,被调用的成员函数所隶属的那个对象(this对象)绝不是隐式类型转换的合格参与者
所以我们应该用non-member函数来写operator*
//一个表现有理数的class
class Rational{
private:
int n, d;//n分子 d分母
public:
Rational(int numerator = 0, int denominator = 1) : n(numerator), d(denominator){}
int n() const;
int d() const;
};
const Rational operator*(const Rational&lhs, const Rational &rhs);
Rational oneEight(1, 8);
Rational oneHalf(1, 2);
Rational result = oneEight * oneHalf;
result = result * oneEight;//正确
result = oneHalf * 2;//正确
result = 2 * oneHalf;//正确
那么这个operator*需要是friend函数吗?本例不需要,因为它用的是Rational的public接口
所以一定记住member对立面是non-member,而不是freind,不要想着某个与类相关的函数不是member的,就应该是freind的!!!无论何时如果你能避免friend函数就应该避免,因为现实中朋友带来的便利往往少于带来的麻烦
当从objectivec++迈向templatec++时,这个条款就不一定适用了
总结:
- 如果你需要为某个函数的所有参数(包括this指向的隐喻参数)进行类型转换,那么这个函数必须是non-member的