多态是什么
通俗的将多态就是多中形态 , 具体讲就是去完成某个行为时不同的对象去完成是会产生不同的状态, 就像买票这一件事情, 成人去买票就是全票, 学生去买票就是半票, 不同的对象去完成同一件事情, 产生的状态是不同的
在C++中, 就体现在不同的但有继承关系的类的对象, 去调用同一个对象时, 产生了不同的行为
如下代码中同样的接口在不同的对象去调用的时候, 打印的效果是不同的
class person{
public:
virtual void BunTicket(){
cout<<"成人购买全票"<<endl;}
};
class student:public person{
public
virtual void BunTicket(){
cout<<"学生购买半票"e<<endl;
}
};
实现多态的条件
多态实现的条件有三个
- 满足继承关系
- 函数声明是必须是虚函数 , 并且虚函数完成复写
- 调用函数的类型必须是指针或者引用
这三者缺一不可
虚函数
在上述代码中, 我们在BuyTicket函数之前加上了virtual关键字, 这样就将该函数声明成了一个虚函数
虚函数的复写 : 就是在派生类中和父类中拥有接口类型完全相同函数名相同, 参数相同, 返回值也相同的函数, 那么在这个派生类中就完成了该虚函数的复写, 如上述代码中student类中的BuyTicket就完成了复写
协变—函数复写的例外
函数的复写是有例外的, 这个例外就是协变, 协变是指函数返回值是拥有继承关系的类的指针或者引用
class A{};
class B: public A{};
class person{
public:
virtual A* BunTicket(){
cout<<"成人购买全票"<<endl;}
};
class student:public person{
public
virtual B* BunTicket(){
cout<<"学生购买半票"e<<endl;
}
};
这种情况下BuyTicket函数也会发生复写
对于第三个必要条件,是因为多态满足的是看对象, 而非多态满足的是看类型
void Func(Person& p){
p.BuyTicket();
}
void Func(Person p){
p.BuyTicket();
}
int main(){
student s;
person p;
Func(s);
Func(p);
return o;
}
在创建出一个一个person与student对象时 , 当Func函数中的参数为person类型时 对Func函数中传入student类型的对象时
当 Func 函数中的参数是一个 person 类型的引用时, 传入一个 student 类型的对象, 此时是 person 引用指向 S 对象,在调用BuyTicket函数时发生了切片操作. 这就是多态看对象
抽象类
纯虚函数
在大多数情况下 , 我们并不怎么去关心父类中被重写的函数的实现, 更多的是使用子类中的复写的函数, 因此只需要让父类提供一个接口, 让子类去实现打的个性化就可, 这种操作称为接口调用.
只需将父类中的接口赋值为0
class person{
public:
virtual void BuyTicket()=0;
};
此时我们将该函数称为纯虚函数, 包含纯虚函数的类称为抽象类,抽象类不能实例化一个对象
final与override关键字
final关键字在继承中使用时 , 使用的类可以被继承, 而在多态中, 父类中使用的函数则不能被子类重写
override关键字在子类中使用,使用的函数必须对父类中的函数经重写(强制重写父类中函数)
//在父类中使用-----体现实现继承
virtual void BuyTicket() final{
cout<<" 全票"<<endl;
}
//在子类中使用-----体现接口继承
virtual void BuyTicket() override{
cout<<" 半票"<<endl;
}
多态的实现原理
class Base{
private:
int _a=0;
public:
virtual void Fun(){
cout<<"haha"<<endl;
}
};
如上述代码所示, Base类的大小是多少呢?
通过测试,发现他的大小是 8 个字节, 其中包含了私有成员_a与一个_vfptr指针
打开监视,我们可以发现_vfptr是一个数组指针,他指向一个数组, 数组中存放的是Fun函数的地址,因此_vfptr是一个二级指针
class person {
public:
virtual void BuyTicket() {
cout << "买票-全价" << endl;
}
virtual ~person(){}
virtual void Fun(){}
void Fun1(){}
private:
int _id;
};
class student : public person {
public:
virtual void BuyTicket() {
cout << "买票-半价" << endl;
}
};
int main(){
person p;
student s;
system("pause");
return 0;
}
现将测试代码改为上述形式, 打开监视
发现父类的数组中, 有BuyTicket函数, 有析构函数,有Fun函数, 其中包含的是虚函数的地址, 因此称该表为虚表, 称_vfptr指针为虚表指针.
但是在子类的虚表中, 发现Fun函数的地址与父类的相同, 但是析构函数与BuyTicket函数的地址发生了变化.
一次推断出: 子类在继承父类的之后同样也将父类的虚表继承了过来, 但是当子类需要复写父类中的虚函数时, 在复写完成之后,就去修改自己虚表中的函数地址, 将原有的地址改为自己复写的函数地址
虚表保存在代码段上
单继承与多继承中的虚函数表
- 单继承中的虚函数表
class Base{
public:
virtual void Fun1(){
cout<<"Fun1()"<<endl;
}
virtual void Fun2(){
cout<<"Fun2()"<<endl;
}
private:
int _a;
};
class Derive:public Base{
public:
virtual void Fun1(){
cout<<"Derive::Fun1()"<<endl;
}
virtual void Fun2(){
cout<<"Fun2()"<<endl;
}
virtual void Fun3(){
cout<<"Fun3()"<<endl;
}
virtual void Fun4(){
cout<<"Fun4()"<<endl;
}
private:
int _b;
};
如上述代码中 , Derive与Base类是单继承关系, 其中在子类中Fun1函数构成多态, 进行了函数的重写, 并且子类中还拥有自己的虚函数Fun3与Fun4.
则子类的虚函数表如下图所示
通过观察, 继承与复写的函数都没有问题,遵循了上述的逻辑,但是子类中自己定义的虚函数却没有出现在监视中, 那是不是真的没有呢? 我们可以通过打印来看一看是否子类自己的虚函数是否在虚表中.(代码如下)
typedef void (*VFunptr)()
void print(VFunptr* VTable){
cout<<"虚函数表地址:\t"<<VTable<<endl;
for(;vfun != nullptr;){
cout << "第i个函数的地址:\t" << "i" << vfun << endl;
vfun();
++VTable;
vfun = *VTable;
i++;
}
}
通过执行该函数之后,我们发现子类自己两个虚函数,也存在与子类的虚表中, 只是VS编译器没有打印出来
- 多继承中的虚函数表
class Base1{
public:
virtual void fun1(){cout<<"fun1"<<endl;}
virtual void fun2(){cout<<"fun2"<<endl;}
private:
int a;
};
class Base2{
public:
virtual void fun1(){cout<<"student::fun1"<<endl;}
virtual void fun2(){cout<<"fun2"<<endl;}
private:
int b2;
};
class Derive:public Base1,public Base2{
virtual void fun1(){cout<<"Derive::fun1"<<endl;}
virtual void fun3(){cout<<"fun3"<<endl;}
};
入上述代码中, 分别有两个独立的类Base1与Base2, 这两个类中,用有一个同名的类fun2() , Derive类继承了这两个类, 其中对fun1()函数进行了重写, 并且还有一个属于自己的虚函数.
打开调试, 我们可以发现, 子类中拥有两个虚函数表, 并且虚函数表的中都对fun1()函数进行了复写. 但是通样的子类中自己的虚函数没有在监视中打印出来.
通过在终端的大印, 发现子类中自己的虚函数存在于继承Base1
中的虚函数表的最后位置,并且按照声明的顺序进行存放