文章目录
关键字protected(带来方便同时带来危险,最好不用)
关于访问控制,即访问类的私有数据成员,之前一直是只用private和public来控制的。现在介绍一个新工具,关键字protected,但他并不是一个多好的东西,有利有弊,是一把双刃剑,使用双刃剑一定要十分小心,很容易杀敌一万自损八千,其实最好不要用这个关键字。下面仔细解释我说的这些。
private数据成员,外部不可以访问,必须通过类的公有接口访问。所以上篇文章写银行账户取款方法和存款方法的时候,访问账户余额必须要通过公有方法Balance()才可以访问到私有数据成员balance,看似麻烦(要多写一个返回私有数据成员的值的方法),实则安全。因为银行账户类的设计,希望账户余额绝对安全,只能让存款取款方法访问和改写余额的值,这样,余额不可能被其他方法修改,是很安全的。
现在有了关键字protected,我们为了方便,不想写Balance()方法,于是干脆把余额设为保护成员,而非私有成员
protected:
double balance;
保护成员的双重身份:
保护成员对于外部世界和私有成员一样,不可以被直接访问,必须通过类的公有接口;
但是对于派生类,以及派生链条上的所有类,保护成员却和公有成员一样,任何派生类都可以直接访问他们。
对于我们的程序,在plus会员派生类中确实不需要写Balance()方法访问balance成员了,可以直接在派生类的方法中访问,方便了,但也危险了,因为派生类的任何方法都可以修改余额,派生类的派生类,派生链条上的所有类,的公有方法都可以随意修改余额。。。你敢把钱存到这家银行吗?
所以protected是一个给成员函数偷懒的好工具,但是如果不是脑子里很清楚被派生类随意访问并无大碍,那就不要用保护成员,乖乖设置为私有成员(C++的开创者Stroustrup都是这么建议的),然后写一个返回其值的公有方法,并不很费神,但百分百安全,毫无后顾之忧,何乐而不为呢?
balance是私有成员,用Balance()访问,下面是派生类取钱方法的代码,要用double bal = Balance();,如果balance是保护成员,则后面都直接用balan
ce了
void BrassPlus::Withdraw(double amt)//虚方法
{
format initialState = setFormat();
precis prec = std::cout.precision(2);
double bal = Balance();
if (amt < 0)
std::cout << "Withdraw amount must be positive!\n"
<< "Withdraw cancelled!\n";
else if (amt <= bal)//不可写amt <= balance,因为不能直接访问基类数据成员
Brass::Withdraw(amt);//不可写balance -= amt;
else if(amt - bal + owesBank <= maxLoan)
{
double advance = amt - bal;
owesBank += advance * (1.0 + rate);//rate是BrassPlus类的私有成员
std::cout << "Bank advance: $" << advance << '\n';
std::cout << "Finance charge: $" << advance * rate << '\n';
//分两步实现扣除账户全部余额(由于基类不允许取款金额超出余额,所以只能先存再取)
Deposit(advance);//放贷
Brass::Withdraw(amt);
}
else
std::cout << "Credit limit exceeded. Transaction cancelled.\n";
restore(initialState, prec);
}
抽象基类和纯虚函数(is-a关系用公有继承实现有时候也不太合适)
前面说了有三种继承——公有继承,私有继承,保护继承。
但是目前还是一直在讨论公有继承。
我们说到基类和派生类的五种常见关系:is-a, has-a, is-like-a, uses-a, is-implemented-by-a,其中只有is-a用公有继承来实现是比较好的,其他四个关系都不适合公有继承。
但是现在我们又要说,is-a关系和公有继承之间也不是百分百合拍,完美和谐。有时候,is-a关系不能简单使用公有继承,否则麻烦不断后患无穷,总之不是好的设计。
用圆和椭圆的笨拙派生为例,挑拨is-a和公有继承的搭档关系
数学上,圆是一种特殊的椭圆,是椭圆的一个特例,特殊在于长半轴和短半轴长度相等。于是满足is-a关系,于是我们使用公有继承,从Ellipse类派生出Circle类。
Ellipse类声明,私有数据成员包括位置坐标,长短半轴长度,角度。方法有移动,旋转,缩放。
由于Circle类也要用这三种方法,所以都设置为了虚函数。
由于Ellipse类要成为基类,所以设置了虚析构函
数。
//ellipse.h
#ifndef ELLIPSE_H_
#define ELLIPSE_H_
class Ellipse{
private:
double x;//位置横坐标
double y;//位置纵坐标
double a;//长半轴
double b;//短半轴
double angle;//x轴和长轴的夹角
public:
Ellipse(double nx = 0.0, double ny = 0.0, double na = 1.0, double nb = 1.0, double ang = 0.0):x(nx), y(ny), a(na), b(nb), angle(ang){
}
virtual ~Ellipse(){
}
virtual void Move(double mx, double my){
x = mx;y = my;}
virtual void Rotate(double ang){
angle += ang;}
virtual void Scale(double sx, double sy){
a *= sx; b *= sy;}
};
#endif // ELLIPSE_H_
于是Circle类的声
明是
//circle.h
#ifndef CIRCLE_H_
#define CITCLE_H_
class Circle : public Ellipse
{
public:
Circle(double nx, double ny, double a, double b):x(nx), y(ny), a(r