JHHK

欢迎来到我的个人网站
行者常至 为者常成

10、虚函数、虚函数表、纯虚函数、抽象类

目录

虚函数

C++中的多态通过虚函数(virtual function)来实现

虚函数:被virtual修饰的成员函数

只要在父类中声明为虚函数,子类中重写的函数也自动变为虚函数(也就是说子类中可以省略virtual关键字)

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

class Animal{
public:
    int age;
//    void speak(){
//        cout<<"Animal::speak()"<<endl;
//    };
//
//    void run(){
//        cout<<"Animal:run()"<<endl;
//    };
    
    //只要有virtual就有虚函数就有虚表
    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;
    }
};


class Cat:public Animal{
public:
    int life;
    void speak(){
        cout<<"Cat::speak()"<<endl;
        //调用父类的成员函数实现
        Animal::speak();
    }
    
    void run(){
        cout<<"Cat::run()"<<endl;
    }
    
    ~Cat(){
        cout<<"Cat::~Cat()"<<endl;
    }
};
 

void virtualUsing(){
    Cat * cat = new Cat();
    
    //如果没有虚函数Cat对象的内存大小为8字节
    //如果有虚函数Cat对象的内存大小为16字节,会增加一个指针
    cout<<"sizeof(cat) is "<<sizeof(Cat)<<endl;
    /**
     (lldb) po cat
     0x000000010054a3c0
     对象cat的内存地址为:0x000000010054a3c0(堆内存)
     
     

     (lldb) memory read/2xg 0x000000010054a3c0
     0x10054a3c0: 0x000000010001d310 0x0000000a00000014
     
     对象cat的内存分布
     0-7字节存储了一个地址:0x000000010001d310 虚表地址
     8-11字节存储age 20
     12-15字节存储life 10

     
     (lldb) memory read/2xg 0x000000010001d310
     0x10001d310: 0x0000000100005aa0 0x0000000100005ae0

     虚表地址内存分布
     0-7字节存储speak方法地址
     8-15字节存储run方法地址
     
     LCClientDemo`tenDay::Cat::speak:
     0x100005aa0 <+0>:  pushq  %rbp

     
     LCClientDemo`tenDay::Cat::run:
     0x100005ae0 <+0>:  pushq  %rbp

     
     x86架构的内存分布图见:
     catObjectVirtualTable.png
     catObjectVirtualTable2.png
     */

    cat->age = 20;
    cat->life = 10;
    cat->speak();
    cat->run();
    
    delete cat;
}

img

img

虚析构函数

含有虚函数的类,应该将析构函数声明为虚函数(虚析构函数)
delete父类指针时,才会调用子类的析构函数,保证析构的完整性

void virtualDestructorUsing(void){
    Animal * cat = new Cat();
    /**
     当父类指针指向子类对象时,调用delete cat 时 只会调用父类的析构函数,
     解决这个问题的方式是,将析构函数声明为虚函数;
     */
    delete cat;
}

纯虚函数

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

抽象类(Abstract Class)
含有纯虚函数的类,不可以实例化(不可以创建对象)
抽象类也可以包含非纯虚函数
如果父类是抽象类,子类没有完全实现纯虚函数,那么这个子类依然是抽象类

//纯虚函数相当于OC中的协议
class Person{
public:
    virtual void speak() = 0;
    virtual void run() = 0;
};

class Student : public Person{
public:
    void speak(){
        cout<<"Student::speak"<<endl;
    }
    
    void run(){
        cout<<"Student::run"<<endl;
    }
};

void pureVirtualFuncUsing(){
    //抽象类 不可以初始化
    //Variable type 'tenDay::Person' is an abstract class
    //Person person;
    
    //如果 speak 和 run 方法 没有完全实现 student初始化的时候会报错
    //Variable type 'tenDay::Student' is an abstract class
    Student stu;
    stu.speak();
    stu.run();
}

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.