C++ 类继承:构造函数与析构函数调用顺序,派生类和基类之间的特殊关系,公有继承及其他

一、派生类构造函数与基类构造函数

派生类的构造函数做了如下事情:

  1. 创建基类对象
  2. 派生类构造函数通过成员初始化列表基类信息传递给基类构造函数
  3. 派生类构造函数初始化派生类新增的数据成员

二、创建与销毁派生类对象时,构造函数和析构函数的调用

创建派生类对象时

  1. 程序调用基类构造函数

    • 初始化继承的数据成员

    • 使用初始化器列表语法指明要使用的基类构造函数

      Derived::Derived(type1 x, type2 y):Base(x,y) //初始化列表
      {
          ...
      }
      
    • 若无初始化器列表语法,则使用默认的基类构造函数

  2. 程序调用派生类构造函数

    • 初始化新增的数据成员

销毁派生类对象时

  1. 程序调用派生类的析构函数
  2. 程序调用基类的析构函数

三、派生类和基类之间的特殊关系

  1. 基类指针可在不进行显式转换的情况下指向派生类对象

    基类引用可在不进行显式转换的情况下引用派生类对象

    基类指针或引用只能调用基类方法,不能调用派生类方法

    Derived derived;
    Base *basePointer = &derived;
    Base &baseReference = derived;
    

    一般要求引用和指针类型与赋给的类型相匹配,这一规则对继承来说是例外。

    然而此例外是单向的,即不可以将基类对象和地址赋给派生类引用和指针。

    Base base;
    //Derived *derived = &base;   Not Allowed
    //Derived &derived = base;    Not Allowed
    
  2. 派生类对象可以使用基类的方法,条件是此方法不是私有的

    class Base{
        private:
        int number;
        void privateMethod(){
            
        }
        public:
        void publicMethod(){
            
        }
    }
    
    class Derived: public Base{
        
    }
    
    Derived derived;
    //derived.privateMethod();   Not allowed
    derived.publicMehod();     //Allowed
    

四、公有继承

(一)、何为公有继承

公有继承为 is-a-kind-of 关系,通常使用术语 is-a 。如从 Fruit 类中派生出 Banana 类,即应采用公有派生,因为 Banana is a kind of fruit

但公有继承不建立 has-a 关系,不建立 is-like-a 关系,不建立 is-implemented-as-a 关系,不建立 uses-a 关系。因此以下例子都不应该采用公有继承。

Fruit->Lunch
Shark->Lawyer
Array->Stack
Computer->Printer
(二)、多态公有继承

实现多态公有继承有两种方法:

  1. 在派生类中重新定义基类的方法
  2. 使用虚函数

以下例子将说明这两种方法的使用,以及它们的不同之处:

class Base {
  private:
    int number1;

  public:
    Base(int number1 = 0);
    virtual void show1(){};
    void show2(){}
    virtual ~Base(){}
}

class Derived : public Base
{
  private:
    int number2;

    public:
    Derived(const Base &b,int number2 = 0);
    Derived(int number1 = 0, int number2 = 0);
    virtual void show1(){}
    void show2(){}
}

int main()
{
    Base base(0);
    Derived derived(0,0);

    //此处代码将演示第一种方法
    //在派生类中重新定义基类的方法,使用对象类型确认使用哪个版本
    //此处 base 和 derived 的类型不同
    base.show();      //use Base::show()
    derived.show();   //use Derived::show()

    //此处代码将演示第二种方法,并说明虚函数与第一种方法的区别
    //此处 base1 和 base2 的类型相同
    Base &base1 = base;
    Base &base2 = derived;

    //使用了virtual,程序将根据引用或指针 **指向的对象** 去选择相应的方法
    base1.show1();    //use Base::show1()
    base2.show1();    //use Derived::show1()

    //没有使用virtual,程序将根据 **引用类型** 或 **指针类型** 去选择相应的方法
    base1.show2();    //use Base::show2()
    base2.show2();    //use Base::show2()

    return 0;
}

另外,注意到了,上述程序使用了虚析构函数。

如果析构函数不为虚,则将只调用对应于指针类型的析构函数。这意味着对于 base1base2 ,二者均只有 Base 的析构函数被调用,即使base2指针指向的为一个Derived对象。

如果析构函数为虚,则将调用对应于指针所指对象类型的析构函数。这意味着对于 base1base2base1会调用Base的析构函数,而base2会先调用Derived的析构函数,再调用Base的析构函数。

(三)、虚函数的工作原理

调用虚函数时:

  1. 程序将查看存储在对象中的vtbl(vitual function table)地址。vtbl 为虚函数表,是对象的一个隐藏成员里保存着的指针。
  2. 程序转向相应的函数地址表。
  3. 程序将使用函数地址表里的地址,执行具有该地址的函数。

如下图所示。

kOG0m9.png

(四)、虚函数注意事项
  1. 在基类方法声明中,使用virtual关键字,可使该方法在基类以及所有派生类中是虚的。包括从派生类里面派生出来的类。

  2. 若使用指向对象的引用或指针来调用虚方法,则程序进行动态联编(亦称晚绑定),将使用为对象类型定义的方法,而不适用为指针或引用类型定义的方法。

    通过晚绑定,基类指针或引用可以指向派生类对象。

  3. 如果定义的类被用作基类,则应将那些要在派生类中重新定义的类方法声明为虚的。

  4. 以下内容一定要理解并记住

    1. 构造函数不可是虚函数

    2. 析构函数应当是虚函数,除非类不用做基类。但即使类不做基类,将类的析构函数声明为虚也并非错误,只不过可能牺牲效率

    3. 友元friend不能是虚函数。只有类成员才能是虚函数。而友元并不是类成员

    4. 若派生类没有重新定义函数,则将使用该函数的基类版本

    5. 在派生类里重新定义函数,并不是重载。此举将隐藏方法。

      class Base{
          private: int num;
          public: virtual void show(int i)const{}
      }
      class Derived: public Base{
          public: virtual void show()const{}
      }
      Derived derived;
      derived.show();      //valid
      //derived.show(2);   invalid
      

五、访问控制:protected

派生类可直接访问基类的保护成员,但不能直接访问基类的私有成员。

在类外,只能用公有类成员去访问protected的类成员。

六、抽象基类

当类声明包含纯虚函数时,不能创建该类的对象。

猜你喜欢

转载自blog.csdn.net/LonelyPlanet_/article/details/88109839