继承中的父类相关知识
父类的构造函数
创建对象的时必须同时创建父类和包含于其中的对象。c++定义了如下的创建顺序:
- 如果某个类具有基类,则执行基类的默认构造函数。除非在初始化列表中调用了基类的构造函数,此时调用这个构造函数不是默认的构造函数。
- 类的非静态成员按照声明的顺序创建。
- 执行该类的构造函数。
可以递归的使用这些规则。如果类有祖父类,祖父类就在父类之前初始化,以此类推。示例:
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
是一个随机值。所以一定要记住:调用基类构造函数之后才初始化数据成员。
父类的析构函数
由于析构函数没有参数,因此始终可以自动调用父类的析构函数。析构函数的调用顺序刚好与构造函数相反:
- 调用类的析构函数
- 销毁类的数据成员,销毁顺序与创建顺序相反
- 如果有父类,调用父类的析构函数
可以递归的使用这些规则,在链的最底层成员总是第一个被销毁。
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。