虚函数

一 实现多态:虚函数

多态的本质:
形式上,使用统一的父类指针做一般性处理,但是实际执行时,这个指针可能指向子类对象。
形式上,原本调用父类的方法,但实际上会调用子类的同名方法。

注意:
程序执行时,父类指针指向父类对象,或子类对象时,在形式上是无法分辨的,只有通过多态机制,才能执行真正对应的方法

虚函数的定义:
在函数的返回值类型之前使用virtual,只在成员函数的声明中添加virtual,在成员函数的实现中不要加virtual

虚函数的继承:
如果某个成员函数被声明为虚函数,那么它的子类(派生类),以及子类的子类中,所继承的这个成员函数,也自动是虚函数
如果在子类中重写这个虚函数,可以不用加virtual,也可以加virtual,加上更可读

二 单个类的虚函数表

#include <iostream>

using namespace std;

class Father
{
public:
    virtual void fun1() { cout << "Father::fun1" << endl; }
    virtual void fun2() { cout << "Father::fun2" << endl; }
    virtual void fun3() { cout << "Father::fun3" << endl; }
    void fun4() { cout << "非虚函数:Father::fun4" << endl; }

public:  // 这里用public是为了方便测试
    int num1 = 100;
    int num2 = 200;
    static int x;  // 静态成员变量
};

// 初始化静态成员变量,放在数据区
int Father::x = 0;

// 定义函数指针
typedef void (*fun_t)(void);

int main()
{
    Father f1;

    // 定义指向f1的虚函数表的指针
    int* vptr = (int*)*(int*)&f1;   

    // 访问f1虚函数表中的每个虚函数
    for (int i = 0; i < 3; i++)
    {
        cout << "调用第" << i + 1 << "个虚函数:";
        ((fun_t) * (vptr + i))();
    }

    // 访问f1对象的数据成员num1和num2
    for (int i = 0; i < 2; i++)
    {
        cout << "num" << i + 1 << ":" << *((int*)&f1 + i + 1) << endl;
    }

    return 0;
}

image

在命令行中添加:/d1 reportSingleClassLayoutFather 查看Father类的内存布局
image

手绘内存分布:
image

对象内,首先存储的是“虚函数表指针”,又称“虚表指针”,然后再存储非静态数据成员。
对象的非虚函数,保存在类的代码中。
对象的内存,只存储虚表指针和数据成员。
类的静态数据成员,保存在数据区中,和对象是分开存储的
新添加虚函数后,对象的内存空间不变,仅虚函数表中添加条目
多个对象,共享同一个虚函数表

三 使用继承的虚函数表

#include <iostream>

using namespace std;

class Father
{
public:
    virtual void fun1() { cout << "Father::fun1" << endl; }
    virtual void fun2() { cout << "Father::fun2" << endl; }
    virtual void fun3() { cout << "Father::fun3" << endl; }
    void fun4() { cout << "非虚函数:Father::fun4" << endl; }

public:
    int num1 = 100;
    int num2 = 200;
    static int x;  // 静态成员变量
};

// 初始化静态成员变量,放在数据区
int Father::x = 0;

class Son :public Father
{
public:
    void fun1() { cout << "Son::fun1" << endl; }  // 重写父类的虚函数fun1方法
    virtual void fun5() { cout << "Son::fun5" << endl; }

public:
    int num3 = 300;
};

// 定义函数指针
typedef void (*fun_t)(void);

int main()
{
    Son s1;

    // 定义指向s1对象的虚表指针
    int* vptr = (int*)*(int*)&s1;

    // 访问Son对象虚表函数中的每个虚函数
    for (int i = 0; i < 4; i++)
    {
        cout << "访问Son对象第" << i + 1 << "个虚函数:";
        ((fun_t) * (vptr + i))();
    }

    // 访问Son对象的数据成员num1,num2,num3
    for (int i = 0; i < 3; i++)
    {
        cout << "num" << i + 1 << ":" << *((int*)&s1 + 1 + i) << endl;
    }

    return 0;
}

image

查看Son类内存布局:
image

手绘内存分布:
image

补充:
image

四 多重继承的虚函数表

#include <iostream>

using namespace std;

class Father
{
public:
    virtual void fun1() { cout << "Father::fun1" << endl; }
    virtual void fun2() { cout << "Father::fun2" << endl; }
    virtual void fun3() { cout << "Father::fun3" << endl; }
    void fun4() { cout << "非虚函数:Father::fun4" << endl; }

public:
    int num1 = 100;
    int num2 = 200;
    static int x;  // 静态成员变量
};

// 初始化静态成员变量,放在数据区
int Father::x = 0;

class Mother
{
public:
    virtual void test1() { cout << "Monter::test1" << endl; }
    virtual void test2() { cout << "Monter::test2" << endl; }
    virtual void test3() { cout << "Monter::test3" << endl; }
    void test4() { cout << "非虚函数 Monter::test4" << endl; }

public:
    int num3 = 300;
    int num4 = 400;
    static int x;  // 静态成员变量
};

class Son :public Father,public Mother
{
public:
    void fun1() { cout << "Son::fun1" << endl; }            // 重写Father类的虚函数fun1方法
    virtual void test1() { cout << "Son::test1" << endl; }  // 重写Mother类的虚函数fun1方法

    virtual void fun5() { cout << "Son::fun5" << endl; }    // 新添加的虚函数fun5

public:
    int num5 = 500;
};

// 定义函数指针
typedef void (*fun_t)(void);

int main()
{
    Son s1;

    // 定义指向Son对象继承的Father类的虚表指针
    int* vptr = (int*)*(int*)&s1;
    // 访问Son对象继承的Father类虚表函数中的每个虚函数
    for (int i = 0; i < 4; i++)
    {
        if (i < 3)
        {
            cout << "访问Son对象继承Father类的第" << i + 1 << "个虚函数:";
            ((fun_t) * (vptr + i))();
        }
        else
        {
            cout << "访问Son对象新添加的虚函数:";
            ((fun_t) * (vptr + i))();
        }
    }
    // 访问Son对象继承的Father类的数据成员num1,num2
    for (int i = 0; i < 2; i++)
    {
        cout << "num" << i + 1 << ":" << *((int*)&s1 + 1 + i) << endl;
    }
    cout << endl;

    // 定义指向Son对象继承的Mother类的虚表指针
    int* vptr2 = (int*)*((int*)&s1 + 3);
    // 访问Son对象继承的Mother类虚表函数中的每个虚函数
    for (int i = 0; i < 3; i++)
    {            
        cout << "访问Son对象继承Mother类的第" << i + 1 << "个虚函数:"; 
        ((fun_t) * (vptr2 + i))();
    }
    // 访问Son对象继承的Mother类的数据成员num3,num4 和新添加的数据成员num5
    for (int i = 0; i < 3; i++)
    {
        cout << "num" << i + 3 << ":" << *((int*)&s1 + 4 + i) << endl;
    }

    return 0;
}

image

查看Son类内存布局:
image

手绘内存分布:
image

注意:子类新添加的虚函数是放在继承的第一个父类的虚函数表中的

五 final

5.1 用final修饰类,让该类不能被继承

class Test1 
{

};

class Test2 final : public Test1  // 这里用final修饰,使Test2类不能被继承
{

};

class Test3 :public Test2  // 这里会报错  不能将"final"类类型作基类
{

};

image

5.2 用final修饰虚函数,使该方法不能被子类重写

final在修饰函数的时候,只能用来修饰虚函数。final只能在函数声明中使用,不能在函数的实现中使用。

class Test1 
{
public:
    virtual void fun1() final;  // final只能在声明的时候添加,实现的时候不能添加final
};

void Test1::fun1() // final只能在声明的时候添加,实现的时候不能添加final
{
}


class Test2  : public Test1 
{
public:
    //void fun1() {}  // 报错,在子类中不能重写父类final修饰的方法
};

image

六 override 提示程序的阅读者,这个函数是重写父类的方法

override仅能用于修饰虚函数。override只能在函数的声明中使用,不能在函数的实现中使用

作用:
1.提示程序的阅读者,这个函数是重写父类的方法
2.防止程序员在重写父类的函数时,把函数名写错

#include <iostream>
using namespace std;
class Father 
{
public:
    virtual void fun1() { cout << "Father::fun1" << endl; }
};

class Son  : public Father 
{
public:
    void fun1() override;  // override告诉程序员,fun1是重写父类的方法
};

void Son::fun1()
{
    cout << "Son::fun1" << endl;
}

七 基类的析构函数使用虚函数

基类的析构函数是非虚函数时,delete父类指针指向子类对象时,不会调用子类的析构函数

#include <iostream>
#include <string.h>

using namespace std;

class Father 
{
public:
    Father(const char* addr = "中国")
    {
        cout << "调用Father类的构造函数" << endl;
        int len = strlen(addr) + 1;
        this->addr = new char[len];
        strcpy_s(this->addr, len, addr);
    }
    ~Father()  // 这里没有使用virtual
    {
        cout << "调用Father类的析构函数" << endl;
        if (addr)
        {
            delete addr;
            addr = NULL;
        }
    }
private:
    char* addr;

};

class Son  : public Father 
{
public:
    Son(const char* addr, const char* game = "王者荣耀") :Father(addr)
    {
        cout << "调用Son类的构造函数" << endl;
        int len = strlen(game) + 1;
        this->game = new char[len];
        strcpy_s(this->game, len, game);
    }
    ~Son()
    {
        cout << "调用了Son类的析构函数" << endl;
        if (game)
        {
            delete game;
            game = NULL;
        }
    }
private:
    char* game;
};

int main()
{
    cout << "----------- case 1 ------------" << endl;
    Father* father;
    father = new Father();
    delete father;
    cout << endl;

    cout << "----------- case 2 ------------" << endl;
    Son* son;
    son = new Son("China");
    delete son;
    cout << endl;

    cout << "----------- case 3 ------------" << endl;
    father = new Son("China");
    delete father;

    return 0;
}

image

这里,我们将Father类的析构函数改为虚函数

virtual ~Father()  // 这里将Father类的析构函数改为虚函数
{
    cout << "调用Father类的析构函数" << endl;
    if (addr)
    {
        delete addr;
        addr = NULL;
    }
}

再来运行:
image

注意:
为了防止内存泄漏,最好是在基类析构函数上添加virtual关键字,使基类析构函数为虚函数
目的在于,当使用delete释放基类指针时,会实现“动态析构”:
如果基类指针指向的是基类对象,那么只调用基类的析构函数
如果基类指针指向的是子类对象,那么先调用子类的析构函数,再调用父类的析构函数

八 纯虚函数与抽象类

什么时候使用纯虚函数:
某些类,在现实角度和项目实现角度,都不需要实例化(不需要创建它的对象),这个类中定义的某些成员函数,只是为了提供一个形式上的接口,准备让子类来做具体的实现。此时,这个方法,就可以定义为“纯虚函数”。

什么是抽象类:
包含纯虚函数的类,就称为抽象类

纯虚函数的用法:
纯虚函数,使用 virtual 和 =0

#include <iostream>
#include <string>
#include <string.h>

using namespace std;

class Shape
{
public:
    Shape(const string color = "weight")
    {
        this->color = color;
    }

    virtual float area() = 0;  // 纯虚函数不用做具体的实现

    string getColor() { return color; }
private:
    string color;
};

class Circle : public Shape
{
public:
    Circle(float radius = 0, const string color = "black"):Shape(color),r(radius){ }

    float area()   // 在子类中实现这个纯虚函数
    {
        return 3.14 * r * r;
    }
private:
    float r;  // 半径
};

int main()
{
    //Shape s1;   // 报错,不能使用抽象类创建对象
    Circle c1(10);
    cout << "颜色:" << c1.getColor() << " 面积:" << c1.area() << endl;

    Shape* p = &c1;
    cout << "颜色:" << p->getColor() << " 面积:" << p->area() << endl;
    return 0;
}

image

纯虚函数注意事项:
父类声明纯虚函数,那么这个类就是抽象类,就不能创建对象
并且它的子类:
1.要么实现这个纯虚函数(最常见)
2.要么继续把这个纯虚函数声明为纯虚函数,这个子类也成为抽象类
3.要么不对这个纯虚函数做任何处理,等效于上一种情况(该方法不推荐)

九 常见错误总结

1.子类在重写继承的虚函数时,要和基类的函数原型必须保持一致
2.析构函数是否使用虚函数? 有子类时,析构函数就应该使用虚函数

posted @ 2022-05-11 00:41  荒年、  阅读(138)  评论(0编辑  收藏  举报