类的继承与多态复习

1.通过类的继承(派生)来引入 是一个 的关系

永远记住: 派生类描述的 “是一个” 基类

  • 通常采用 public 继承( struct[public] V.S. class[private] )

  • 注意:继承部分不是类的声明

  • 使用基类的指针或引用可以指向派生类对象

  • 静态类型 V.S. 动态类型

    #include<iostream>
    #include<vector>
    #include<string>
    struct Base
    {
      void fun(){}
    };
    struct Base2 : base
    {
    };
    struct Derive : public Base2//class缺省的话是private继承
    {
      void fun2(){}
    };
    struct Derive2 : public Base2//class缺省的话是private继承
    {
      void fun2(){}
    };
    
    int main()
    {
      Derive d;
      Drive d2;
    
      - 使用基类的指针或引用可以指向派生类对象
      - Base& 为静态类型,编译器识别的类型
      - 实际上为动态类型 d (Derive&):运行期识别的类型
      Base& ref = d;//Base&为静态类型,编译期识别的类型
      Base* ptr = &d;
      ptr = &d2;//合法,动态类型类型发生改变
      ref.fun();//静态类型,编译器识别,编译不会报错
      ref.fun2();//fun2为Derive成员函数.动态类型,编译器无法正确识别,编译报错
    }
    

2.类的派生会形成嵌套域

嵌套域:

{
  {
    类似这种
  }
}
  • 派生类所在域位于基类内部

  • 派生类中的名称定义会覆盖基类

    #include<iostream>
    #include<vector>
    #include<string>
    struct Base
    {
      int val = 2;
    };
    
    struct Derive : public Base
    {
      public:
        void fun2()
        {
          std::cout<< val <<std::endl;
        }
        int val = 3;
    };
    int main()
    {
      Derive d;
      d.fun2();//3
    }
    
  • 在派生类中调用基类的构造函数

    #include<iostream>
    #include<vector>
    #include<string>
    struct Base
    {
      Base()
      {
        std::cout<<"Base Constructor is called!"<<std::endl;
      }
    };
    
    struct Derive : public Base
    {
      Derive()
      {
         std::cout<<"Derive Constructor is called!"<<std::endl;
      }
      public:
        void fun2()
        {
          std::cout<< val <<std::endl;
        }
        int val = 3;
    };
    int main()
    {
      Derive d;
      打印顺序:
      - 1.Base Constructor is called!
      - 2.Derive Constructor is called!
    }
    

    这种情况会报错,系统不知道怎样去调用base的构造函数

    #include<iostream>
    #include<vector>
    #include<string>
    struct Base
    {
      Base(int)//构造函数加一个参数
      {
        std::cout<<"Base Constructor is called!"<<std::endl;
      }
    };
    
    struct Derive : public Base
    {
      Derive(int a)
      {
         std::cout<<"Derive Constructor is called!"<<std::endl;
      }
      public:
        void fun2()
        {
          std::cout<< val <<std::endl;
        }
        int val = 3;
    };
    int main()
    {
      Derive d;
    }
    

    此时需要Derive显式的去调用构造函数

    #include<iostream>
    #include<vector>
    #include<string>
    struct Base
    {
      Base(int)
      {
        std::cout<<"Base Constructor is called!"<<std::endl;
      }
    };
    
    struct Derive : public Base
    {
       - 利用初始化列表显式调用基类的构造函数!!
      Derive(int a)
        :Base(a)
      {
         std::cout<<"Derive Constructor is called!"<<std::endl;
      }
      public:
        void fun2()
        {
          std::cout<< val <<std::endl;
        }
        int val = 3;
    };
    int main()
    {
      Derive d(3);//此时就合法了!!
      打印顺序:
      - 1.Base Constructor is called!
      - 2.Derive Constructor is called!
    }
    

3.类的继承——虚函数

vtable

  1. 左边最里面的方框为一个基类;
  2. 第二个方框为一个派生类;

3.1. 通过虚函数与引用(指针)实现动态绑定

  • 使用关键字 virtual 引入
  • 非静态、非构造函数可声明为虚函数
  • 虚函数会引入vtable结构

  • 1.派生类转换为基类(派生类描述的 “是一个” 基类的关系,因此这种转换没问题):

     动态类型-->静态类型
     MyClassDerived2 d;
     Base& b = d;
     Base* ptr = &d;
    
  • 2.但是反过类,可以将基类转为派生类吗?不一定,静态类型–>动态类型, 这个静态类型可能还对应着其他的动态类. 如何保证一个相对安全的静态类型–>动态类型的转换呢?dynamic_cast。 基类类型转派生类类型–>运行期发生

  • 3.之所以可以这样转换是因为存在虚函数表vtable, vtable是虚函数引入的, 因此,如果没有虚函数,也无法进行这样的转换(没有虚函数,基类就不支持多态的转换)

     MyClassDerived2& d2 = dynamic_cast<MyClassDerived2>(b)
     MyClassDerived2* ptr2 = dynamic_cast<MyClassDerived2*>(ptr)
    
  • 4.只要链路完整,都能查找成功

    MyClassDerived& d3 = dynamic_cast<MyClassDerived>(b)
    MyClassDerived* ptr3 = dynamic_cast<MyClassDerived*>(ptr)
    

3.2.虚函数在基类中的定义

  • 引入缺省逻辑

    #include<iostream>
    #include<vector>
    #include<string>
    struct Base
    {
      //默认是public
      //此处的虚函数是缺省的逻辑
      virtual void fun()
      {
        std::cout<< "Base::fun() is called!\n";
      }
    };
    
    struct Derived : public Base
    {
    };
    
    int main()
    {
      Derived d;
      d.fun();//Base::fun() is called!
    }
    

    这里是面试笔试经常会遇到的情况:

    #include<iostream>
    #include<vector>
    #include<string>
    struct Base
    {
      //默认是public
      virtual void fun()
      {
        std::cout<< "Base::fun() is called!\n";
      }
    };
    
    struct Derived : public Base
    {
      void fun()//重写
      {
        std::cout<<"Derive::fun() is called!\n";
      }
    
    };
    
    - 动态多态(运行期多态): 通过动态类型(运行期绑定)实现的运行期多态
    
    void proc(Base& b)
    {
      b.fun();
    } 
    
    int main()
    {
      Derived d;
      d.fun();//Derive::fun() is called!
    
      - 由于vtable的存在,b虽然是Base&类型, 但指向的是d Derived类型, 会在运行期进行绑定
      Base& b = d;
      b.fun();//Derive::fun() is called!
    
      proc(d);//合法,等价于 Base& b = d;Derive::fun() is called!
      ------------
      Base b;
      proc(b);//合法;Base::fun() is called!
    }
    

    如果删除virtual:

    #include<iostream>
    #include<vector>
    #include<string>
    struct Base
    {
      //默认是public
      void fun()
      {
        std::cout<< "Base::fun() is called!\n";
      }
    };
    
    struct Derived : public Base
    {
      void fun()
      {
        std::cout<<"Derive::fun() is called!\n";
      }
    
    };
    
    int main()
    {
      Derived d;
      d.fun();//Derive::fun() is called!
      - 此时没有vtable,就无法由基类找到派生类,在编译期就被绑定在一起
      Base& b = d;
      b.fun();//Base::fun() is called!
    }
    
  • 可以通过 = 0 声明纯虚函数,相应地构造抽象基类

    #include<iostream>
    #include<vector>
    #include<string>
    
    struct Base//包含纯虚函数的类: 抽象基类
    {
      virtual void fun() = 0;// 声明纯虚函数,在vtable里面先开个槽,作为一个接口在派生类中进行实现
    };
    
    struct Derived : public Base
    {
      void fun()
      {
        std::cout<<"Derived::fun() is called!\n";
      }
    };
    void proc(Base& b)
    {
      b.fun();
    }
    
    int main()
    {
      Derived d;
      d.fun();
    
      Base b;//报错,不能使用抽象基类定义对象
      Base& b = d;//允许使用抽象基类指向派生类(指针或)
    }
    
  • 派生类对纯虚函数的重写

    #include<iostream>
    #include<vector>
    #include<string>
    
    //struct Base2{};
    
    struct Base//包含纯虚函数的类: 抽象基类
    {
      virtual void fun() = 0;// 声明纯虚函数,在vtable里面先开个槽,作为一个接口在派生类中进行实现
    };
    
    struct Derived2 : Base//合法,但你本质上Derived2还是一个抽象基类
    {
    
    };
    
    int main()
    {
      Derived2 d2;//报错,无法构造对象,Derived2派生自Base,Base为抽象基类,Derived2没有对纯虚函数进行重写
    }
    

    重写后:

    #include<iostream>
    #include<vector>
    #include<string>
    
    //struct Base2{};
    
    struct Base//包含纯虚函数的类: 抽象基类
    {
      virtual void fun() = 0;// 声明纯虚函数,在vtable里面先开个槽,作为一个接口在派生类中进行实现
    };
    
    struct Derived2 : Base//合法,对纯虚函数进行了实现,此时不再是抽象基类了
    {
      void fun()
      {
        std::cout<<"Derived2::fun() is called!\n";
      }
    };
    
    struct Derived : Derived2
    {
    };
    void proc(Base& b)
    {
      b.fun();
    }
    
    int main()
    {
      Derived d;
      d.fun();//Derived2::fun() is called!
    
      Derived2 d2;//合法
    }
    
    • 给纯虚函数一个定义:
    #include<iostream>
    #include<vector>
    #include<string>
    
    //struct Base2{};
    
    struct Base//虽然定义了纯虚函数,但是并不改变Base是一个抽象基类
    {
      virtual void fun() = 0;// 声明纯虚函数,在vtable里面先开个槽,作为一个接口在派生类中进行实现
    };
    
    - 要定义纯虚函数,只能在类的外部
    void Base::fun()
    {
      std::cout<<"Base::fun() is called!\n";
    }
    
    struct Derived : Base//合法,对纯虚函数进行了实现,此时不再是抽象基类了
    {
      void fun()
      {
        Base::fun();//显式调用
        std::cout<<"Derived::fun() is called!\n";
      }
    };
    
    void proc(Base& b)
    {
      b.fun();
    }
    
    int main()
    {
      //Base::fun() is called!
      //Derived::fun() is called!
      Derived d;
      d.fun();
      Base b;//依然报错
    }
    

3.3.虚函数在派生类中的重写( override )

  • 函数签名(都是void fun(),包括函数名与形参不变)保持不变(返回类型可以是原始返回指针 / 引用类型的派生指针 / 引用类型)

    #include<iostream>
    #include<vector>
    #include<string>
    
    struct Base2{};
    struct Derived2 : Base2 {};
    
    struct Base
    {
      virtual Base2& fun()
      {
        std::cout<< "virtual Base2& fun() is called!\n";
        static Base2 b;
        return b;
      }
    };
    
    struct Derived : public Base
    {
      Derived2& fun()//是重写
      {
        std::cout<<"Derived2& fun() is called!\n";
        static Derived2 inst;
        return inst;
      }
    
    };
    void proc(Base& b)
    {
      b.fun();
    }
    
    int main()
    {
      Derived d;
      d.fun();//Derived2& fun() is called!
    
      Base b;
      b.fun();//virtual Base2& fun() is called!
    }
    

    如果虚函数形参不同,则不是重写是重载:

    #include<iostream>
    #include<vector>
    #include<string>
    
    struct Base2{};
    struct Derived2 : Base2 {};
    
    struct Base
    {
      virtual void fun()
      {
        std::cout<< "Base::fun() is called!\n";
      }
    };
    
    struct Derived : public Base
    {
      /*
      int fun()//报错,不match,函数签名不一样了
      {
        std::cout<<"Derived::int fun() is called!\n";
        return 3;
      }
      */
      void fun(int)//合法,但不是重写,是重载,参数列表发生了改变
      {
        std::cout<<"Derived::fun(int) is called!\n";
      }
    
    };
    void proc(Base& b)
    {
      b.fun();
    }
    
    int main()
    {
      Derived d;
      d.fun();//Base::fun() is called!
    
      Base b;
      b.fun();//Base::fun() is called!
    }
    
  • 虚函数特性保持不变:在基函数中是虚函数–>在派生类中任然是虚函数

    #include<iostream>
    #include<vector>
    #include<string>
    
    
    struct Base
    {
      virtual void fun()
      {
        std::cout<<"Base::fun() is called!\n";
      }
    };
    
    struct Derive : Base
    {
      void fun()//虚函数特性保持不变:在基函数中是虚函数-->在派生类中任然是虚函数
      {
        std::cout<<"Derive::fun() is called!\n";
      }
    };
    
    void proc(Base& b)
    {
      b.fun();
    }
    
    int main()
    {
      Derived d;
      d.fun();//Derived::fun() is called!
    
    }
    
  • override 关键字

    #include<iostream>
    #include<vector>
    #include<string>
    
    struct Base
    {
      virtual void fun()
      {
        std::cout<<"Base::fun() is called!\n";
      }
    };
    
    struct Derive : Base
    {
      //如果形参不同了,则构成了重载的关系,但如果实际上不是我们的本意,我们希望的是重写,
      //此时就可以加关键字override,如果重写不合法,就会报错提示
      void fun(int) override //此时我们希望是重写,但这里是重载,会有报错提示
      {
        std::cout<<"Derive::fun() is called!\n";
      }
    };
    
    void proc(Base& b)
    {
      b.fun();
    }
    
    int main()
    {
      Derived d;
      d.fun();//err
    }
    

3.4.由虚函数所引入的动态绑定属于运行期行为,与编译期行为有所区别

  • 虚函数的缺省实参只会考虑静态类型

    #include<iostream>
    #include<vector>
    #include<string>
    
    struct Base
    {
      virtual void fun(int x = 3)
      {
        std::cout<<"Base:"<<x<<std::endl;
      }
    };
    
    struct Derive : Base
    {
      void fun(int x = 4) override
      {
        std::cout<<"Derive:"<<x<<std::endl;
      }
    };
    
    void proc(Base& b)
    {
      b.fun();
    }
    
    int main()
    {
      Derive d;
      - 打印: Derive: 3 --> 虚函数的缺省实参只会考虑静态类型(proc函数的形参为Base,静态类型为Base, x为3)
      proc(d);//这段代码可以放入cppinsights中看一下
    }
    
  • 虚函数的调用成本高于非虚函数: 很直观,需要各种跳转

    • final 关键字: 告诉编译器,此后派生的类不会再对该虚函数进行重写,也就是说当前这个虚函数已经是虚函数表的最后一个,在一定程度上提升代码性能(类的派生很大程度上就是重写虚函数实现功能,如果定义为了final,基本上也意味着不会再对该类进行派生了,因此final也可以修饰类)
    #include<iostream>
    #include<vector>
    #include<string>
    
    struct Base
    {
      virtual void fun(int x = 3)
      {
        std::cout<<"Base:"<<x<<std::endl;
      }
    };
    
    struct Derive : Base
    {
      void fun(int x = 4) override final
      {
        std::cout<<"Derive:"<<x<<std::endl;
      }
    };
    
    struct Derive final : Base//final也可以修饰类
    {
      void fun(int x = 4) override
      {
        std::cout<<"Derive:"<<x<<std::endl;
      }
    };
    struct Derive2 : Derive//再派生就报错!
    {
    
    }
    void proc(Base& b)
    {
      b.fun();
    }
    
    int main()
    {
      Derive d;
      - 打印: Derive: 3 --> 虚函数的缺省实参只会考虑静态类型(proc函数的形参为Base,静态类型为Base, x为3)
      proc(d);//这段代码可以放入cppinsights中看一下
    }
    
  • 为什么要使用指针(或引用)引入动态绑定

    #include<iostream>
    #include<vector>
    #include<string>
    
    struct Base
    {
      virtual void fun(int x = 3)
      {
        std::cout<<"Base:"<<x<<std::endl;
      }
    };
    
    struct Derive final : Base//final也可以修饰类
    {
      void fun(int x = 4) override
      {
        std::cout<<"Derive:"<<x<<std::endl;
      }
    };
    
    void proc(Base b)//没有指针或引用就是使用d来构造Base(将Derive隐式转换为了Base)
    {
      b.fun();
    }
    
    int main()
    {
      Derive d;
      - 打印: Base: 3
      proc(d);
    }
    
  • 在构造函数中调用虚函数要小心

    #include<iostream>
    #include<vector>
    #include<string>
    
    struct Base
    {
      Base()//构造函数
      {
        fun();
      }
      virtual void fun()
      {
        std::cout<<"Base:"<<std::endl;
      }
    };
    
    struct Derive final : Base//final也可以修饰类
    {
      Derive()//构造函数
        :Base()
        {
        }
      void fun() override
      {
        std::cout<<"Derive:"<<std::endl;
      }
    };
    
    int main()
    {
      Derive d;//打印--Base:
    }
    
  • 派生类的析构函数会隐式调用基类的析构函数

    #include<iostream>
    #include<vector>
    #include<string>
    #include<memory>
    struct Base
    {
       ~Base()
      {
        std::cout<<"Base"<<std::endl;
      }
    };
    
    struct Derive final : Base//final也可以修饰类
    {
      ~Derive()
      {
        std::cout<<"Derive"<<std::endl;
      }
    };
    
    int main()
    {
      Derive* d = new Derive();
      delete d;
      - 打印顺序:
        Derive
        Base
    
      ------
      Derive* d = new Derive();
      Base* b = d;
      delete b;
      - 打印(行为是未定义的,大概率是只调用Base的析构函数):
        Base
    
      ------
      std::shared_ptr<Base> ptr(new Derive());
      - 打印顺序(行为是未定义的,不可控):
        Derive
        Base
    
      ------
      std::unique_ptr<Base> ptr(new Derive());
      - 打印顺序(行为是未定义的,不可控):
        Base
    
    }
    
  • 通常来说要将基类的析构函数声明为 virtual 的

    #include<iostream>
    #include<vector>
    #include<string>
    #include<memory>
    struct Base
    {
      virtual ~Base()
      {
        std::cout<<"Base"<<std::endl;
      }
    };
    
    struct Derive final : Base//final也可以修饰类
    {
       ~Derive()//会继承虚函数,这个析构函数也是虚函数
      {
        std::cout<<"Derive"<<std::endl;
      }
    };
    
    int main()
    {
      Derive* d = new Derive();
      delete d;
      - 打印顺序:
        Derive
        Base
    
      ------
      Derive* d = new Derive();
      Base* b = d;
      delete b;
      - 打印顺序(行为是确定的,可以通过vtable进行查找,后构造的先析构):
        Derive
        Base
    
      ------
      std::shared_ptr<Base> ptr(new Derive());
      - 打印顺序(行为是确定的,可以通过vtable进行查找,后构造的先析构):
        Derive
        Base
      
      ------
      std::unique_ptr<Base> ptr(new Derive());
      - 打印顺序(行为是确定的,可以通过vtable进行查找,后构造的先析构):
        Derive
        Base
    }
    

    析构函数大括号里面没有什么需要写的东西时:

    #include<iostream>
    #include<vector>
    #include<string>
    #include<memory>
    struct Base
    {
       virtual ~Base() = default;//重点在virtual 
    };
    
    struct Derive final : Base//final也可以修饰类
    {
       ~Derive()//会继承虚函数,这个析构函数也是虚函数
      {
        std::cout<<"Derive"<<std::endl;
      }
    };
    
    int main()
    {
      std::shared_ptr<Base> ptr(new Derive());
    }
    
  • 基类的析构函数一定要申明为虚函数吗?答:只有通过基类的指针删除派生类时才需要这样处理.

    #include<iostream>
    #include<vector>
    #include<string>
    #include<memory>
    struct Base
    {
      virtual ~Base()
      {
        std::cout<<"Base"<<std::endl;
      }
    };
    
    struct Derive final : Base//final也可以修饰类
    {
       ~Derive()//会继承虚函数,这个析构函数也是虚函数
      {
        std::cout<<"Derive"<<std::endl;
      }
    };
    
    int main()
    {
      - 如果是通过基类的指针删除派生类对象,则基类的析构函数需要是virtual
      Derive d = new Derive();
      Base* b = d;
      delete b;
    
      - 如果是通过派生类删除派生类对象,则基类的析构函数不需要是virtual
      Derive* d = new Derive();
      delete d;//系统知道,派生类是派生自哪个基类
    }
    
  • 在派生类中修改虚函数的访问权限:访问权限是编译期行为

    #include<iostream>
    #include<vector>
    #include<string>
    #include<memory>
    struct Base
    {
      protected:
      virtual void fun()
      {
        std::cout<<"Base"<<std::endl;
      }
    };
    
    struct Derive final : Base//final也可以修饰类
    {
      public:
      void fun() override
      {
        std::cout<<"Derive"<<std::endl;
      }
    };
    
    int main()
    {
      Derive d;
      d.fun();//Derive,这里就有访问权限,相当于改变了虚函数的访问权限
      ----
      Base& b = d;//编译期看的是静态类型,静态类型Base的fun没有外部访问权限
      b.fun();//报错,没有访问权限
    }
    

4.类的继承——继承与特殊成员函数

  • 派生类合成的:

    • 缺省构造函数会隐式调用基类的缺省构造函数
    • 拷贝构造函数将隐式调用基类的拷贝构造函数
    • 赋值函数将隐式调用基类的赋值函数
      #include<iostream>
      #include<vector>
      #include<string>
      #include<memory>
      struct Base
      {
        Base()
        {
          std::cout<<"Base default constructor is called!"<<std::endl;
        }
      
        Base(const Base& val)
        {
          std::cout<<"Base copy constructor is called!"<<std::endl;
        }
        Base& operator=(const Base&)//赋值函数
        {
          std::cout<<"Base assignment is called!"<<std::endl;
          return *this;
        }
      };
      
      struct Derive : Base
      {
        /*添加这几行,同样的效果,都是编译器合成
        Derive() = default;
        Derive(const Derive& val) = default;
        Derive& operator=(const Derive&) = default;
        */
      };
      
      int main()
      {
        - 缺省构造函数会隐式调用基类的缺省构造函数
          打印:Base default constructor is called!
        Derive d;//默认构造
      
        -----
        - 拷贝构造函数将隐式调用基类的拷贝构造函数
          打印:Base default constructor is called!
              Base copy constructor is called!
        Derive d;//默认构造
        Derive x(d);//拷贝初始化
      
        -----
        - 赋值函数将隐式调用基类的赋值函数
          打印:Base default constructor is called!
              Base copy constructor is called!
              Base assignment is called!
        Derive d;//默认构造
        Derive x(d);//拷贝初始化
        x = d;//赋值
      }
      
  • 派生类的析构函数会调用基类的析构函数

    #include<iostream>
    #include<vector>
    #include<string>
    #include<memory>
    struct Base
    {
      Base()
      {
        std::cout<<"Base default constructor is called!"<<std::endl;
      }
    
      Base(const Base& val)
      {
        std::cout<<"Base copy constructor is called!"<<std::endl;
      }
      Base& operator=(const Base&)//赋值函数
      {
        std::cout<<"Base assignment is called!"<<std::endl;
        return *this;
      }
      ~Base()
      {
        std::cout<<"Base destructor is called!"<<std::endl;
      }
    };
    
    struct Derive : Base
    {
      ~Derive()
      {
        std::cout<<"Derive destructor is called!"<<std::endl;
      }
    };
    
    int main()
    {
      - 派生类的析构函数会调用基类的析构函数
        打印:Base default constructor is called!
            Derive destructor is called!
            Base destructor is called!
      Derive d;//默认构造
    }
    
  • 派生类的其它构造函数将隐式调用基类的缺省构造函数

    #include<iostream>
    #include<vector>
    #include<string>
    #include<memory>
    struct Base
    {
      Base()
      {
        std::cout<<"Base default constructor is called!"<<std::endl;
      }
      Base(int)//只是对当前类有意义
      {
        std::cout<<"Base constructor 2 is called!"<<std::endl;
      }
      Base(const Base& val)
      {
        std::cout<<"Base copy constructor is called!"<<std::endl;
      }
      Base& operator=(const Base&)//赋值函数
      {
        std::cout<<"Base assignment is called!"<<std::endl;
        return *this;
      }
      ~Base()
      {
        std::cout<<"Base destructor is called!"<<std::endl;
      }
    };
    
    struct Derive : Base
    {
      Derive(int)
      {
      }
    };
    
    int main()
    {
      - 派生类的其它构造函数将隐式调用基类的缺省构造函数
        打印:Base default constructor is called!
            Base destructor is called!
        注意不是Base constructor 2 is called!
      Derive d(3);
    }
    
    #include<iostream>
    #include<vector>
    #include<string>
    #include<memory>
    struct Base
    {
      Base()
      {
        std::cout<<"Base default constructor is called!"<<std::endl;
      }
      Base(int)//只是对当前类有意义
      {
        std::cout<<"Base constructor 2 is called!"<<std::endl;
      }
      Base(const Base& val)
      {
        std::cout<<"Base copy constructor is called!"<<std::endl;
      }
      Base& operator=(const Base&)//赋值函数
      {
        std::cout<<"Base assignment is called!"<<std::endl;
        return *this;
      }
    };
    
    struct Derive : Base
    {
      Derive() = default;
      Derive(const Derive&)//此时不是default
      {
      }
    };
    
    int main()
    {
      - 派生类的其它构造函数将隐式调用基类的缺省构造函数
        打印:Base default constructor is called!
            Base default constructor is called!
        注意不是Base constructor 2 is called!
      Derive d;//Base default constructor is called!
      Derive x(d);//Base default constructor is called!
    }
    
  • 所有的特殊成员函数在显式定义时都可能需要显式调用基类相关成员

    #include<iostream>
    #include<vector>
    #include<string>
    #include<memory>
    struct Base
    {
      Base()
      {
        std::cout<<"Base default constructor is called!"<<std::endl;
      }
      Base(int)//只是对当前类有意义
      {
        std::cout<<"Base constructor 2 is called!"<<std::endl;
      }
      Base(const Base& val)
      {
        std::cout<<"Base copy constructor is called!"<<std::endl;
      }
      Base& operator=(const Base&)//赋值函数
      {
        std::cout<<"Base assignment is called!"<<std::endl;
        return *this;
      }
    };
    
    struct Derive : Base
    {
      Derive()
        :Base(0)//显示调用
        {
        }
      Derive(const Derive& input)
        : Base(input)//显示调用
      {
      }
    };
    
    int main()
    {
      - 所有的特殊成员函数在显式定义时都可能需要显式调用基类相关成员
        打印:Base constructor 2 is called!
            Base copy constructor is called!
      Derive d;//Base constructor 2 is called!
      Derive x(d);//Base copy constructor is called!
    }
    
  • 构造与销毁顺序–>符合常规习惯

    • 基类的构造函数会先调用,之后才涉及到派生类中数据成员的构造
    • 派生类中的数据成员会被先销毁,之后才涉及到基类的析构函数调用

5.类的继承—补充知识

2.5.1.public 与 private 继承(参考资料

  • public 继承:描述 “是一个” 的关系。

  • private 继承:描述 “根据基类实现出” 的关系。
    对于private继承,基类也随即变为private,对于这种情况,更加通用的情况是直接将基类声明为子类的私有成员变量,而不是通过继承来实现.

  • protected 继承:几乎不会使用。

所以,绝大部分时候都是使用public继承。

5.2.using 与继承

  • 使用 using 改变基类成员的访问权限

    • 派生类可以访问该成员(基类中是private的属性,无法改变)
    • 无法改变构造函数的访问权限
    #include<iostream>
    
    struct Base
    {
    public:
      int x;
      void fun(int){}
    private:
      int y;
    protected:
      int z;
      void fun(){}//重载
      Base(int){}//构造函数
    };
    
    struct Derive : public Base
    {
    public:
      using Base::z;//使用using改变基类的访问权限(改为当前权限域的权限类型:将z改为public)
      using Base::Base;//报错:无法改变构造函数的访问权限
    private:
      using Base::x;//使用using改变基类的访问权限(改为当前权限域的权限类型:将x改为private)
      using Base::fun;//对所有基类中的fun()函数全部变为private
    private:
      using Base::y;//报错,因为y在基类属于private,y对于子类不可见,也就没办法对它进行操作
    
    };
    int main()
    {
      Derive d;
      d.z;//可访问
      d.x;//不可访问
    }
    
  • 使用 using 继承基类的构造函数逻辑

    #include<iostream>
    
    struct Base
    {
    public:
      Base(int){}//构造函数
    };
    
    struct Derive : public Base
    {
    public:
      using Base::Base;//使用 using 继承基类的构造函数逻辑,而无需重写
    };
    int main()
    {
      Derive d(100);
    }
    
  • using 与部分重写

    这是正常的逻辑

    #include<iostream>
    
    struct Base
    {
    protected:
      virtual void fun(){
        std::cout<<"1\n";
      }    
      virtual void fun(int){
        std::cout<<"2\n";
      }
    };
    
    struct Derive : public Base
    {
    public:
      using Base::fun;
    };
    int main()
    {
      Derive d;
      d.fun();//1
      d.fun(3);//2
    }
    

    如果我们想重写fun(int)函数,该如何处理呢:

    #include<iostream>
    
    struct Base
    {
    protected:
      virtual void fun(){
        std::cout<<"1\n";
      }    
      virtual void fun(int){
        std::cout<<"2\n";
      }
    };
    
    struct Derive : public Base
    {
    public:
      using Base::fun;
      //重写fun(int)
      void fun(int) override{
        std::cout<<"3\n";
      }
    };
    int main()
    {
      Derive d;
      d.fun();//1
      d.fun(4);//3
    }
    

    延伸:

    #include<iostream>
    
    struct Base
    {
    protected:
      Base(){}//缺省构造函数
    };
    
    struct Derive : public Base
    {
    public:
      using Base::base;
      //Derive() = default;
    };
    int main()
    {
      Derive d;//不是改变了Base()的权限,而是系统构造了缺省的构造函数,去调用了基类的构造函数
    }
    
  • 继承与友元:友元关系无法继承,但基类的友元可以访问派生类中基类的相关成员

  • 通过基类指针实现在容器中保存不同类型对象

    #include<iostream>
    
    struct Base
    {
    };
    
    struct Derive : public Base
    {
    };
    int main()
    {
      Derive d;
      Base* ptr = &d;//使用基类指针,指向派生类地址
    }
    
    • 数组,或容器,保存的是类型一致的数据,但是我们能不能使用这些容器保存数据类型不一样的东西呢,实际上是可以做到的:
       #include<iostream>
      #include<memory>
      #include<vector>
      struct Base
      {
        //还是有一定的限制,只能统一返回double,如果是string就没办法了
        virtual double GetValue() = 0;
        virtual ~Base() = default;
      };
      
      struct Derive : public Base
      {
        Derive(int x)
          : val(x){}
          double GetValue() override{
            return val;
          }
        int val;
      };
      struct Derive2 : public Base
      {
        Derive2(double x)
          : val(x){}
        double GetValue() override{
          return val;
        }
        double val;
      };
      int main()
      {
        //std::vector<Base*>
        std::vector<std::shared_ptr<Base>> vec;
        vec.emplace_back(new Derive(1));
        vec.emplace_back(new Derive2(3.2));
      
        std::cout<< vec[0]->GetValue()<<std::endl;    //1
        std::cout<< vec[1]->GetValue()<<std::endl;    //3.2
      }
    
  • 多重继承与虚继承

    #include<iostream>
    struct Base
    {
      virtual ~Base() = default;
      int x;
    };
    
    struct Base1 :Base
    {
      virtual ~Base1() = default;
    };
    
    struct Base2 :Base
    {
      virtual ~Base2() = default;
    };
    
    //多重继承在实际应用中,用得还是相对较少
    struct Derive : public Base1, public Base2
    {
    
    };
    
    int main()
    {
      Derive d;
      d,x;//报错:当前类不包含,基类也不包含,系统无法知道是通过Base1去继续找x,还是通过Base2去继续往上找x.
    }
    

    解决:虚继承

    #include<iostream>
    struct Base
    {
      virtual ~Base() = default;
      int x;
    };
    
    struct Base1 : virtual Base
    {
      virtual ~Base1() = default;
    };
    
    struct Base2 : virtual Base
    {
      virtual ~Base2() = default;
    };
    
    //多重继承在实际应用中,用得还是相对较少
    struct Derive : public Base1, public Base2
    {
    
    };
    
    int main()
    {
      Derive d;
      d,x;//合法,通过虚函数表来理解
    }
    
  • 空基类优化与 属性 [[no_unique_address]]

    #include<iostream>
    struct Base
    {
      void fun(){}//此时加了一个函数,基类的大小同样是1 : 
    };
    
    struct Derive Base1
    {
      int x;
    };
    
    int main()
    {
      std::cout<<sizeof(Base)<<std::endl;//空基类:1
      std::cout<<sizeof(Derive)<<std::endl;//4
    
    }
    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值