继承方式:
私有继承:has_a 关系
保护、公有:is_a 关系
隐藏:(有,看不见)继承关系下,不同的作用域。
覆盖:(不存在)派生类里面的同名函数把基类的覆盖了(相当于替换)
基类和派生类的相互指向
#include<iostream>
using namespace std;
class Base//这个类中,两个Show()是重载关系
{
public:
Base(int a) :ma(a)
{
cout << "Base::Base() " << endl;
}
~Base()
{
cout << "Base::~Base()" << endl;
}
protected://可以扩展,且不能在类外访问
int ma;
};
class Derive :public Base
{
public:
Derive(int b) :mb(b),Base(b)
{
cout << "Derive::Derive() " << endl;
}
~Derive()
{
cout << "Derive::~Derive()" << endl;
}
private:
int mb;
};
int main()
{
Base base(10);
Derive derive(20);
//Derive* pd = &base;// ×
Base* pb = &derive;// √
return 0;
}
解释:
解释:
假如把基类比作普通人,学生比作派生类,学生会做的事情一个普通的人并非会做,比如解高数。
Derive* pd = &base;// × 相当于把base(人)这个对象赋给pd(学生), 让一个普通人去做学生做的事情,比如解高数。
Base* pb = &derive;// √ 相当于把pd(学生) 这个对象赋给base(人), 让一个学生去做普通人做的事情,学生本身即是人继承来的,理所应当会。
同理:
Base& rb = derive; //√
//Derive& rd = base; // ×
总结:基类的指针或引用可以指向或者引用派生类对象,派生类的指针或引用不可指向或者引用基类对象。
观察下面的代码:
#include<iostream>
#include<string>
#include<vector>
using namespace std;
class Base
{
public:
Base(int a) :ma(a)
{
cout << "Base::Base() " << endl;
}
~Base()
{
cout << "Base::~Base()" << endl;
}
void Show()
{
cout << "Base::Show()" << endl;
cout << "ma " << ma << endl;
}
protected:
int ma;
};
class Derive :public Base
{
public:
Derive(int b) :mb(b),Base(b)
{
cout << "Derive::Derive() " << endl;
}
~Derive()
{
cout << "Derive::~Derive()" << endl;
}
void Show()
{
cout << "Derive::Show()" << endl;
cout << "ma " << ma << endl;
cout << "mb " << mb << endl;
}
private:
int mb;
};
int main()
{
cout << sizeof(Base) << endl;//4
cout << sizeof(Derive) << endl;//8
Base* pb = new Derive(10);
cout << typeid(pb).name() << endl;//class Base *
cout << typeid(*pb).name() << endl;//class Base
pb->Show();//Base::Show()
return 0;
}
打印结果:
若将Base里面的Show()函数修改如下:
virtual void Show()
{
cout << "Base::Show()" << endl;
cout << "ma " << ma << endl;
}
打印结果:
对比观察两个图:
virtual 关键字带来的改变:
基类中是虚函数,派生类中同名且参数类型相同的函数也是虚函数。
在基类中再加入这样的代码:
virtual void Show(int)
{
cout << "Base::Show(int)" << endl;
cout << "ma " << ma << endl;
}
虚函数表:
虚函数指针指向虚表,这里的RTTI指的是: runtime tyoe information 运行时类型信息
内存布局中,vfptr优先级高,这里的 0 指的是vfptr相对于内存布局的偏移,所以为0。
Base* pb = new Derive(10); *pb解引用,对象是派生类对象
这里派生类 &Base::Show是带有一个整形参数是继承基类得来的。
这里有没有奇怪?为什么只有一个虚函数指针?派生类的vfptr难道不该有两个吗?
解释:派生类的向基类中的合并,合并规则:沿着继承链。一个虚表对应一个类。
注意:虚函数表是在编译期间确定的,编译期间进行语法语义的分析,存放在 .rodata(只读数据段),运行时vfptr指向这个表。
虚函数的机制是为了实现动多态。
早绑定、晚绑定
普通函数调用 :call函数入口地址(编译期间确定)
虚函数调用: call寄存器(运行期间)
早绑定(静态绑定)(编译期间确定函数的调用)
晚绑定(动态绑定)(运行期间确定函数的调用)(动多态)
程序怎样拿到虚函数入口地址?
将虚函数的入口地址放到 .rodata 中, .rodata会加载到内存中,call eax时,可以找到函数入口地址。
哪些函数可以成为虚函数?
1)函数能取地址(内联函数不可以,不生成符号,无法取地址)
2)必须依赖对象调用(通过对象内存中的vfptr找vftable)(构造、static修饰的成员方法不可以)
3)析构函数,普通函数,可以设置为虚函数
动多态发生条件:
对象调用虚函数是静多态,指针调用虚函数是动多态且对象必须调用完整(也就是说,指针必须指向一个完整的对象,然后在在调用虚函数)。若在构造中调用虚函数,则对象不完整,为静多态。若在析构中调用虚函数,则对象不完整,为静多态。
观察下面的代码
#include<iostream>
#include<string>
#include<vector>
using namespace std;
class Base
{
public:
Base(int a) :ma(a)
{
cout << "Base::Base() " << endl;
}
~Base()
{
cout << "Base::~Base()" << endl;
}
virtual void Show()
{
cout << "Base::Show()" << endl;
cout << "ma " << ma << endl;
}
void Show(int a)
{
cout << "Base::Show(int)" << endl;
cout << "ma " << ma << endl;
}
protected:
int ma;
};
class Derive :public Base
{
public:
Derive(int b) :mb(b),Base(b)
{
cout << "Derive::Derive() " << endl;
}
~Derive()
{
cout << "Derive::~Derive()" << endl;
}
void Show()
{
cout << "Derive::Show()" << endl;
cout << "ma " << ma << endl;
cout << "mb " << mb << endl;
}
void Show(int a)
{
cout << "Derive::Show(int)" << endl;
cout << "ma " << ma << endl;
cout << "mb " << mb << endl;
}
private:
int mb;
};
int main()
{
Base* pb = new Derive(10);
pb->Show(); //动多态
delete pb;
return 0;
}
打印结果:
仔细观察,会发现没有派生类的析构。
delete pb; //pb->~Base(); //静态绑定 只释放了基类的,没有释放派生类自己的,内存泄漏。
将基类析构函数代码改为:
virtual ~Base()
{
cout << "Base::~Base()" << endl;
}
继承关系中,基类的析构和派生类的析构是覆盖关系。(基类是虚析构,同名的派生类中析构也将变为虚析构)
调用派生类析构时,系统自动调用基类析构。
打印结果: