目录
父类指针指向子类对象
父类指针指向子类对象,是安全的的(继承方式必须是public)
子类指针指向父类对象
子类指针指向父类对象是不安全的
struct Person
{
int m_age;
};
struct Student : Person
{
int m_score;
};
int main()
{
// 父类指针指向子类对象,是安全的的(继承方式必须是public)
Person *p = new Student();
p->m_age = 10;
//子类指针指向父类对象是不安全的
/*
Student *p =(Student * ) new Person();
p->m_age = 10;
p->m_score = 100; // 报错
*/
return 0;
}
多态
c++默认根据指针类型调用对应的函数
非多态demo
#无多态,c++默认根据指针类型调用对应的函数。
#include <iostream>
using namespace std;
struct Animal
{
void speak(){
cout << "Animal::speak()" << endl;
}
void run(){
cout << "Animal::run()" << endl;
}
};
struct Dog : Animal
{
//重写(复写,覆盖,override),重写要求与父类完全一样的函数
void speak(){
cout << "Dog::speak()" << endl;
}
//重载,
void speak(int age){
cout << "Dog::speak age :" << age << endl;
}
void run(){
cout << "Dog::run()" << endl;
}
};
struct Cat : Animal
{
void speak(){
cout << "Cat::speak()" << endl;
}
void run(){
cout << "Cat::run()" << endl;
}
};
void liu(Animal *p){
p->speak();
p->run();
}
int main()
{
liu(new Dog());//指针是Animal *p,默认调用Animal的函数
liu(new Cat());
return 0;
}
输出:
Animal::speak()
Animal::run()
Animal::speak()
Animal::run()
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
目的:传入的对象不同,实现的结果不同
c++中的多态通过虚函数实现(virtual修饰)
父类的函数是虚函数,子类重写的函数也是虚函数。
虚函数实现demo
#include <iostream>
using namespace std;
struct Animal
{
virtual void speak(){
cout << "Animal::speak()" << endl;
}
virtual void run(){
cout << "Animal::run()" << endl;
}
};
struct Dog : Animal
{
//重写(复写,覆盖,override),重写要求与父类完全一样的函数
void speak(){
cout << "Dog::speak()" << endl;
}
//重载,
void speak(int age){
cout << "Dog::speak age :" << age << endl;
}
void run(){
cout << "Dog::run()" << endl;
}
};
struct Cat : Animal
{
void speak(){
cout << "Cat::speak()" << endl;
}
void run(){
cout << "Cat::run()" << endl;
}
};
void liu(Animal *p){
p->speak();
p->run();
}
int main()
{
liu(new Dog()); //父类指针调用子类对象,利用父类指针调用(重写的)成员函数
liu(new Cat());
return 0;
}
结果:
Dog::speak()
Dog::run()
Cat::speak()
Cat::run()
虚函数实现原理
虚函数的实现原理是虚表,这个虚表里面存储着最终需要调用的虚函数地址,这个虚表也叫做虚函数表
Animal *cat = new Cat(); //初始化成员变量为0
Animal *cat = new Cat;//不初始化成员变量
汇编分析
struct Animal
{
int m_age;
virtual void speak(){
cout << "Animal::speak()" << endl;
}
virtual void run(){
cout << "Animal::run()" << endl;
}
};
struct Cat : Animal
{
int m_life;
void speak(){
cout << "Cat::speak()" << endl;
}
void run(){
cout << "Cat::run()" << endl;
}
};
int main()
{
Animal *cat = new Cat();
cat->m_age = 20;
cat->speak();
cat->run();
return 0;
}
// 调用speak 汇编分析
Animal *cat = new Cat();
cat->m_age = 20;
cat->speak();
//ebp-8是指针变量cat的地址
mov eax,dword ptr [ebp-8] //找到指针变量cat的地址,取出cat存贮的地址给eax,eax是Cat对象的地址值
mov edx,dword ptr [eax] //找到Cat对象的地址,取出前面4个字节(虚表的地址)给edx
mov eax,dword ptr [edx] // 根据Cat对象前面的四个字节存储数据(地址值:0x00B89B64,是虚表的地址),找到存储空间,取出虚表前面四个字节存储的数据(地址值:0x00B814E7)给eax
call eax //调用eax(0x00B814E7),即Cat:speak的调用地址
// 调用run 汇编分析
cat->run();
//ebp-8是指针变量cat的地址
mov eax,dword ptr [ebp-8] //找到指针变量cat的地址,取出cat存贮的地址给eax,eax是Cat对象的地址值
mov edx,dword ptr [eax] //找到Cat对象的地址,取出前面4个字节(虚表的地址)给edx
mov eax,dword ptr [edx+4] // 根据Cat对象前面的四个字节存储数据(地址值:0x00B89B64,是虚表的地址),找到存储空间,跳过虚表的4个字节后取出4个字节存储的数据(地址值:0x00B814CE)给eax
call eax //调用eax(0x00B814CE),即Cat:run的调用地址
内存分析
虚表(X86环境图)
内存地址 | 内存数据 | 内存地址 | 内存数据 | |||
cat | 0x00E69B60 | 0x00B89B64 | 虚表 | 0x00B89B64 | 0x00B814E7 | |
0x00E69B61 | 0x00B89B65 | |||||
0x00E69B62 | 0x00B89B66 | |||||
0x00E69B63 | 0x00B89B67 | |||||
&m_age | 0x00E69B64 | 20 | 0x00B89B68 | 0x00B814CE | ||
0x00E69B65 | 0x00B89B69 | |||||
0x00E69B66 | 0x00B89B6A | |||||
0x00E69B67 | 0x00B89B6B | |||||
&m_life | 0x00E69B68 | 0 | ||||
0x00E69B69 | ||||||
0x00E69B6A | Cat:speak的调用地址:0x00B814E7 | |||||
0x00E69B6B | Cat:run的调用地址:0x00B814CE |
cat指针指向的是cat对象存储空间,然后可找到12个字节,首先可以取出前面四个字节是虚表地址,然后根据地址找到虚表。
E8开头的机器指令是 直接call 地址
FF 开头的call,是从寄存器(rec)call。