美文网首页
09-虚表、抽象类和多继承

09-虚表、抽象类和多继承

作者: 一亩三分甜 | 来源:发表于2021-07-23 10:24 被阅读0次

《C++文章汇总》
上一篇介绍了引用和汇编《08-初始化列表、父类构造函数、虚函数和多态》,本文介绍虚表、抽象类和多继承。

1.虚表

◼ 虚函数的实现原理是虚表,这个虚表里面存储着最终需要调用的虚函数地址,这个虚表也叫虚函数表

struct Animal{
    int m_age;
    virtual void speak(){
        cout << "Animal::speak()" << endl;
    }
    virtual void run(){
        cout << "Animal::run()" << endl;
    }
};
struct Dog:Animal{
    void speak(){
        cout << "Dog::speak()" << endl;
    }
    void run(){
        cout << "Dog::run()" << endl;
    }
};
struct Cat:Animal{
    int m_life;
    void speak(){
        cout << "Cat::speak()" << endl;
    }
    void run(){
        cout << "Cat::run()" << endl;
    }
};
struct Pig:Animal{
    void speak(){
        cout << "Pig::speak()" << endl;
    }
    void run(){
        cout << "Pig::run()" << endl;
    }
};
void liu(Animal *p){
    p->speak();
    p->run();
}
int main(){
    Animal *cat = new Cat();
    cat->m_age = 20;
    cat->speak();
    cat->run();
    
    cat = new Pig();
    cat->speak();
    cat->run();
    return 0;
}
//输出
Cat::speak()
Cat::run()
Pig::speak()
Pig::run()

x86环境的虚表图


image

虚表汇编分析

//调用speak
Animal *cat = new Cat();
cat->speak();
//ebp-8是指针变量cat的地址
//eax存储的是cat对象的地址
mov eax,dword ptr [ebp-8]
//取出Cat对象的最前面的4个字节(虚表地址)给edx
//取出虚表的地址值给edx
mov edx,dword ptr [eax]
//取出虚表的最前面的4个字节给eax
//取出Cat::speak的函数调用地址给eax
mov eax,dword ptr [edx]

//call Cat::speak
call eax

//调用run
Animal *cat = new Cat();
cat->run();
//ebp-8是指针变量cat的地址
//eax存储的是Cat对象的地址
mov eax,dword ptr [ebp-8]

//取出Cat对象的最前面的4个字节(虚表地址)给edx
mov edx,dword ptr [eax]

//跳过虚表的最前面4个字节,取出4个字节赋值给eax
//取出Cat::run的函数调用地址给eax
mov eax,dword ptr [edx+4]
//call Cat::run
call eax

虚表内存分析

//Cat对象的地址
0x016DDB98
//虚表地址
0x00259b34
//虚表的内容
0x00251569
0x00251550

◼ 所有的Cat对象(不管在全局区、栈、堆)共用同一份虚表
子类中没有方法时,会调用父类中virtual修饰的方法,放入子类对象(cat对象)的前4个字节内存地址中

struct Animal{
    int m_age;
    virtual void speak(){
        cout << "Animal::speak()" << endl;
    }
    virtual void run(){
        cout << "Animal::run()" << endl;
    }
};
struct Dog:Animal{
    void speak(){
        cout << "Dog::speak()" << endl;
    }
    void run(){
        cout << "Dog::run()" << endl;
    }
};
struct Cat:Animal{
    int m_life;
//    void speak(){
//        cout << "Cat::speak()" << endl;
//    }
    void run(){
        cout << "Cat::run()" << endl;
    }
};
struct Pig:Animal{
//    void speak(){
//        cout << "Pig::speak()" << endl;
//    }
    void run(){
        cout << "Pig::run()" << endl;
    }
};
void liu(Animal *p){
    p->speak();
    p->run();
}
int main(){
    Animal *cat = new Cat();
    cat->m_age = 20;
    cat->speak();
    cat->run();
    
    cat = new Pig();
    cat->speak();
    cat->run();
    return 0;
}
//输出
Animal::speak()
Cat::run()
Animal::speak()
Pig::run()
图片.png

父类没有子类继承,有virtual修饰的方法函数,则也存在虚表,调用时会从虚表中查找调用函数的地址

struct Animal{
    int m_age;
    virtual void speak(){
        cout << "Animal::speak()" << endl;
    }
    virtual void run(){
        cout << "Animal::run()" << endl;
    }
};
int main(){
    Animal *ani = new Animal();
    ani->m_age = 20;
    ani->speak();
    ani->run();
}

子类中不存在virtual修饰的函数,仍存在虚表

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;
    }
};
struct WhiteCat:Cat{
    
};
int main(){
    Animal *ani = new WhiteCat();
    ani->speak();
    ani->run();
    
    return 0;
}
//输出
Cat::speak()
Animal::run()

若虚函数写在子类中,则从子类开始有虚表

struct Animal{
    int m_age;
    void speak(){
        cout << "Animal::speak()" << endl;
    }
    void run(){
        cout << "Animal::run()" << endl;
    }
};
struct Cat:Animal{
    int m_life;
    virtual void speak(){
        cout << "Cat::speak()" << endl;
    }
    virtual void run(){
        cout << "Cat::run()" << endl;
    }
};
struct WhiteCat:Cat{
    void speak(){
        cout << "WhiteCat::speak()" << endl;
    }
    void run(){
        cout << "WhiteCat::run()" << endl;
    }
};
int main(){
    Animal *ani = new WhiteCat();
    ani->speak();
    ani->run();
    
    Cat *cat = new WhiteCat();
    cat->speak();
    cat->run();
    return 0;
}
//输出
Animal::speak()
Animal::run()
WhiteCat::speak()
WhiteCat::run()

子类的函数是虚函数,要么是从父类重写,要么是自己在函数名前加virtual

2.调用父类的成员函数

先执行父类的重写的虚函数,C++没有super关键字,用父类的类名调用

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(){
        Animal::speak();
        cout << "Cat::speak()" << endl;
    }
    void run(){
        cout << "Cat::run()" << endl;
    }
};
int main(){
    Animal *ani = new Cat();
    ani->speak();
    ani->run();
    return 0;
}
//输出
Animal::speak()
Cat::speak()
Cat::run()

3.虚析构函数

◼ 如果存在父类指针指向子类对象的情况,应该将析构函数声明为虚函数(虚析构函数)
delete父类指针时,才会调用子类的析构函数,保证析构的完整性,先调用子类析构函数后调用父类析构函数

struct Animal{
    int m_age;
    virtual void speak(){
        cout << "Animal::speak()" << endl;
    }
    virtual void run(){
        cout << "Animal::run()" << endl;
    }
    Animal(){
        cout << "Animal::Animal()" << endl;
    }
    virtual ~Animal(){
        cout << "Animal::~Animal()" << endl;
    }
};

struct Cat:Animal{
    int m_life;
    void speak(){
        cout << "Cat::speak()" << endl;
    }
    void run(){
        cout << "Cat::run()" << endl;
    }
    Cat(){
        cout << "Cat::Cat()" << endl;
    }
    ~Cat(){
        cout << "Cat::~Cat()" << endl;
    }
};
int main(){
    Animal *ani = new Cat();
    delete ani;
    return 0;
}
//输出
Animal::Animal()
Cat::Cat()
Cat::~Cat()
Animal::~Animal()

4.纯虚函数

◼ 纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范

struct Animal{
    virtual void speak() = 0;
    virtual void run() = 0;
};
struct Dog:Animal{
    void speak(){
        cout << "Dog::speak()" << endl;
    }
    void run(){
        cout << "Dog::run()" << endl;
    }
};
struct Cat:Animal{
    void speak(){
        Animal::speak();
        cout << "Cat::speak()" << endl;
    }
    void run(){
        cout << "Cat::run()" << endl;
    }
};
struct Pig:Animal{
    void speak(){
        cout << "Pig::speak()" << endl;
    }
    void run(){
        cout << "Pig::run()" << endl;
    }
};
int main(){
    Animal *ani = new Pig();
    ani->speak();
    ani->run();
    
    getchar();
    return 0;
}
//输出
Pig::speak()
Pig::run()

5.抽象类(Abstract Class)

含有纯虚函数的类,不可以实例化(不可以创建对象)
抽象类也可以包含非纯虚函数、成员变量

struct Animal{
    int m_age;
    virtual void speak() = 0;
    virtual void run() = 0;
    void eat(){}
};

如果父类是抽象类,子类没有完全重写纯虚函数,那么这个子类依然是抽象类

//抽象类
struct Animal{
    int m_age;
    virtual void speak() = 0;
    virtual void run() = 0;
    void eat(){
        
    }
};
//抽象类
struct Dog:Animal{
    void speak(){
        cout << "Dog::speak()" << endl;
    }
};
//非抽象类
struct HaShiQi:Dog{
    void speak(){
        cout << "HaShiQi::speak()" << endl;
    }
    void run(){
        cout << "HaShiQi::run()" << endl;
    }
};

6.多继承

◼ C++允许一个类可以有多个父类(不建议使用,会增加程序设计复杂度)


image
struct Student{
    int m_score;
    void study(){
        cout << "Student::study() - score = " << m_score << endl;
    }
};
struct Worker{
    int m_salary;
    void work(){
        cout << "Worker::work() - salary = " << m_salary << endl;
    }
};
struct Undergraduate:Student,Worker{
    int m_grade;
    void play(){
        cout << "Undergraduate::play() - m_grade = " << m_grade << endl;
    }
};
int main(){
    Undergraduate *u = new Undergraduate();
    u->m_score = 100;
    u->m_salary = 2000;
    u->m_grade = 4;
    u->work();
    u->study();
    u->play();
    
    return 0;
}
//输出
Worker::work() - salary = 2000
Student::study() - score = 100
Undergraduate::play() - m_grade = 4

7.多继承体系下的构造函数调用

struct Student{
    int m_score;
    Student(int score):m_score(score){}
    void study(){
        cout << "Student::study() - score = " << m_score << endl;
    }
};
struct Worker{
    int m_salary;
    Worker(int salary):m_salary(salary){}
    void work(){
        cout << "Worker::work() - salary = " << m_salary << endl;
    }
};
struct Undergraduate:Student,Worker{
    int m_grade;
    Undergraduate(int grade,int score,int salary):m_grade(grade),Student(score),Worker(salary){}
    void play(){
        cout << "Undergraduate::play() - m_grade = " << m_grade << endl;
    }
};
int main(){
    Undergraduate *u = new Undergraduate(4,100,2000);
    cout << u->m_grade << endl;
    cout << u->m_score << endl;
    cout << u->m_salary << endl;
    return 0;
}
//输出
4
100
2000

8.多继承-虚函数

◼ 如果子类继承的多个父类都有虚函数,那么子类对象就会产生对应的多张虚表


图片.png

9.同名函数

struct Student{
    int m_score;
    void eat(){
        cout << "Student::eat()" << endl;
    }
};
struct Worker{
    int m_salary;
    void eat(){
        cout << "Worker::eat()" << endl;
    }
};
struct Undergraduate:Student,Worker{
    int m_grade;
    void eat(){
        cout << "Undergraduate::eat()" << endl;
    }
};
int main(){
    Undergraduate ug;
    ug.eat();
    ug.Student::eat();
    ug.Worker::eat();
    ug.Undergraduate::eat();
    
    return 0;
}
//输出
Undergraduate::eat()
Student::eat()
Worker::eat()
Undergraduate::eat()

若子类没有eat(),直接调用eat()会产生二义性报错

10.同名成员变量

struct Student{
    int m_age;

};
struct Worker{
    int m_age;
};
struct Undergraduate:Student,Worker{
    int m_age;
};
int main(){
    cout << sizeof(Undergraduate) << endl;
    Undergraduate ug;
    ug.m_age = 10;
    cout << ug.m_age << endl;
    ug.Student::m_age = 20;
    ug.Worker::m_age = 23;
    cout << ug.Student::m_age << endl;
    cout << ug.Worker::m_age << endl;
    
    return 0;
}
//输出
12
10
20
23
image

相关文章

  • 09-虚表、抽象类和多继承

    《C++文章汇总》[https://www.jianshu.com/p/bbd842438420]上一篇介绍了引用...

  • 10.26学习总结

    今天学习了多态和抽象类。 多态学了虚方法、方法的重写,多态和继承的区别,重写和隐藏的区别。 抽象类学了抽象类的方法...

  • 纯虚函数、抽象类、多继承、菱形继承、虚继承、虚基类

    纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范 抽象类(Abstract Class)含有纯虚函数的类...

  • 08. 纯虚函数、抽象类、多继承、菱形继承、虚

    一.虚函数 二.纯虚函数 三,虚析构函数 四.纯虚函数 五,多继承 六.多继承-虚函数 七.菱形继承 八. 虚继承...

  • ★01.基础概念

    抽象类:含有虚函数的类。 抽象基类:含有纯虚函数的类。 虚基类:多重继承中虚继承的基类。 类静态数据成员的定义必须...

  • C++ 类-2

    虚函数 virtual 修饰有纯虚函数的类叫做抽象类只能被继承不能被实例化。

  • Android面试题

    接口和抽象类的区别: 抽象类只能单继承,接口能多实现(一个类只能继承一个抽象类,但是能实现多个接口) 抽象类是一个...

  • 虚函数和非虚函数

    接口继承和实现继承 public继承下,派生类总是继承基类的接口。 纯虚函数必须被派生类重新声明,通常在抽象类中没...

  • 第五周Boolan

    对象模型 vptr(虚表指针) 和vtbl(虚函数表) 继承函数指的是继承调用权 而不是内存的大小 静态绑定与动态...

  • 纯虚函数和抽象类

    纯虚函数和抽象类 概念 纯虚函数:没有函数体的虚函数。 抽象类:包含纯虚函数的类就称为抽象类。 纯虚函数就是在函数...

网友评论

      本文标题:09-虚表、抽象类和多继承

      本文链接:https://www.haomeiwen.com/subject/glvumltx.html