【c++】继承(2)
hello~ 很高兴见到大家! 这次带来的是C++中关于模板进阶这部分的一些知识点,如果对你有所帮助的话,可否留下你宝贵的三连呢?
个 人 主 页: 默|笙
文章目录
- 一、继承与静态成员
- 二、多继承与菱形继承问题
- 1. 单继承和多继承
- 2. 菱形继承
- 3. 虚拟继承
- 4. 多继承中指针偏移问题
- 三、继承和组合
继上次的继承章节—继承1
一、继承与静态成员
- 若基类定义了静态成员,那么整个继承体系之中也就只会有一个这样的成员。无论派生出多少个类,都只会有一个static成员实例。
class Person
{
public:string _name;static int _count;
};int Person::_count = 0;//可以通过类域访问静态成员class Student : public Person
{
protected:int _stuNum;
};int main()
{Person p;Student s;cout << "普通成员变量" << endl;cout << &p._name << endl;cout << &s._name << endl;cout << "静态成员变量" << endl;cout << &p._count << endl;cout << &s._count << endl;return 0;
}
- 可以发现,对于普通成员变量_name,对象p和对象s的_name变量地址是不相同的,它们不会共用_name,而对于静态成员变量_count,对象p和对象s的_count变量地址是相同的,这说明这两个对象是共用同一个_count的。无论再由基类继承多少个派生类,在这一个继承体系里面都是共用同一个静态成员变量_count的。
二、多继承与菱形继承问题
1. 单继承和多继承
- 单继承:一个派生类只有一个直接基类时称这个继承关系为单继承。
- 多继承:一个派生类有两个及以上的直接基类时称这个关系为多继承。多继承对象在内存中的模型是:先继承的在前面,后继承的在后面,派生类成员放到最后面(具体会在指针偏移问题处给出例子)。
- 在uml图里,继承关系表示是由派生类指向基类。
- 如上图,对于多继承,就好比学生与老师和助教之间的关系,助教拥有双重身份,所以Assistant既需要继承Student也需要继承Teacher。也如同西红柿,西红柿既可以看作蔬菜,也可以看作是一种水果。
2. 菱形继承
- 菱形继承是多继承的一种特殊情况。从下图可以看出Assistant既继承了Student也继承了Teacher,而Student和Teacher都继承了同一个基类Person,那么相应的Assistant类就会继承两个Person里的成员,会有数据冗余和二义性的问题。
- 关于数据冗余:比如Assistant里面就包含了多份Person成员。关于二义性:在通过Assistant对象访问Person里的成员时,编译器无法分清需要访问·的是Student里的,还是Teacher里的。
3. 虚拟继承
-
数据冗余和二义性这两个问题,在虚拟继承之前,二义性可以通过指定类域来解决问题,但是数据冗余不行。而一旦解决了数据冗余的问题,二义性的问题也将会被解决。
-
有多继承,就不可避免的会存在菱形继承,想要解决菱形继承的问题,就需要解决菱形继承所带来的数据冗余和二义性的问题。为此虚拟继承出现了,它的关键字是virtual。
-
虽然通过虚拟继承解决了菱形继承的问题,但是它的底层实现非常复杂,它会造成一些无法避免的损失。从这个点也可以看出,多继承算是c++的一个缺陷,在之后的一些编程语言里就没有多继承比如java。
-
什么时候用到关键字virtual–>在会出现数据冗余的基类(这个基类在后面的派生类会出现多个)被继承的时候在派生类的继承方式前添上virtual关键字(如class Student : virtual public Person)。
- 虚拟继承其实就是把Person这个类单独拿出来,Person不再在Student和Teacher类里面。这样就能够保证只会有一个Person。
- 尽量不要设计出菱形虚拟继承,它的底层会非常的复杂,使用也会变得复杂。
- 菱形虚拟继承的应用:在IO库里面有使用到。
4. 多继承中指针偏移问题
- 接下来我们观察一段代码:
class Student { public: int _b1; };
class Person { public: int _b2; };
class Assistant : public Student, public Teacher { public: int _d; };
int main()
{Assistant d;Student* p1 = &d;Teacher* p2 = &d;Assistant* p3 = &d;return 0;
}
- p1、p2、p3都指向哪里?
- d的地址存储到基类指针变量里面时,会有一个偏移量,d会先偏移一定位置使得指针指向对应的基类子对象。
三、继承和组合
- 组合:它表示一个类(整体)是由其他类(部分)对象共同组成的。组合是一种has-a的关系,假设B组合了A,那么每一个B对象里面都会有一个A对象。就像stack的实现可以在类里面放置一个vector对象。
- 继承是一种 is-a 的关系,可以理解为它的每一个派生类对象都是一个特殊的基类对象。
- 就好比可以说宝马是车,但不能说宝马有车。而像栈和顺序表之间,既可以认为栈是特殊的顺序表,也可以认为是栈里有顺序表,这种情况下继承和组合都可以使用。
- 通过继承生成派生类的复用一般被成为白箱复用(在继承关系中,基类的内部细节对派生类可见),这在一定程度上破坏了基类的封装特性(基类的公有和保护成员都可以被派生类使用),倘若改变基类的成员,那么派生类一般都会受影响,它们之间有很强的依赖关系,耦合度比较高。
- 对象组合要求被组合的对象要拥有良好定义的接口,这一般被成为黑箱复用(对象的内部细节通常都是不可见的)。被组合的类仅能使用黑箱暴露出来的接口(公有成员),改变对象所在的类,被组合的类不一定会受到影响,相比于继承的白箱复用,这种复用的耦合度比较低。
- 在继承和组合都可以使用的情况下,优先使用组合,但在需要实现多态的情况下,还是得用继承。组合相比于继承,它的耦合度更低,代码维护性更好。
今天的分享就到此结束啦,如果对读者朋友们有所帮助的话,可否留下宝贵的三连呢~~
让我们共同努力, 一起走下去!