• 继承(一)


    C++很重要的一个特征就是代码重用。在C语言中重用代码的方式就是拷贝代码、修改代码。C++可以用继承或组合的方式来重用。通过组合或继承现有的的类来创建新类,而不是重新创建它们。
    关于“继承”是什么,只要是有过面向对象的编程基础的应该都很容易理解,接下来会详细对其进行介绍,而上面提到了一个“组合”,那它的表现形式是咋样的呢?
    定义一个A类:
    此时有一个B类,它的功能跟A类的很相近,没必要重头到尾编写这些功能,所以可以去调用A类的方法来实现重用,如下:
    实际上组合也就是将一个类作为另一类的对象成员,来达到复用代码的目的。另外一种复用代码的方式则就是继承了,下面来了解C++的继承:

    • 继承是使用已经编写好的类来创建新类,新的类具有原有类的所有属性和操作,也可以在原有类的基础上作一些修改和增补。
    • 新类称为派生类或子类,原有类称为基类或父类。
    • 派生类是基类的具体化。
    • 派生类的声明语法为:

      class 派生类名 : 继承方式  基类名

        {

                 派生类新增成员的声明;

        };

    下面用代码来具体使用一下:

    #include <iostream>
    using namespace std;
    
    class Base {//声明基类
    public:
        int x_;
    protected:
        int y_;
    private:
        int z_;
    };
    
    
    class PublicInherit : public Base {//声明派车类,公有继承基类
    public:
        void Test(){
            x_ = 10;
            y_ = 20;
            z_ = 30;
        }
    private:
        int a;
    };
    
    int main(void) {
    
        return 0;
    }

    以上代码没有什么意义,纯是学习语法,编译一下:

    报错了,继承类无法使用基类的私有成员,关于这个修饰符跟java的类似,很容易理解。对于protected、public的区别,也差不多,前者只能在派生类中访问,而后者则可以在类的外部进行访问,如下:

    #include <iostream>
    using namespace std;
    
    class Base {//声明基类
    public:
        int x_;
    protected:
        int y_;
    private:
        int z_;
    };
    
    
    class PublicInherit : public Base {//声明派车类,公有继承基类
    public:
        void Test(){
            x_ = 10;
            y_ = 20;
            //z_ = 30;    ERROR,派生类中无法访问基类的私有成员
        }
    private:
        int a;
    };
    
    int main(void) {
        Base b;
        b.x_ = 20;//在类外部访问public成员
        return 0;
    }

    这时可以正常编译的,但是如果访问protected成员,则会报错:

    编译:

    对于这些修饰符有了初步的认识之后,下面来详细介绍一下它们:

    • 在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。
    • 在关键字private后面声明,只允许本类中的函数访问,而类外部的任何函数都不能访问。
    • 在关键字protected后面声明,与private类似,其差别表现在继承与派生时对派生类的影响不同。

    对于我们写的例子用的是公有继承:

    跟java不一样的,它还有其它方式进行继承,下面用一张表来总结一下继承情况:

    下面来使用一下私有继承的情况:

    #include <iostream>
    using namespace std;
    
    class Base {//声明基类
    public:
        int x_;
    protected:
        int y_;
    private:
        int z_;
    };
    
    
    class PublicInherit : public Base {//声明派车类,公有继承基类
    public:
        void Test(){
            x_ = 10;
            y_ = 20;
            //z_ = 30;    ERROR,派生类中无法访问基类的私有成员
        }
    private:
        int a;
    };
    
    class PrivateInherit : private Base {//声明派车类,私有继承基类
    public:
        void Test(){
            x_ = 10;
            y_ = 20;
        }
    private:
        int a;
    };
    
    int main(void) {
        
        return 0;
    }

    先看下私有继承,能否对public和protect成员进行访问:

    正常编译,那如果以对象的形式能否访问呢?

    那如果以对象的形式去访问呢,又会如何?

    #include <iostream>
    using namespace std;
    
    class Base {//声明基类
    public:
        int x_;
    protected:
        int y_;
    private:
        int z_;
    };
    
    
    class PublicInherit : public Base {//声明派车类,公有继承基类
    public:
        void Test(){
            x_ = 10;
            y_ = 20;
            //z_ = 30;    ERROR,派生类中无法访问基类的私有成员
        }
    private:
        int a;
    };
    
    class PrivateInherit : private Base {//声明派车类,私有继承基类
    public:
        void Test(){
            x_ = 10;
            y_ = 20;
            //z_ = 30;    ERROR,派生类中无法访问基类的私有成员
        }
    private:
        int a;
    };
    
    int main(void) {
        PrivateInherit pi;
        pi.x_ = 10;
        return 0;
    }

    编译:

    经过私有继承之后,基类的公有成员也变为私有的了,无法在外部进行访问。

    对于保护继承就不测试了,其结果可以从表中知道。

    • class Base {};
    • struct D1 : Base {};  // 对于结构体而言,默认就是公有继承
    • class D2 : Base {};  // 对于类而言,默认就是私有继承

    • 我们将类的公有成员函数称为接口。
    • 公有继承,基类的公有成员函数在派生类中仍然是公有的,换句话说是基类的接口成为了派生类的接口,因而将它称为接口继承。
    • 实现继承,对于私有、保护继承,派生类不继承基类的接口。派生类将不再支持基类的公有接口,它希望能重用基类的实现而已,因而将它称为实现继承。

    •  对基类的数据成员的重定义。

      这时重定义一下x_:

      #include <iostream>
      using namespace std;
      
      class Base {//声明基类
      public:
          Base() : x_(0) {
      
          }
          int getBaseX() const {
              return x_;
          }
          int x_;
      };
      
      class Derived : public Base {
      public:
          Derived() : x_(0) {
      
          }
          int getDerivedX() const {
              return x_;
          }
          int x_;//重定义了x_
      };
      
      int main(void) {
          Derived d;
          d.x_ = 10;
          cout<<d.getBaseX()<<endl;
          cout<<d.getDerivedX()<<endl;
          return 0;
      }

      这时打印一下看改变的是:

      当然是自身重定义的x_喽。

    • 对基类成员函数的重定义分为两种
      ①、overwrite【注意:它不是重载(overload),重载是作用域相同才可以,也就是在同一个类中发生的】
        a、与基类完全相同
        b、与基类成员函数名相同,参数不同
      #include <iostream>
      using namespace std;
      
      class Base {//声明基类
      public:
          Base() : x_(0) {
      
          }
          int getBaseX() const {
              return x_;
          }
          void show() {
              cout<<"Base::show ..."<<endl;
          }
          int x_;
      };
      
      class Derived : public Base {
      public:
          Derived() : x_(0) {
      
          }
          int getDerivedX() const {
              return x_;
          }
          int x_;//重定义了x_
      };
      
      int main(void) {
          Derived d;
          d.x_ = 10;
          cout<<d.getBaseX()<<endl;
          cout<<d.getDerivedX()<<endl;
      
          d.show();//派生类没有重定义show函数,所以调用的肯定是基类的show了
      
          return 0;
      }

      编译运行:

      接下来派生类重写一下show函数:

      #include <iostream>
      using namespace std;
      
      class Base {//声明基类
      public:
          Base() : x_(0) {
      
          }
          int getBaseX() const {
              return x_;
          }
          void show() {
              cout<<"Base::show ..."<<endl;
          }
          int x_;
      };
      
      class Derived : public Base {
      public:
          Derived() : x_(0) {
      
          }
          int getDerivedX() const {
              return x_;
          }
          void show(int n) {
              cout<<"Derived::show ..."<<n<<endl;
          }
          int x_;//重定义了x_
      };
      
      int main(void) {
          Derived d;
          d.x_ = 10;
          cout<<d.getBaseX()<<endl;
          cout<<d.getDerivedX()<<endl;
      
          d.show();
      
          return 0;
      }

      编译:

      这说明基类的无参的show函数被隐藏了,这是带参数的重写,另外也有不带参数的重写,如下:

      编译运行:

      那如果想访问基类的show()方法,可以这样做:

      编译运行:

      同样的,如果想访问被重写的父类成员也一样:



      ②、override【覆盖,需要虚函数才可以,虚函数之后再学习,先了解一下】

    • 无论是继承与组合本质上都是把子对象放在新类型中,两者都是使用构造函数的初始化列表去构造这些子对象。
      也就是说明继承与组合的内存模型是一样的,下面用代码来说明下:
      #include <iostream>
      using namespace std;
      
      class Base {//声明基类
      public:
          Base() : x_(0) {
      
          }
          int getBaseX() const {
              return x_;
          }
          void show() {
              cout<<"Base::show ..."<<endl;
          }
          int x_;
      };
      
      class Derived : public Base {
      public:
          Derived() : x_(0) {
      
          }
          int getDerivedX() const {
              return x_;
          }
          void show(int n) {
              cout<<"Derived::show ..."<<n<<endl;
          }
          void show() {
              cout<<"Derived::show ..."<<endl;
          }
          int x_;//重定义了x_
      };
      
      int main(void) {
          Derived d;
          d.x_ = 10;
          d.Base::x_ = 20;
          cout<<d.getBaseX()<<endl;
          cout<<d.getDerivedX()<<endl;
      
          d.show();
          d.Base::show();
      
          cout<<sizeof(Derived)<<endl;//打印一下派生类的大小,实际上包含两个int类型,一个是基类的,一个是自身的
      
          return 0;
      }

      下面用组合来看一下:
      #include <iostream>
      using namespace std;
      
      class Base {//声明基类
      public:
          Base() : x_(0) {
      
          }
          int getBaseX() const {
              return x_;
          }
          void show() {
              cout<<"Base::show ..."<<endl;
          }
          int x_;
      };
      
      class Derived : public Base {
      public:
          Derived() : x_(0) {
      
          }
          int getDerivedX() const {
              return x_;
          }
          void show(int n) {
              cout<<"Derived::show ..."<<n<<endl;
          }
          void show() {
              cout<<"Derived::show ..."<<endl;
          }
          int x_;//重定义了x_
      };
      
      class Test {
      public:
          Base b_;//组合关系
          int x_;
      };
      
      int main(void) {
          Derived d;
          d.x_ = 10;
          d.Base::x_ = 20;
          cout<<d.getBaseX()<<endl;
          cout<<d.getDerivedX()<<endl;
      
          d.show();
          d.Base::show();
      
          cout<<sizeof(Derived)<<endl;
          cout<<sizeof(Test)<<endl;
      
          return 0;
      }

    • 组合通中是在希望新类内部具有已存在的类的功能时使用,而不是希望已存在类作为它的接口。组合通过嵌入一个对象以实现新类的功能,而新类用户看到的是新定义的接口,而不是来自老类的接口。(has-a)
      举个现实中的例子来理解:
      一部汽车(它有行驶的功能)有引擎(它有启动、停止、加速、减速的功能)和轮胎(它有滚动的功能),而汽车是利用引擎和轮胎来实现实驶的功能,所以汽车与引擎和轮胎是组合关系。
    • 如果希望新类与已存在的类有相同的接口(在这基础上可以增加自己的成员)。这时候需要用继承,也称为子类型化。(is-a)【实际上它是LSP(Liskov Substitution Principle里氏代换原则),用它可以检验继承的质量,这个在之后会学习到,了解一下~】
  • 相关阅读:
    无缝世界场景加载的解决方案研究
    3D物体绘制不见
    dx sdk中关于常用dx api的performace性能参数
    OpenGL/DirectX渲染技巧集
    每天送你一個simle
    [原创] 一种页面数据错误输入提示方法
    [原创] ASP.NET 中如何弹出提示窗口然后导向另外一个页面
    [原创] 部署含有ReportView的控件的ASPX页面时出现错误
    公布一个简单的日志记录方法
    [原创] 如何在没有ASP.NET AjaxEnabled Web Site 向导的情况下加入Ajax支持
  • 原文地址:https://www.cnblogs.com/webor2006/p/5540460.html
Copyright © 2020-2023  润新知