2.子类先析构,父类再析构。子类析构函数不需要显示调用父类析构,子类析构后会自动调用父类析构
构造顺序:先父类,再子类;析构顺序:先子类,再父类。
5.继承与友元
友元关系不能继承!
若子类对象也想访问友元函数,那只能在子类中也加上友元!(但不建议使用友元,会破坏继承关系)
6. 继承与静态成员
子类继承父类,不是继承父类这个对象,而是会有一份父类的模型。父类有的成员变量,子类也会有一份,互不干扰。
但静态成员就不一样了,他们是同一份;静态成员属于整个类和类的所有对象。同时也属于所有派生类及派生类的对象。
class Person { public: //friend void Display(const Person& p, const Student& s); protected: string _name; // 姓名 public: static int _num; }; int Person::_num = 0; class Student : public Person { protected: int _stuNum; // 学号 }; void test() { Student s; Person p; cout << p._num << endl; p._num++; cout << p._num << endl; s._num++; cout << s._num << endl; cout << &s._num << endl; cout << &p._num << endl; }
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例
重点:
Person* ptr = nullptr; ptr->Print(); cout << ptr->_num << endl; cout << ptr->_name << endl; cout << (*ptr)._num << endl; (*ptr).Print();
对象里面只存成员变量,成员函数在代码段中,所以以上代码哪个不对呢?
我们知道空指针不能解引用,解引用意思是,这里是去访问指针指向对象的内部成员,那看一看哪个访问了内部的成员呢?
函数不在内部,在代码段,可以!
_num为对象内部成员变量,不能解引用访问,不可以!
(*ptr)是解引用了吗?我们不能凭借解引用符号来判断是否解引用,我们需要看内部的访问情况,(*ptr)->Print();并没有访问内部成员,可以!
(*ptr)->_num;也可以,_num是静态成员,不在成员里面。
7.多继承
7.1继承分类
单继承:一个子类只有一个直接父类
多继承:一个子类有两个或两个以上的父类
菱形继承:是多继承的一种特殊情况,会产生数据冗余和二义性!
(person类的中的成员,会在student和teacher中都有一份,assistant继承student和teacher时,assistant中会有两份person,造成了数据冗余和二义性)
解决方法:
解决二义性:
可以通过访问限定符来指定访问哪一个成员。
那如何解决二义性的问题呢?
此时虚继承就上线了!
虚继承在腰部继承,谁引发的数据冗余,谁就进行虚继承(解决冗余)
由此可见,加上virtual,变为虚继承以后,确实解决了数据的冗余
那么到底如何解决的呢??具体下面分析!
7.2 菱形继承 &&菱形虚拟继承
为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助 内存窗口观察对象成
员的模型。
1.解决二义性的过程(指定作用域)
(菱形继承)
class A { public: int _a; }; class B:public A { public: int _b; }; class C:public A { public: int _c; }; class D:public B,public C { public: int _d; }; int main() { D d; d._b = 1; d._c = 2; d._d = 3; d.B::_a = 4; d.C::_a = 5; }
2.解决数据冗余(需要虚继承)
(菱形虚拟继承)
class A { public: int _a; }; class B:virtual public A { public: int _b; }; class C:virtual public A { public: int _c; }; class D:public B,public C { public: int _d; }; int main() { D d; d._b = 1; d._c = 2; d._d = 3; d.B::_a = 4; d.C::_a = 5; }
那如果遇到这种情况呢???父子类的赋值转换(切片)
class A { public: int _a; }; class B:virtual public A { public: int _b; }; class C:virtual public A { public: int _c; }; class D:public B,public C { public: int _d; }; int main() { D d; d._b = 1; d._c = 2; d._d = 3; d._a = 4; d._a = 5; B b; b._a = 1; b._b = 3; B* ptr = &b; ptr->_a = 2; ptr = &d; ptr->_a = 6; }
从b对象可以看的出来,只要是虚继承以后,就会把虚基类放到最下面;
就像切片这种情况,ptr指向不同,那么距离虚基类的距离就不同,所以就必须要有虚基表的地址,来访问虚基表继而找到偏移量,然后访问到虚基类!
我们通常使用下,很忌讳出现菱形继承,但可以多继承。
可以看得出,虚继承在时间上确实有损耗,过程比较复杂,但是如果虚基类比较大时,就可以很大程度上节省内存。
8.继承和组合(都是一种复用)
public继承是一种 is-a(是一个)的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种 has-a(有一个)的关系。假设B组合了A,每个B对象中都有一个A对象。
我们会说低耦合高内聚有,意思就是相互的联系比较小,不会因为改动一个,而很大的影响另一个;
在组合中,两个类中的成员变量一般都是私有,那么就无法访问,那么修改也不会相互影响到;
在继承中,因为要继承,所以父类成员一般子类都可以访问的,那么修改的话,彼此相互影响就比较大!
那么组合其实就是很好的低耦合。
就比如我们平时举例说到的:person,student,这就是继承关系,学生是一个人;
那再举一个,头有一双眼睛(这就是组合)
事实上,哪个适合就用哪个,都适合就先用组合!
总结:
一口气说了这么多,你学会了吗?细节还是比较多的,我们应该下去多多自己琢磨,反复调试,去感受过程,从而理解的更深刻!下期再见!