IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> [杂记]C++中关于虚函数的一些理解 -> 正文阅读

[C++知识库][杂记]C++中关于虚函数的一些理解

最近在重学C++,做一些笔记。

1. 为什么要使用虚函数

我们现在考虑类的继承问题. 假如有一个父类 A A A, 有一个子类 B B B, 子类 B B B中重写了父类的一个方法(函数). 现在我们要额外写一个函数, 这个函数要调用父类的这个方法, 因此这个函数我们可以按值传递, 也可以按引用传递和指针传递. 但是在给这个函数传参的过程中, 我们有可能会传入的是子类对象, 即如下所示:

void func(A& a){
	a.method();
}

int main(){
	B b;
	func(b);
}

那我们还有必要再写(重载)一个func, 把参数换成 B B B吗? 答案是否定的. 换言之, 既然函数里执行的是子类父类的相似行为, 那么我们可以期望利用父类类型来对可能传入的子类们进行统一操作, 提高代码的复用性.

但是现在有什么问题? 我们传入的是子类对象, 而参数是父类引用, 在这个过程中会发生类型转换. 因此, 在调用method的时候, 我们还是调用的父类的method.

解决这个问题的方式, 就是将父类的method声明为virtual的:

class A{
...
public:
	virtual void method()...
};

这样编译器就会根据引用或指针指向的类型来确定调用哪个函数. 那么编译器是怎么做到的呢?

2. 虚函数是如何工作的

这里借用《C++ primer plus》中的讲述:

通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(virtual function table,vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址。例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含另一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;如果派生类没有重新定义虚函数,该vtbl将保存函数原始版本的地址如果派生类定义了新的虚函数,则该函数的地址也将被添加到vtbl中。注意,无论类中包含的虚函数是1个还是10个,都只一需要在对象中添加1个地址成员,只是表的大小不同而已。

在这里插入图片描述

3. 其他细节

  1. 构造函数没有必要成为虚函数
    因为子类在执行构造函数时一定会执行父类的构造函数, 因此构造函数是不会被重写的, 因此也不应该成为虚函数
  2. 析构函数最好为虚函数
    这是因为如果子类申请了新的内存,而且按照上面说的方式定义了父类类型但是指向子类对象的引用或指针的话,则会执行父类的析构函数,而父类的析构函数不会释放子类申请的新内存,为此,应将父类的析构函数声明为virtual的,这样就先执行子类的析构,再执行父类的析构
  3. 友元函数不应为虚函数
    这是因为友元函数根本不是成员函数
  4. 子类重新定义函数并不是重载
    如果子类重新定义父类的方法,但是改变了参数类型等,这样会覆盖父类的定义,也即子类无法访问父类原本的方法。返回类型协变除外。
    此外,如果父类有重载,则子类如果想要修改其中的任一个,必须再一一声明重载,否则就覆盖了。

4. 程序实例

我简单写了一个测试程序,涵盖了以上几点:

class BaseClass{
private:
    int value;
public: 
    BaseClass(const int v) { this->value = v;};
    virtual void showValue() const { std::cout << "BaseClass " << this->value << "\n"; };
    virtual ~BaseClass() { std::cout << "Deconstructor of BaseClass \n"; };
}; 

class Child : public BaseClass{
private:
    char* name;
public: 
    Child(const int v, const char* str) : BaseClass(v){
        this->name = new char[100];
        strcpy(this->name, str);
    }
    void showValue() const { std::cout << "ChildClass " << this->name << "\n"; };
    virtual ~Child(){
        delete[] this->name; 
        this->name = nullptr;
        std::cout << "Deconstructor of Child \n";
    }
};

void func(BaseClass& bc){
    bc.showValue();
}

void test(){
    BaseClass class0 (10);

    char str[] = "sadskda";
    Child class1 (10, str);

    func(class0);
    func(class1);
}

int main(){
    test();

    system("pause");
    return 0;
}

其中func函数实现了只传递父类对象,但也可以执行子类方法,这是通过虚函数实现的。此外,子类的析构函数需要释放申请的内存,因此程序输出如下:

BaseClass 10
ChildClass sadskda
Deconstructor of Child
Deconstructor of BaseClass
Deconstructor of BaseClass
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-15 01:47:57  更:2022-09-15 01:50:23 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 14:18:03-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码