基类和派生类的关系
基类和派生类不是2个独立的类型,派生类是一个基类的类型,它们2个不是分开的,当我们创建一个派生类的时候,派生类中的数据member应该如下
BaseClass : [Base Data]
DerivedClass : [Base Data][Derived Data]
派生类的数据类型附加于基类之后
当我们给一个基类类型指向派生类的时候,这个基类指针访问的是派生类继承基类的那部分数据,就算我们在派生类中重新更改继承而来的基类类型,基类指针访问的还是原来的数值,如下
#include <iostream>
using namespace std;
class Base{
public:
int Base_value1 = 1;
int Base_value2 = 2;
};
class Derived : public Base{
public:
int Derived_value1 = 3;
int Derived_value2 = 4;
int Base_value1 = 10;
};
int main()
{
Base* b = new Derived;
cout<< b->Base_value1 << "\n";
return 0;
}
我们的输出结果如下为1,说明访问的还是base的变量
1
为什么是这样?我们直接看derived对象的内存layout(栈空间),如下图所示
base::base_value1 base::base_value2 derived::derived_value1 derived::derived_value2 derived::base_value1
我们可以知道derived对象的栈内存中其实有父类的public and private成员,且在栈中标明范围,所以在derived中创建一个名字和base一模一样的变量不会引起冲突或者说覆盖,更多信息
所以我们创建一个derived类对象,并且将其赋值给父类其实访问的是父类对应的数据(父类public数据也存在于派生类对象中)
假如我们想做到这个Base 指针访问Derive特有的成员而不是访问从自己身上继承而来的,这个时候虚函数就派上用场了!!!我们在Base中定义一个纯虚函数,然后Derive继承他,并且重载他,因为纯虚函数本来就被Derive继承,本来存于Derive继承Base的member区域中,Derive这个区域中的虚函数是一个虚表指针,虚表指针直接指向这个obj的虚表,这个虚表里面才是具体执行函数的地址,并且Derive进行了override,所以Derive继承而来的虚函数最终指向的是这个Derive override后的函数,如下
#include <iostream>
using namespace std;
class Base{
int ii;
public:
int i = 1;
Base(){};
virtual void print() = 0; //纯虚函数
};
void
Base::print(){
cout << "Base_Print!" << "\n";
}
class Derived : public Base{
public:
int i = 10;
void print() override;
};
void
Derived::print(){
cout << "Derived_Print!" << "\n";
}
int main()
{
Base* b = new Derived;
b->print();
cout << b->i << endl;
return 0;
}
我们来看上面derived类对象的内存layout
stack
------------------
base::i
base::_vptr--------------------------------->dereive::print() //此处已经被重载成derive的print()
derived::i
------------------
.
.
.
.
.
.
text
------------------
base::base()
base::print()
derive::print()
所以我们上述的代码虽然我们的derive指向了base指针,但是打印的是derive重载的结果(假如derive没有继承那么打印b的源函数和变量,比如i没有被derive继承,所以不会被override那么b打印base的i,函数同理)
小结:
当我们的derive对象指向base指针的时候,derive继承base的函数成员或者变量成员会在打印的时候打印derive 对象override的内容或者值,假如derive没有继承base的函数或者变量,并且我们打印对应的值或者指向对应的函数,会执行base的值或者函数。
扩展
我们再考虑以下的代码
#include <iostream>
using namespace std;
class base{
//do something
};
class derive : public base{
public:
~derive(){
cout << "delete derive" << endl;
}
void print(){ cout << "print derive" << endl; }
};
int
main(){
base* b = new derive();
// b->print(); //会报错因为虽然是derive对象,但是derive对象赋值给base指针,且base指针没有print函数,所以报错
delete b; //undefined behavior!!!
}
为什么delete b
是undefined behavior?因为删除b的时候不会触发derive的析构函数,但是base又没有定义析构函数,为什么不会触发derive的析构函数?因为我们虽然是derive的对象,其本质是指向base指针,所以删除b的时候应该调用b的析构函数
那么我们应该这样做
#include <iostream>
using namespace std;
class base{
//do something
public:
base(){
cout << "construct base" << endl;
}
virtual ~base(){
cout << "delete base" << endl;
}
};
class derive : public base{
public:
derive(){
cout << "construct derive" << endl;
}
~derive(){
cout << "delete derive" << endl;
}
void print(){ cout << "print derive" << endl; }
};
int
main(){
base* b = new derive();
// b->print(); //会报错因为虽然是derive对象,但是derive对象赋值给base指针,且base指针没有print函数,所以报错
delete b; //undefined behavior!!!
}
结果如下
construct base
construct derive
delete derive
delete base
综上所述我们还额外发现创建派生类的时候除了派生类会构造,基类也会构造,假如我们将派生类的对象赋值给基类的指针会按照我们上面总结的规律进行cover,假设我们不给base的析构函数加上virtual也意味着派生类不会继承,所以我们删除的时候只会调用基类的析构函数,而派生类的析构函数虽然在内存中但是对于对象是不可访问的,从而不会调用派生类的析构函数,那么对应的内存空间就不会被清除干净,假设我们加上virtual,派生类的析构函数被重载(对对象可见),且基类的析构函数也是可见的