// 12 什么时候使用私有继承,什么时候使用包含
/*
既然私有继承与包含的作用相同,为什么还要使用包含量呢?
这是因为对于程序员来说,包含易于理解,我们可以显式地在包含类中定义被包含类的对像,然后用该对像调用被包含类的成员函数,这会让程序员看上去非常清淅易懂,但私有继承私不这么直观,它使类之间的关系变得更加抽像,并且很多情况下,这种关系比较抽像和复杂,程序员必须处理很多继承带来的问题,比如说派生类与基类的函数重名问题,也就是我们前面讲过的两义性,或者继承一些不该继承的方法或变量,经如说派生类的某个基类上还有基类,那么这个基类的方法也会延续到后面的所有派生类中
显然,作为一个位地继承层次之外的类,包含类则不存在这样的问题,另外,包含类还可以包含另一个类的多个对像,但是私有继承不能这么做
既然包含有这么多的优点,那么为什么还要使用私有继承呢?这是因为私有继承有包含所没有的特殊属性,比如说私有继承的派生类与基类是继承与被继承的关系,我们知道公有继承的情况下基类的保护成员在派生类中也是保护成员,我们可以像在基类中一样访问它,私有继承的情况下基类的保护成员在派生类中是私有成员,我们可以在派生类中设置成员函数来访问它,但是包含与被包含却没有这层关系,由于所包含的对像位于继承层次之外,并且保护成员只对派生类开放,因此包含类不能通过所包含的对像来访问该对像的保护成员
私有继承的另外一个优势是使派生类可以重新定义基类的虚函数,这个被重新定义的虚函数只能在访我的中使用,它的私有成员身体不变,包含则没有这个特性
*/
//21 保护继承
/*
继承时使用关键字protected,如
class B:protected A{
}
类B从类B保护派生,这样,类A公有成员和保护成员在类B中都是保护成成员,类b可以像类a那样访问他们,类a的私有成员在类b中变成了不可访问,只能通过类a的接品函数来实现
基类的保护成员对于派生类而言,与自已的私有成员差不多,是可以通过公有函数进行访问,但是对于继承层次之外的类来说,它是不可访问的成员,因此,以保护的方式从基类派生出一个类后,基类的公有成员在派生类中变成了受保护的,对于继承层次之外的类来说它们是不可直接访问,但是对于派生类的子类来说是可以直接访问的,这样在加强代码的安全性的同时还有利于再次派生,但是私有继承则不行,第一次派生后公有成员变成了私有成员,再次派生后,私有成都员变成了不可访问成员,当所有成员都变成不可访问成员,那么再往下派生就没有什么意义了
下面的表总结了公有、私有和保护继承的不同
变化 公有继承 保护继承 私有继承
基类的公有成员变成 派生类的公有成员 派生类的保护成员 派生类私有成员
基类的保护成员变成 派生类的保护成员 派生类的保护成员 派生类私有成员
基类的私有成员变成 只能通过基类的接口访问 只能通过基类的接口访问 只能通过基类的接口访问
*/
#include <iostream>
using namespace std;
class A
{
public:
void Set(int a, int b){ x=a; y=b; }
void Show(){ cout<<"x:"<<x<<endl; cout<<"y:"<<y<<endl; }
protected:
int x;
private:
int y;
};
//class B: public A
//{
//public:
//void Set(int a, int b){ x=a;
//y=b; 私有成员在派生类中变成了不可访问
//}
//void Show(){ cout<<"x:"<<x<<endl;
//cout<<"y:"<<y<<endl; 私有成员在派生类中变成了不可访问
//}
//};
//class B : protected A
//{
//public:
//公有成员变成了保护成员,只能通过派生类的公有成员来访问基类的保护成员
//void SetB(int a, int b){ Set(a,b); }
//void ShowB(){ Show();}
//};
//class B : private A
class B : protected A
{
public:
void SetB(int a, int b){ Set(a,b); }
void ShowB(){ Show();}
//当类a被类b私有继承时,类a中的所有公有成员和所有保护成员将同时成为类b中的私有成员,类a中的所有私有成员成为类b中不可直接访问的成员,假如类b不再往下派生,那么私有成员与保护成员是相同的
//关于私有成员是这样写的: "只允许来自类内部的访问,不允许任何来自该类外部的访问"
//派生类也是不允许的,保护成员则放宽了一条,可以允许派生类进行访问,因此,私有成员与保护成员的区别就在于派生类中,也就是说假如不继承往下派生,那么保护继承后的类b与私有继承的类b没有区别
};
class C: public B
{
public:
void SetC(int a, int b){ Set(a,b); }
void ShowC(){ Show();}
};
//公有继承与保护继承和私有继承都有所不同,
//公有继承后访问权根维持不变,基类的公有成员被派生类继承后还是公有的,基类的保护成员被派生类继承后是受保护的
//对于私有成员来说呢,由于它的特性,只允许来自类内部的访问,不允许任何人来自访类外部的访问,所以无论是以公有,私有还是保护的方式进行继承,在子类中都不可访问的,不过子类可以用父类的函数来操作他们,假如父类没有提供这样的函数,那么就不能访问了
//这样设计有什么意义呢?我们可以用这种方式将我们的成员保护得更好,让子类的设计者只能通过父类的方法修改父类的私有成员,假如父亲没有提供,那么就不允许修改,这样将会把类保护得更好,这对于维护一个完整而复杂的继承体系是优为重要的
int main()
{
B b;
b.SetB(1,2);
b.ShowB();
return 0;
}
//总结
/*
本章再以面向对像的方式重写了这个链表
本章的链表更楞灵活,代码更加健壮,更容易维护和重复利用
由于这么说大家不容易理解,举一个生活中的例子,大家都用过电视机和电脑,都知道操作电视机的同时不会影响到电脑,比如说你操作电视机的时候不会删除电脑中的文件,而删除电脑文件也不会关闭电视机
之所以这样,是因为它们是互不相干的单独个体,数据也不是共享的,这就是面向对像的一个比较形像的体现,它会将有相同或相似性质的对像划分成一类,比如说电视机对像刬分到电视机类中,将电脑这个对像分到电脑这个类中,每个对像都拥有自己的数据,比如说电视机这个对像有音量,频道,色彩等
而电脑则拥有视频,图片,音乐等数据,电脑不能干预电视机的数据,电视机也不能干预电脑的数据
但是换成以面向结构的方式来高驻地电视机和电脑的话,那么就全变了,结构化方法的本质是功能分解,它只将复杂的功能分解成若干可以实现的小功能,所以在它的眼里,没有电视和电脑,只有一个个小的螺丝钉,或者集成电路
由于不能将电视机和电脑分开,所以也不能区分某个螺丝钉是电脑的还是电视机的,这样的缺陷是显而易见的,程序员在设计一个螺丝钉或者集成电路的时候是非常得心应手的,但是在批量生产电视机或电脑的时候就晕头了,面对大批的螺丝钉和集成电路,如果将它们组装成电视机和电脑是一件令人漰溃的事情,同时由于结构化方法无法区别电视机和电脑的方法,数据也只有共有,这样电视机可以何意访问和修改电脑的数据,电脑也可以何意访问和修改电视机的数据,
一旦程序的某个地方出错,就会产生无法预料的后果,这些错误往往是不易发现和纠正的,程序员往往要花费大量的时候和精力来纠正这些错误,由此可见结构化方法开发的软件,基稳定性和可维护性是相当关的
结构化编程的另外个缺陷是可重用性差,假如有一种新式电视机需要高设计,面向对介编辑只需要将源来的电视机类继承过来,再给它添加几个新功能就可以了,但是对于面向结构化来说,由于它不能将电视机看做是一个整体,一个对像,它只能将电视机分解成一堆螺丝,集成电路和显像管,因此这对于他来说这可是一件非常复杂的差使,它需要重新组合这些螺丝,集成电路和显像管,必要的情况下甚至需要重新设计这些零件,这还不是最坏的,假如电板又叫你生产
一台老式的电视机,那么这些代码又得重写了,代码的可重用性非常差
结构化编辑的第三个缺陷是不能实现多态,对比第15章的链表,本章为链表增加了一个抽像类Date来演示多态的作用,有了抽像类Date做接口,主可以何意派生出多个类
实例中派生了两个类,一个是图书类,一个是药品类,这两个类都覆盖了基类中的输出孙函数函数print,只不过图书类的print输出的是图书的数据,而药口的print函数则用于输出药品的数据,多态的特性是一个同样的函数对于不同的对像可以不同的实现,在程序运行时演示了多态的这种特性,虽然执行的都是同一个方法print()但是当用户选择图收时,则输出图书的数据,而选择药品时,则输出药品的数据,这对于结构化编辑来说是不可想像的
*/