C++ 虚函数

1、什么是虚函数?

虚函数是C++实现运行时多态的核心机制,它允许通过基类指针调用派生类的重写方法。

2、基本语法

cpp 复制代码
    class A_v{
        public:
            virtual void v_func(){
                cout << "A_v::v_func()" << endl;
            }
    };

    class B1 : public A_v{
        public:
            void v_func(){  // 等价于:virtual void v_func() override
            	  // 还是建议void v_func() override这么写
                cout << "B1::v_func()" << endl;
            }
    };

    int main(){
        A_v *p = new B1();
        p->v_func();
        return 0;
    }

在派生类中重写的 v_func() 会自动成为虚函数。 ,不需要显示写virtual,这是C++的隐式规则:

一旦基类中某个函数被声明为virtual,该函数在所有派生类中自动保持虚函数特性,无论是否显示写virtual!

3. 什么是纯虚函数?

  • 纯虚函数在类中声明时,加上=0;
  • 含有纯虚函数的类成为抽象类(纯虚函数类似java中的抽象函数),类中只有接口,没有具体实现方法;
  • 继承纯虚函数的派生类,如果没有完全实现基类纯虚函数,依然是抽象类,不能实例化对象!

说明:

  • 抽象类虽然不能实例化对象,但可以声明抽象类指针、引用;
  • 子类必须继承父类的纯虚函数,并全部实现后,才能创建子类的对象;

4. 虚函数和纯虚函数的区别?

  • 使用方式不同:虚函数可以直接使用,纯虚函数必须在派生类中实现后才能使用;
  • 定义形式不同:虚函数在定义时在普通函数的基础上加virtual关键字即可,纯虚函数定义时除了加virtual关键字还需要加上=0;
  • 虚函数必须实现,否则编译器报错;
  • 析构函数最好定义为虚函数,特别是对于含有继承关系的类;析构函数可以定义为纯虚函数,此时,其所在的类为抽象基类,不能创建实例化对象;

5. 虚函数机制

虚函数机制 :是由虚函数表vtable 实现的。虚函数的地址保存在虚函数表中,类对象保存了指向虚函数表的指针vptr。通过vptr就可以找到对应类的vtable。vtable中解决了基类和派生类的继承问题和类中成员的覆盖问题。当用基类的指针来操作一个派生类的时候,这张vtable就指明了实际应该调用的函数。

cpp 复制代码
class Base{
    public:
        virtual void B_func1() {cout << "Base::B_func1()" << endl;}
        virtual void B_func2() {cout << "Base::B_func2()" << endl;}
        virtual void B_func3() {cout << "Base::B_func3()" << endl;}
};

class Derive : public Base{
    public:
    virtual void B_func1() override {cout << "Derive::D_fun1()" <<endl;}
    virtual void D_func2() {cout << "Derive::D_fun2()" << endl;}
    virtual void D_func3() {cout << "Derive::D_func3()" << endl;}
};

int main(){
    Base *p = new Derive();
    p->B_func1(); //输出:Derive::D_fun1()
    p->D_func3(); //error
    return 0;
}

总结:

  • 指针类型决定"我能看到什么接口"(编译器检查)
  • 对象的实际类型决定"我真正是谁,vptr指向哪个vtable"(运行时决定)

Base* p告诉编译器:"请按Base的接口来检查我的代码",p指向的内存里vptr,在new Dervie()时被设置为Derive版本。vtable的查找发生在运行时,但代码走在这一步,必须先通过编译器的名字查找。

设置为虚函数的析构函数,在delete时会连同父类的析构函数一并调用,一并释放。

6. 为啥构造函数不写为虚函数?

还没构造出来对象呢,那里存放vptr?