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

简介: 【C++】你不得不爱的——继承

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

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

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

总结:

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

目录
相关文章
|
4月前
|
编译器 C++
【C++】详解C++的继承
【C++】详解C++的继承
|
1月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
80 11
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
53 1
|
1月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
39 1
|
1月前
|
安全 编译器 程序员
C++的忠实粉丝-继承的热情(1)
C++的忠实粉丝-继承的热情(1)
18 0
|
1月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
29 0
|
1月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
32 0
|
2月前
|
C++
C++(二十)继承
本文介绍了C++中的继承特性,包括公有、保护和私有继承,并解释了虚继承的作用。通过示例展示了派生类如何从基类继承属性和方法,并保持自身的独特性。此外,还详细说明了派生类构造函数的语法格式及构造顺序,提供了具体的代码示例帮助理解。
|
2月前
|
C++
c++继承层次结构实践
这篇文章通过多个示例代码,讲解了C++中继承层次结构的实践应用,包括多态、抽象类引用、基类调用派生类函数,以及基类指针引用派生类对象的情况,并提供了相关的参考链接。
|
3月前
|
安全 Java 编译器