多继承
多重继承可以使得子类拥有多个父类特性的成员,
多继承例子:
#include <iostream>
using namespace std;
class M
{
public:
M(int i)
:_data(i){}
int _data;
};
class X:public M
{
public:
X(int d)
:M(d){}
void setData(int i)
{
_data = i;}
};
class Y:public M
{
public:
Y(int d)
:M(d){}
int getData()
{
return _data;
}
};
class Z:public X,public Y
{
public:
Z():X(2),Y(3){}
void dis()
{
//cout<<_data<<endl; 直接访问_data会出错,因为存在多份数据
cout<<X::_data<<endl;
cout<<Y::_data<<endl;
}
};
int main()
{
Z z; //Z拥有了X和Y的特性
z.dis();
z.setData(2000);//使用X中的setdata函数
cout<<z.getData()<<endl;//Y中getdata函数
return 0;
}
但多个重名函数可能会出现不必要的开销。考虑以下这种情况。
A是祖父类,B,C是D的父类。此时在D中会保存两份A的数据成员,这样就造成了内存浪费,而且调用麻烦,由于有多份同名成员,所以需要指明作用域。
解决方法: 让B,C虚继承于A,此时A是虚基类,D正常继承于B,C;则D只保存了一份A的数据成员。
如图:
虚继承的意义
在多继承中,保存共同基类的多份同名成员,虽然有时是必要的,可以在不同的数
据成员中分别存放不同的数据,但在大多数情况下,是我们不希望出现的。因为保留多
份数据成员的拷贝,不仅占有较多的存储空间,还增加了访问的困难。
为此,c++提供了,虚基类和虚继承机制,实现了在多继承中只保留一份共同成员。
虚继承是继承的一种拓展
多态
多态是指代码中的同一种行为程序有不同的响应:如同样一个点击事件,如果点击的是exe,则响应是运行这个exe,而如果点击的是一个文本文件,则响应是打开文件。
多态包括
重载(overload):在编译阶段通过倾轧的方式区分不同的函数体,决定运行哪一个函数。
模板:模板与重载实现方式类似,都称为静多态。
动多态:虚函数在运行时决定运行哪一个函数。
这里主要讲动多态
解释
虚函数是指这个函数的签名。
而 这个虚函数在类中的实现不是,她是一个实实在在的成员函数。
和普通的函数一样的。
如果这个函数是虚函数,此时编译器会把其地址放在虚表中。
所以这个函数有两个入口。
第一个就是你这样呼叫,和普通的成员函数一致。
第二个是通过引用呼叫,此时编译器会通过虚表来呼叫。
此时需要添加一个修正 this 指针的转换函数。
赋值兼容
虚函数的基础是赋值兼容
赋值兼容的规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代。赋值兼容是一种默认行为,不需要任何的显示的转化步骤。
赋值兼容规则中所指的替代包括以下的情况:
1、派生类的对象可以赋值给基类对象。
2、派生类的对象可以初始化基类的引用。
3、派生类对象的地址可以赋给指向基类的指针。
在替代之后,派生类对象就可以作为基类的对象使用,但只能使用从基类继承的成员。
动多态条件
1、基类有虚函数
2、派生类重写(override)了基类虚函数。(要求函数名,返值类型,函数参数个数及类
型全部匹配。并根据派生类的需要重新定义函数体。可以为任意访问权限)
3、通过基类指针或引用指向派生类对象,调用公共接口(虚函数)
例子:
#include <iostream>
using namespace std;
class Base
{
public:
virtual void func()
{
cout<<"Base"<<endl;
}
};
class Derive:public Base
{
private:
//子类virtual可写可不写,推荐写上,注明是虚函数
void func()
{
cout<<"Derive"<<endl;
}
};
int main()
{
Derive d;
// d.func();
Base*p = &d;
p->func();//访问的是子类fuunc函数
return 0;
}
原理
带虚函数的会在对象的首地址存储一个虚函数表的入口地址(一个函数指针),虚函数表中记录了这个类每个虚函数的入口地址,而如果重写了父类函数,则在这个表中只保存了子类的虚函数称为子类覆盖(override)了父类的虚函数,此时通过父类指针访问子类对象,即动多态,访问的则是子类虚函数。
另外覆盖了父类函数并不代表无法访问父类的虚函数,你可以通过父类对象调用,或者显式指明父类作用域的虚函函数,因为虚函数表,动多态的存在只是提供了一个额外的入口。