6.面向对象三大特性---继承

一.概述

1.使用继承的目的

  ·继承就是为了实现软件工程的目的:高类聚,低耦合

  ·高类聚:任何一个类,让它的成员尽量多,即把内容关联度高的放在一起的放在一起

  ·低耦合:不同的类之间重合少,即降低每个功能模块之间的关联度,互相依赖少

2.用两种方式看待类之间的关系

  以马来举例。

  2-1 集合思想:白马、黄马都是马。马包含白马、黄马

  

 

  2-2 数据思想:不管是白马还是黄马,除了是马以外,还有自己的特性(比如颜色)。白马包含马,黄马也包含马,相当于数学中的交集

  

 

3.基类与子类

  继承就是使用数据思想去看待基类(马)与子类(白马、黄马)

  ·基类:大的分类,少的共同特性

  ·子类(派生类):小的分类,多的特性

 

二.继承于派生

1.派生类继承基类的方式

   ·class 派生类名:继承方式 基类类名 {  };

   ·举例:class B : public A {  };

2.派生类是怎么去继承基类的成员变量与成员函数的

   ·成员变量

    (1) 派生类对象中包含基类对象

      ·即在构造派生类对象时,会先构造出一个基类对象。派生类大小 = 基类所有成员变量大小 + 派生类所有成员变量大小。

    (2) 成员变量由偏移量决定

      ·偏移量,指的是一个地址段到另一个地址段之间的距离

      ·构造出的对象,其实就是一个内存段,对象就相当于是保存了一个内存段首地址。

      ·成员变量在编译器存储的方式是,每个成员变量相对于对象首地址的偏移量,即成员变量在类中的位置是,对象首地址+偏移量。

  ·成员函数

    (1) 拷贝偏移量:同成员变量

    (2) 类型决定代码段中访问那个函数

class A
{
  int a;
public:
  A()
  {
    a = 1;
  }
  void show() 
  {
    printf("A:%d\n",a);
  }
};

class B
{
  int b;
public:
  B() 
  {
    b = 2;
  }
  void show() 
  {
    printf("B:%d\n",b);
  }
};


int main()
{
  A a;
  B* b = (B*)&a;

  b->show();
  cout << "对象a的首地址" << &(a) << endl;
  cout << "对象b的首地址" << b << endl;
  cout << ""   
while(1);   return 0; } //调用的是B类的show,显示的是A类变量a的值 //B::1

//1.函数调用:对象b的类型是B类,所以调用的B类的show()。可以理解为函数是与变量类型绑定的
//2.变量调用:对象b保存的首地址是对象a的,由于变量int b;在B类中的偏移量是0,而变量int a;在A类中的偏移量也是0,
    在类中访问成员变量的方式为首地址+偏移量,故最终得到的是变量int a;的值

3.继承中的封装

   ·封装的三种方式

    private:只有本类对象的成员函数可以访问类中用private修饰的函数与变量
    protected:派生类对象的成员函数可以访问基类中用protected修饰的函数和变量
    public:类中,类外都能访问用public修饰的函数和变量

  ·对象中的封装

    在类中的封装,限制成员变量与函数,在本类外的访问权限

  ·继承过程中的封装

    稳妥为主,限制基类成员变量与函数,在基类之外的访问权限

    举例:基类中使用的private,派生类继承的方式是public,那么结果为private

         基类中使用的public,派生类继承的方式是private,那么结果同样为private

4.继承中的构造与析构

   ·构造

    派生类对象构造过程:先调用基类构造器,再调用派生类构造器

    如果希望构造派生类对象中继承自基类对象的成员变量,必须调用基类构造器

class A
{
    int a;
public:
    A(int x):a(x){};
}

class B
{
    int b;
public:
    //A(x),就是调用A类的构造函数
    B(int x,int y):A(x),b(y){};
}

  ·析构

     派生类对象析构过程:与构造相反,先调用派生类析构器,再调用基类析构器

5.多重复杂继承

   ·继承的三大原则

    一个类可以被多个类继承,一个类也可以继承自多个类,自己不能继承自己

  ·多重复杂继承的经典例子:菱形继承

    图解:

    

    示例:

//基类A
class A
{
    int a;
public:
    A(int x):a(x){};
};

//派生类B
class B :public A
{
    int b;
public:
    B(int x,int y):A(x),b(y){};
};

//派生类C
class C :public A
{
    int c;
public:
    C(int x,int y):A(x),c(y){};
};

//派生类D
class D :public A
{
    int d;
public:
    D(int x,int y):A(x),d(y){};
};


//派生类E
class E :public B, public C, public D
{
    int e;
public:
    E(int a,int b,int c,int d,int f):B(a,d),C(b,d),D(c,d),e(f){};
};

int main()
{
  E e;
  /*
   此时对象e中有以下这些成员变量
    e
    B::A::a
    B::b
    C::A::a
    C::c
    D::A::a
    D::d
  */
  return 0;
}

6.虚继承

   ·目的:只是为了节约一个对象

    举例:有一个A类,B类继承A类,C类也继承A类,D再继承B类和C类。

    类D继承自类B和类C,而B类和C类都继承自类A,类D中会两次继承A,为了节省空间,可以将B、C对A的继承定义为虚拟继承,而A就成了虚拟基类,B、C就都使用这个虚拟基类,不用再去构造A类了。

  ·virtual 关键字修饰继承过程

class A
{
    int a;
public:
    A(int x):a(x){};
};

class B :virtual public A
{
    int b;
public:
    B(int x,int y):A(x),b(y){};
};

  ·虚继承的继承过程提前:先构造出虚继承的类,为了让其他类在继承的时候直接使用

class A
{
public:
    A()
    {
        cout << "A类构造" <<endl;
    }
};

class B
{
public:
    B()
    {
        cout << "B类构造" <<endl;
    }
};


class C:public A,virtual public B
{
public:
    C()
    {
        cout << "C类构造" <<endl;
    }
};

//正常流程:
A类构造->B类构造->C类构造

//virtual修饰后流程:B类构造->A类构造->C类构造

  ·继承同一个类的时候都是虚继承,则共用同一个基类构造器

    通过Virtual Studio编译器的监视,可以发现,在delete d;之后,A类对象是无法访问内存的,间接说明了虚拟出来的类,只是临时存在的。

//基类A
class
A { int a; public: A() { a = 1; cout << "A类构造" <<endl; } };
//派生类B,虚继承
class B:virtual public A { int b; public: B() { b = 2; cout << "B类构造" <<endl; } }; //派生类c,虚继承 class C:virtual public A { int c; public: C() { cout << "C类构造" <<endl; } };
//派生类D
class D:public B,public C { int d; public: D() { cout << "D类构造" <<endl; } };

int main()
{
  D* d = new D();
  delete d;
  
  return 0;
}

//正常流程: A类构造->B类构造->A类构造->C类构造->D类构造

//virtual修饰后流程:A类构造->B类构造->C类构造->D类构造

 

posted @ 2020-06-16 16:42  打好基础才是第一步  阅读(215)  评论(0)    收藏  举报