继承中的父类相关知识

文章详细阐述了C++中继承时父类构造函数和析构函数的调用顺序。在构造过程中,首先调用父类的构造函数(默认或指定),然后按顺序初始化成员变量;析构时,顺序相反,先析构派生类的数据成员,再调用父类的析构函数。强调了基类的析构函数声明为virtual的重要性,以确保多态删除对象时能正确调用派生类的析构函数。
摘要由CSDN通过智能技术生成

继承中的父类相关知识

父类的构造函数

创建对象的时必须同时创建父类和包含于其中的对象。c++定义了如下的创建顺序:

  1. 如果某个类具有基类,则执行基类的默认构造函数。除非在初始化列表中调用了基类的构造函数,此时调用这个构造函数不是默认的构造函数。
  2. 类的非静态成员按照声明的顺序创建。
  3. 执行该类的构造函数。

可以递归的使用这些规则。如果类有祖父类,祖父类就在父类之前初始化,以此类推。示例:

class Something_1
{
public:
    Something_1() { cout << "Something_1!" << endl; }
};

class Something_2
{
public:
    Something_2() { cout << "Something_2!" << endl; }
};

class Base
{
public:
    Base() { cout << "base!" << endl; }
};

class Derived : public Base
{
public:
    Derived() { cout << "Derived!" << endl; }

private:
    Something_2 m_s2;
    Something_1 m_s1;
};

int main()
{
    Derived MyD;

    return 0;
}

输出结果:

base!
Something_2!
Something_1!
Derived!

可以看到先初始化的基类的构造函数,在按数据成员的声明顺序依次创建,最后执行该类自己的构造函数。

注意:Base类的构造函数是自动调用的,C++将自动调用父类的默认构造函数(如果存在的话)。如果父类,没有默认的构造函数,或者存在默认构造函数但是希望使用其他的构造函数,可以在构造函数的初始化器中像初始化数据成员那样链接构造函数。示例:

class Base
{
public:
    Base() { cout << "base!" << endl; }
    Base(int i) { cout << "Base(int i)" << endl; }
};

class Derived : public Base
{
public:
    // 显示的调用基类的有参构造函数
    Derived() : Base(9) { cout << "Derived!" << endl; }

private:
    Something_2 m_s2;
    Something_1 m_s1;
};

注意:不能将派生类的数据成员作为基类的构造函数的参数

class Derived : public Base
{
public:
    Derived() : Base() { cout << "Derived!" << endl; }
    Derived(int i) : m_i{i}, Base{m_i} { cout << "Derived(int i)" << endl; }

private:
    int m_i;
    Something_2 m_s2;
    Something_1 m_s1;
};

代码虽然可以编译,但是传递给基类构造函数的m_i是一个随机值。所以一定要记住:调用基类构造函数之后才初始化数据成员。

父类的析构函数

由于析构函数没有参数,因此始终可以自动调用父类的析构函数。析构函数的调用顺序刚好与构造函数相反:

  1. 调用类的析构函数
  2. 销毁类的数据成员,销毁顺序与创建顺序相反
  3. 如果有父类,调用父类的析构函数

可以递归的使用这些规则,在链的最底层成员总是第一个被销毁。

class Something_1
{
public:
    Something_1() { cout << "Something_1!" << endl; }
    virtual ~Something_1() { cout << "~Something_1" << endl; }
};

class Something_2
{
public:
    Something_2() { cout << "Something_2!" << endl; }
    virtual ~Something_2() { cout << "~Something_2" << endl; }
};

class Base
{
public:
    Base() { cout << "base!" << endl; }
    Base(int i) : m_i{i} { cout << "Base(int i), " << m_i << endl; }
    virtual ~Base() { cout << "~Base" << endl; }

private:
    int m_i;
};

class Derived : public Base
{
public:
    Derived() : Base() { cout << "Derived!" << endl; }
    // Derived(int i) : m_i{i}, Base{m_i} { cout << "Derived(int i)" << endl; }
    virtual ~Derived() { cout << "~Derived" << endl; }

private:
    int m_i = 10;
    Something_2 m_s2;
    Something_1 m_s1;
};

int main()
{
    Derived MyD;
    cout << "---------destroy---------" << endl;
    // Derived MyD1{9};
    return 0;
}

输出结果,可以看到构造顺序与销毁顺序正好相反:

base!
Something_2!
Something_1!
Derived!
---------destroy---------
~Derived
~Something_1
~Something_2
~Base

值得注意的是,上述示例中的析构函数即使没有声明virtual,代码也是可以正确的运行的。但是,在某些情况下,则变得不正确了!例如,当使用Base对象的指针访问Derived对象,并delete对象的时候,就会出问题。

Base *pBase{new Derived()};
delete pBase;

去掉virtual后,只会执行Base的析构函数。并没有执行派生类的及其数据成员的析构函数。所以析构函数声明为virtual是明智的做法。编译器默认生成的析构函数是不带virtual的。

值得说明的是:不一定非要将所有析构函数都声明为virtual,只需要将基类的析构函数声明为virtual即可,派生类的析构函数会自动“虚化”。如果将所有的析构函数都声明为virtual,因为底层实现是创建虚函数表,所以会增加代码的体积,所以不建议将所有的析构函数都声明为virtual。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值