目录
一、什么是多态
不同对象完成同一事件产生出不同的结果就被称作多态
就好比买票这个事件,成人、学生、军人购买时的顺序不同购买所付金额也不同;
成人无疑是排队原价购买,学生则是硬座半价,军人则可以优先买票
这就是多态
二、多态的定义及实现
1、多态的构成条件
在继承中要构成多态的两个条件:
1.必须通过基类的指针或者引用调用虚函数
2.被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
最终根据调用对象的不同所产生的结果也就不同
2、虚函数
被virtual修饰的成员函数就是虚函数
class A
{
public:
virtual void Printf() const
{
cout << "a"<< endl;
}
};
3、虚函数的重写
虚函数的重写要满足三同:参数相同,返回值相同,函数名相同
class A
{
public:
//父类的虚函数
virtual void Printf() const
{
cout << "a"<< endl;
}
};
class B : public A
{
public:
//子类对父类的虚函数重写
//子类的virtual可以不加
virtual void Printf() const
{
cout << "b" << endl;
}
};
虚函数重写的两个例外
1、协变
即基类与虚函数返回值类型不同,但是返回值类型必须是构成父子关系指针或者引用(同时是指针 或 同时是引用)
2、析构函数的重写
析构函数的重写,基类和派生类的析构函数名不同
class A
{
public:
//虚函数
virtual ~A()
{
cout << "~A()" << endl;
}
};
class B :public A
{
public:
//虚函数重写
virtual ~B()
{
cout << "~B()" << endl;
}
};
int main()
{
A* a1 = new A;
A* a2 = new B;
delete a1;
delete a2;
return 0;
}
4、重载、重写、重定义对比
三、虚函数表
在32位的机器下虚函数会在内存中多存一个指针指向虚函数表
如果父类中的虚函数没有被重写,那么派生类的虚函数表还是指向基类的虚函数;如果重写了,则指向重写的虚函数。
虚函数表存储的位置,通过一段代码来进行验证
class A
{
public:
virtual void func()
{
cout << "A->func()" << endl;
}
virtual void Func()
{
cout << "A->Func()" << endl;
}
int _a;
};
class B :public A
{
public:
virtual void func()
{
cout << "B->func()" << endl;
}
};
void Print(A a)
{
a.func();
}
int main()
{
A aa;
B bb;
int a = 0;
printf("栈:%p\n", &a);
static int b = 0;
printf("静态区:%p\n", &b);
int* p = new int;
printf("堆:%p\n", p);
const char* str = "hello";
printf("常量区:%p\n", str);
//前四个字节,一定是虚表的地址
printf("虚表a:%p\n", *((int*)&aa));
printf("虚表b:%p\n", *((int*)&bb));
}
上述代码的运行结果
又图可知虚表的存储位置与常量区(代码段)的位置最近所以推测出虚表是存储在常量区(代码段) 推测依据:相同空间存储的数据相隔不会太远
四、多态的原理
有了虚表的概念,这我们就能理解,为什么构成多必须是通过基类的指针或引用调用虚函数。因为只有父类的虚表才能既能指向父类,又能指向子类。
那这里还有一个问题就是,为什么必须是指针或引用呢?
class A
{
public:
virtual void func()
{
cout << "A->func()" << endl;
}
virtual void Func()
{
cout << "A->Func()" << endl;
}
int _a;
};
class B :public A
{
public:
virtual void func()
{
cout << "B->func()" << endl;
}
};
void Print(A a)
{
a.func();
}
int main()
{
A a;
a._a = 1;
B b;
b._a = 10;
a = b;
A* pa = &b;
A& ref = b;
}
这段代码调试发现,子类赋值给父类,父类会进行切片,这里值会拷贝过去,但是虚表并不会拷贝;因为如果拷贝了虚表的话,这样父类对象中的虚表指向的是父类还是子类就混淆了
五、多继承中虚表的关系
#include <iostream>
#include <string>
using namespace std;
typedef void(*FUNC_PTR) ();
void PrintVFT(FUNC_PTR* table)
{
for (size_t i = 0; table[i] != nullptr; i++)
{
printf("[%d]:%p->", i, table[i]);
FUNC_PTR f = table[i];
f();
}
printf("\n");
}
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() {cout << "Derive::func1" << endl;}
virtual void func3() {cout << "Derive::func3" << endl;}
private:
int d1;
};
int main()
{
Derive d;
int vft1 = *((int*)&d);
Base2* ptr = &d;
int vft2 = *((int*)ptr);
PrintVFT((FUNC_PTR*)vft1);
PrintVFT((FUNC_PTR*)vft2);
return 0;
}
上述代码打印的fun1的地址不一样,都是重写的为啥会不同,接下来我们意义剖析
通过反汇编的底层代码可知虽然一开始显示的地址不同但最终却是相同的