我们先了解derived class在调用constructor的时候,首先先调用的是其父类的构造函数
why?
首先我们的derived class obj中有所有的父类class的成员,在derived clas obj中其父类成员的可访问程度根据父类成员所在位置所定(子类对象可以访问父类的public和protected成员),所以在我们的子类构造的时候要先构造父类class(主要将其父类成员初始化)
没有构造函数编译器会默认分配一个编译器给你,其功能仅仅是初始化成员而已
如下图、
#include class Base{
public:Base(){ std::cout<< "constructor of Base " <<'\n'; }
};class Derived: public Base{
public:Derived(){ std::cout << "constructor of derived " << '\n'; }
};int main(){Derived* d1 = new Derived();return 0;
}
打印如下
constructor of Base
constructor of derived
当然我们构造子类的时候也可以选择性的调用父类构造函数
#include class Base{
public:Base(){ std::cout<< "constructor of Base " <<'\n'; }Base(int a) : Base_value(a) {std::cout << "constructor of base and init base_value and base_value is " << Base_value << '\n';}int Base_value;
};class Derived: public Base{
public:Derived(): Base(){ std::cout << "constructor of derived " << '\n'; }Derived(int a): Base(a){std::cout << "call second constructor of Base_Value" << '\n';}
};int main(){Derived* d1 = new Derived(100);// Derived obj;return 0;
}
我们上面讲到了子类中其实是有父类的成员,所以在构造子类的时候先调用父类的构造函数构造父类,有构造那么就有析构函数,析构的顺序正好相反,先析构子类,再析构父类如下
#include class Base{
public:Base(){ std::cout<< "constructor of Base " <<'\n'; }Base(int a) : Base_value(a) {std::cout << "constructor of base and init base_value and base_value is " << Base_value << '\n';}~Base(){std::cout << "destructor of Base" <<'\n';}int Base_value;
};class Derived: public Base{
public:Derived(): Base(){ std::cout << "constructor of derived " << '\n'; }Derived(int a): Base(a){std::cout << "call second constructor of Base_Value" << '\n';}~Derived(){std::cout << "destructor of Derived" << '\n';}
};int main(){Derived* d1 = new Derived(100);// Derived obj;std::cout << "---------------" << '\n';delete d1;return 0;
}
打印如下
constructor of base and init base_value and base_value is 100
call second constructor of Base_Value
---------------
destructor of Derived
destructor of Base
我们再看下面代码在delete derived class的时候只有base的destructor被执行,而derived class的destructor并没有被执行
#include class Base{
public:virtual void fun() {std::cout << "base fun" << std::endl; }~Base() {std::cout << "base destructor" << std::endl;}
};class Derive: public Base{
public:virtual void fun() { std::cout << "derived fun" << '\n';}~Derive() { std::cout << "Derive destructor" <<'\n'; }
};int main(){Base *b1 = new Base();Base *b2 = new Derive();b1->fun();b2->fun();delete b1;delete b2;}
打印如下
base fun
derived fun
base destructor
base destructor
为什么会这样?我们注意我们new完Derived class后将其赋值给Base class指针,此时我们new完Derived class生成的obj1虽然有derived class自有的成员变量,但是obj1归根结底是Base class的obj,所以不可访问Derived Class的额外成员这叫做object slicing
此时又有一个问题诞生,因为我们将子类的对象指针obj1赋值给父类,那么意味着obj1只能使用父类部分的成员,我们直接实例化父类这样实例化后的对象又比obj1占用的空间小,又简单不是挺好吗?为什么还要将子类对象obj1赋值给父类?这里有一个用处,就是父类中的一些成员是virtual,那么子类对象中父类vptr指向的函数为子类override后的地址,如下图
class X {int x;string str;public:X() {}virtual ~X() {}virtual void printAll() {}
};class Y : public X {int y;public:Y() {}~Y() {}void printAll() {}
};
Y memory layout
| | |------------------------------| <------ Y class object memory layout| int X::x |
stack |------------------------------|| | int string::len || |string X::str ----------------|| | char* string::str | \|/ |------------------------------| |-------|--------------------------|| X::_vptr |------| | type_info Y ||------------------------------| |--------------------------|| int Y::y | | address of Y::~Y() ||------------------------------| |--------------------------|| o | | address of Y::printAll() || o | |--------------------------|| o |
------|------------------------------|--------| X::X() | |------------------------------| | | X::~X() | ||------------------------------| | | X::printAll() | \|/ |------------------------------| text segment| Y::Y() ||------------------------------|| Y::~Y() ||------------------------------|| Y::printAll() ||------------------------------|| string::string() ||------------------------------|| string::~string() ||------------------------------|| string::length() ||------------------------------|| o || o || o || |
上图中Y继承了X,那么Y对象中应该含有X的vptr,上图中Y的确含有X的vptr,但是X的vptr不再指向X的函数而是指向class Y override后的函数
所以我们在析构函数中加上virtual关键字后子类对象memory layout中对应的父类vptr指针(子类没有定义virtual函数,所以子类memory layout中没有关于子类的vptr指针,如果子类对象obj1赋值给父类,那么obj1就会使用其内存中x的vptr指向的虚函数)指向的析构函数变成了子类override的那一个(父类对象memory layout中vptr还是父类virtual function的地址),相反如果我们的子类中没有对父类的虚函数重载定义,那么子类中的vptr指向的是父类函数的地址,然后编译器执行的时候会先在vptr中找对应的函数地址,因为我们子类没有重新定义父类虚函数,那么vptr中指向的还是父类对应的虚函数,则最后编译器执行的是父类的函数而不是子类的函数.
下一篇:暗什么不什么的成语