关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

【C++】你不得不爱的——继承(二)

发布时间:2023-06-29 13:00:40

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,这就是继承关系,学生是一个人;

那再举一个,头有一双眼睛(这就是组合)

事实上,哪个适合就用哪个,都适合就先用组合!

总结:

一口气说了这么多,你学会了吗?细节还是比较多的,我们应该下去多多自己琢磨,反复调试,去感受过程,从而理解的更深刻!下期再见!


/template/Home/leiyu/PC/Static