继承

继承:类的继承,就是新的类从已有类那里得到已有的特性。原有的类称为基类或父类,产生的新类称为派生类或子类。

基本语法

派生类的声明:

class 派生类名:继承方式 基类名1, 继承方式 基类名2,...,继承方式 基类名n
{
派生类成员声明;
};

在 c++ 中,一个派生类可以同时有多个基类,这种情况称为多重继承。如果派生类只有一个基类,称为单继承。派生类继承基类中除构造和析构函数以外的所有成员。

类的继承方式


继承方式规定了如何访问基类继承的成员。继承方式有public, private, protected。如果不显示给出继承方式,默认为private继承。继承方式指定了派生类成员以及类外对象对于从基类继承来的成员的访问权限。

  • 公有继承
    当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问。即基类的公有成员和保护成员被继承到派生类中仍作为派生类的公有成员和保护成员。派生类的其他成员可以直接访问它们。无论派生类的成员还是派生类的对象都无法访问基类的私有成员。

    • 私有继承
      当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。基类的公有成员和保护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类外部通过派生类的对象无法访问。无论是派生类的成员还是通过派生类的对象,都无法访问从基类继承的私有成员。通过多次私有继承后,对于基类的成员都会成为不可访问。因此私有继承比较少用。
    • 保护继承
      保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。派生类的其他成员可以直接访问从基类继承来的公有和保护成员,但是类外部通过派生类的对象无法访问它们,无论派生类的成员还是派生类的对象,都无法访问基类的私有成员。

派生类的构造函数

    1. 派生类中由基类继承而来的成员的初始化工作还是由基类的构造函数完成,派生类中新增的成员在派生类的构造函数中初始化。
      派生类构造函数的语法:

      派生类名::派生类名(参数总表):基类名1(参数表1),基类名(参数名2)....基类名n(参数名n),内嵌子对象1(参数表1),内嵌子对象2(参数表2)....内嵌子对象n(参数表n)
      {
      派生类新增成员的初始化语句;
      }

      注:构造函数的初始化顺序并不以上面的顺序进行,而是根据声明的顺序初始化。

      1. 如果基类中没有不带参数的构造函数,那么在派生类的构造函数中必须调用基类构造函数,以初始化基类成员。
        1. 派生类构造函数执行的次序:
          1. 调用基类构造函数,调用顺序按照它们 被继承时声明的顺序 (从左到右);
          2. 调用内嵌成员对象的构造函数,调用顺序按照它们在类中声明的顺序;
          3. 派生类的构造函数体中的内容。

      派生类的析构函数

      派生类的析构函数的功能是在该对象消亡之前进行一些必要的清理工作,析构函数没有类型,也没有参数。析构函数的执行顺序与构造函数相反。

      实例:

    2. #include <iostream>
      #include <time.h>
      using namespace std;
      // 基类 B1
      class B1
      {
      public:
      B1(int i)
      {
      cout<<"constructing B1 "<<i<<endl;
      }
      ~B1()
      {
      cout<<"destructing B1"<<endl;
      }
      };
      //基类 B2
      class B2
      {
      public:
      B2(int j)
      {
      cout<<"constructing B2 "<<j<<endl;
      }
      ~B2()
      {
      cout<<"destructing B2"<<endl;
      }
      };
      //基类 B3
      class B3
      {
      public:
      B3()
      {
      cout<<"constructing B3"<<endl;
      }
      ~B3()
      {
      cout<<"destructing B3"<<endl;
      }
      };
      //派生类 C, 继承B2, B1,B3(声明顺序从左至右。 B2->B1->B3)
      class C: public B2, public B1, public B3
      {
      public:
      C(int a, int b, int c, int d):B1(a), memberB2(d), memberB1(c),B2(b)
      {
      //B1,B2的构造函数有参数,B3的构造函数无参数
      //memberB2(d), memberB1(c)是派生类对自己的数据成员进行初始化的过程、
      //构造函数执行顺序, 基类(声明顺序)-> 内嵌成员对象的构造函数(声明顺序) -> 派生类构造函数中的内容
      }
      private:
      B1 memberB1;
      B2 memberB2;
      B3 memberB3;
      };
      int main()
      {
      C obj(,,,);
      return ;
      }
      /* 输出结果 */
      /*
      constructing B2 2
      constructing B1 1
      constructing B3
      constructing B1 3
      constructing B2 4
      constructing B3
      destructing B3
      destructing B2
      destructing B1
      destructing B3
      destructing B1
      destructing B2
      */

      二义性问题

      在单继承下,基类的public 和protected 成员可以直接被访问,就像它们是派生类的成员一样,对多继承这也是正确的。但是在多继承下,派生类可以从两个或者更多个基类中继承同名的成员。然而在这种情况下,直接访问是二义的,将导致编译时刻错误。
      示例:

      #include <iostream>
      using namespace std;
      class A
      {
      public:
      void f();
      };
      class B
      {
      public:
      void f();
      void g();
      };
      class C : public A, public B
      {
      public:
      void g();
      void h();
      };
      int main(){
      C c1;
      // c1.f(); 产生二义性问题,访问A中的 f()? or B的 f() ?
      //通过指定成员名,限定消除二义性
      c1.A::f();
      c1.B::f();
      }

      使用成员名限定法可以消除二义性,但是更好的解决办法是在类C中定义一个同名函数 f(), 类C中的 f() 再根据需要来决定调用 A::f() or B::f(), 这样 c1.f() 将调用 C::f().

      当一个派生类从多个基类派生类,而这些基类又有一个共同的基类,则对该基类中说明的成员进行访问时,也可能会出现二义性。
      示例:

      //  派生类 B1,B2 继承相同的基类 A, 派生类 C 继承 B1, B2
      class A
      {
      public:
      int a;
      };
      class B1 : public A
      {
      private:
      int b1;
      };
      class B2 : public A
      {
      private:
      int b2;
      };
      class C : public B1, public B2
      {
      public:
      int f();
      private:
      int c;
      };
      int main(){
      C c1;
      c1.a();
      c1.A::a();
      c1.B1::a();
      c1.B2::a();
      return ;
      }

      c1.a;c1.A::a; 这两个访问都有二义性,c1.B1::a;c1.B2::a;是正确的:
      类C的成员函数 f() 用如下定义可以消除二义性:

      int C::f()
      { 
      retrun B1::a + B2::a; 
      }

      由于二义性的原因,一个类不可以从同一个类中直接继承一次以上。

      虚基类

      多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如非常经典的菱形继承层次。如下图所示:

      graph TD;
      A-->B;
      A-->C;
      B-->D;
      C-->D;

      类A派生出类B和类C,类D继承自类B和类C,这个时候类A中的成员变量和成员函数继承到类D中变成了两份,一份来自 A–>B–>D 这一路,另一份来自 A–>C–>D 这一条路。当D访问从A中继承的数据时,变一起将无法决定采用哪一条路传过来的数据,于是便出现了虚基类。

      在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突,而且很少有这样的需求。使用虚基类,可以使得在派生类中只保留间接基类的一份成员。

      声明虚基类只需要在继承方式前面加上 virtual 关键字,如下面示例:

      #include <iostream>
      using namespace std;
      class A{
      protected:
      int a;
      public:
      A(int a):a(a){}
      };
      class B: virtual public A{ //声明虚基类
      protected:
      int b;
      public:
      B(int a, int b):A(a),b(b){}
      };
      class C: virtual public A{ //声明虚基类
      protected:
      int c;
      public:
      C(int a, int c):A(a),c(c){}
      };
      class D: virtual public B, virtual public C{ //声明虚基类
      private:
      int d;
      public:
      D(int a, int b, int c, int d):A(a),B(a,b),C(a,c),d(d){}
      void display();
      };
      void D::display(){
      cout<<"a="<<a<<endl;
      cout<<"b="<<b<<endl;
      cout<<"c="<<c<<endl;
      cout<<"d="<<d<<endl;
      }
      int main(){
      (new D(, , , )) -> display();
      return ;
      }
      /*
      运行结果:
      a=1
      b=2
      c=3
      d=4
      */

      本例中我们使用了虚基类,在派生类D中只有一份成员变量 a 的拷贝,所以在 display() 函数中可以直接访问 a,而不用加类名和域解析符。

      虚基类的初始化
      : 请注意派生类D的构造函数,与以往的用法有所不同。 以往,在派生类的构造函数中只需负责对其直接基类初始化,再由其直接基类负责对间接基类初始化。现在,由于虚基类在派生类中只有一份成员变量,所以对这份成员变量的初始化必须由派生类直接给出。如果不由最后的派生类直接对虚基类初始化,而由虚基类的直接派生类(如类B和类C)对虚基类初始化,就有可能由于在类B和类C的构造函数中对虚基类给出不同的初始化参数而产生矛盾。所以规定:在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。

      在上述代码中,类D的构造函数通过初始化表调了虚基类的构造函数A,而类B和类C的构造函数也通过初始化表调用了虚基类的构造函数A,这样虚基类的构造函数岂非被调用了3次?大家不必过虑,C++编译系统只执行最后的派生类对虚基类的构造函数的调用,而忽略虚基类的其他派生类(如类B和类C)对虚基类的构造函数的调用,这就保证了虚基类的数据成员不会被多次初始化。

      最后请注意: 为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类,否则仍然会出现对基类的多次继承。

      赋值兼容原则

      赋值兼容
      : 赋值兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。

      赋值兼容规则中所指的替代包括:

        • 派生类的对象可以赋值给基类对象;
        • 派生类的对象可以初始化基类的引用;
        • 派生类对象的地址可以赋给指向基类的指针。
          在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。

此文转自huqunxing.site/

C++ 三大特性 继承(转载)的更多相关文章

  1. [.net 面向对象编程基础] (12) 面向对象三大特性——继承

    [.net 面向对象编程基础] (12) 面向对象三大特性——继承 上节我们说了面向对象的三大特性之一的封装,解决了将对同一对象所能操作的所有信息放在一起,实现统一对外调用,实现了同一对象的复用,降低 ...

  2. Java提高篇之理解java的三大特性——继承

    在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句 ...

  3. python学习-64 面向对象三大特性----继承1

    面向对象三大特性 1.三大特性? 继承,多态,封装 2.什么是继承? 类的继承和现实生活中的父与子,继承关系是一样的,父类为基类. python中的类继承分为:单继承和多继承 3.举例说明 class ...

  4. 二、java三大特性--继承

    在讲解之前我们先看一个例子 Husband.java public class Husband { private String name; private String sex; privatein ...

  5. java三大特性--继承

    定义: 继承就是子类继承父类的特征和行为,使得子类具有父类的各种属性和方法,使得子类具有父类相同的行为. 继承的好处: 有效实现代码复用,避免重复代码的出现. 让类与类之间产生了关系,是多态的前提. ...

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

    继承: 继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类(基类或超类),新建的类是所继承的类的(派生类或子类) 人类和狗 有相同的属性, 提取了一个__init__方法,在这 ...

  7. day35-1 类的三大特性---继承,以及类的派生

    目录 类的继承 继承的特性 类的派生 类的组合 类的继承 继承是为了拿到父类的所有东西 继承的特性 减少代码的冗余 Python中父类和子类的对应关系是多对多 使用__bases__方法获取对象继承的 ...

  8. python之面向对象三大特性: 继承(单继承)

    什么是继承 专业角度: B 继承 A类, B就叫做A的子类,派生类, A叫做B的父类,基类,超类. B类以及B类的对象使用A类的所有的属性以及方法. 字面意思: 继承就是继承父母所有的资产 class ...

  9. C#-面向对象的三大特性——继承

    继承 注意事项: 继承语法:   类名:父类名 父类也称之为 基类 ,子类也可以成为 xxx的派生类 或 超类. 父类可以有无限个子类,子类只能有一个父类(亲爹),可以有无限个接口(干爹) 子类并不是 ...

随机推荐

  1. 原生javascript禁用和屏蔽鼠标右键

    (function(){ var doc=document, ua = navigator.userAgent.toLowerCase(), check = function(r){return r. ...

  2. Problem 5 素数筛法+并查集

    $des$ 给定一个长度为 $n$ 的正整数序列 ${a_i }$.将 ${1,2,...,n}$ 划分成两个非空集合 $S.T$,使得 $gcd(\prod_{i \in S} a_i, \prod ...

  3. 2017.10.2 国庆清北 D2T2 树上抢男主

    /* 我只看懂了求LCA */ #include<iostream> #include<cstring> #include<cstdio> #include< ...

  4. docker-machine 远程安装docker

    base=https://github.com/docker/machine/releases/download/v0.14.0 && curl -L $base/docker-mac ...

  5. Spring boot Aop 示例

    需要的依赖 <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -- ...

  6. python 通过下载包setup.py安装模块

    下载安装包,并解压到相应的位置 1.打开cmd 2.到达安装目录 3.python setup.py build 4.python setup.py install

  7. ubuntu之路——day10.1 ML的整体策略——正交化

    orthogonalization 正交化的概念就是指,将你可以调整的参数设置在不同的正交的维度上,调整其中一个参数,不会或几乎不会影响其他维度上的参数变化,这样在机器学习项目中,可以让你更容易更快速 ...

  8. 小福bbs-冲刺日志(第六天)

    [小福bbs-冲刺日志(第六天)] 这个作业属于哪个课程 班级链接 这个作业要求在哪里 作业要求的链接 团队名称 小福bbs 这个作业的目标 后端努力完成大部分功能操作,前端UI完成大部分功能测试 作 ...

  9. html5中的fieldset/legend元素和keygen元素

    html5中的fieldset/legend元素和keygen元素 一.总结 一句话总结: fieldset/legend元素和figure和figcaption很像,只不过是作用于表单,前者表示内容 ...

  10. 范仁义web前端介绍课程---5、webstorm的下载安装

    范仁义web前端介绍课程---5.webstorm的下载安装 一.总结 一句话总结: webstorm破解版搜索:webstorm破解 site:52pojie.cn 编辑器随便选用,功能都差不多,哪 ...