啃书《C++ Primer Plus》 面向对象部分 虚机制——虚函数表、虚指针

本文详细探讨了C++中的虚函数机制,包括虚函数的声明与实现,`override`与`overwrite`的概念,以及虚函数表和虚指针的工作原理。文章通过实例介绍了虚函数在类继承和多态中的应用,并强调了虚析构函数的重要性,特别是在动态内存管理中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

长文干货预警


虚机制,一个听起来不好惹的角色,却是C++面向对象部分的精髓。不得不承认的是,这部分的内容有些多,也不太好理解。但同样不得不承认的是,虚机制在面向对象部分准确说是多态特性发挥着举足轻重的作用。可以说没有了虚机制,C++的面向对象就没有了灵魂。

因此,这一篇,我们就来整理有关虚机制的内容。本文的内容思维导图如下:
在这里插入图片描述
有关静态编联和动态编联的内容,可以参考另一篇博文:

啃书《C++ Primer Plus》面向对象部分 静态联编与动态联编


虚函数的创建和实现

这虚函数也是函数啊,唯一不同的就是它比较虚~~

因此,只需要在成员函数前面加上“虚”就完事了。

虚函数必须是成员函数

  • 这里,需要强调,虚函数必须是成员函数!!!
    对于这样的定义:
class A{
   
public:
    static virtual void f(){
   }	//企图定义一个静态的虚函数
};

编译器会给予报错:
在这里插入图片描述
告知静态的和虚的不允许同时出现。

这样的规定不难理解,只需要认识到,静态函数是仅属于类的,而成员函数“属于”对象。
虚函数提供了一种父类声明,子类实现的机制,这与静态函数仅属于类是冲突的。

虚函数的声明

刚刚说到,在成员函数前加上“虚”的修饰,就可以让它变成虚函数。这个"虚"就是英文单词virtual

class A{
   
public:
	virtual void f(){
   }//在A类中定义了一个虚函数f
};

在子类中重写这个虚函数时,则可以省略掉virtual关键字

class A{
   
public:
	virtual void f(){
   }//基类中定义了一个虚函数
};
class B: public A{
   
public:
	void f(){
   }//派生类中重写虚函数可以省略virtual
};

override与overwrite

这里介绍两个概念:overrideoverwrite

override的意思是重写虚函数,代表着子类给父类的某些虚函数提供一种实现,这是虚函数实现多态最常见的途径,即父类声明函数,子类提供实现。

在刚刚的例子中,B类重新实现了A类的虚函数f的行为就是一种重写。

overwrite译作中文应该叫“覆盖”,(不过由于翻译的五花八门,因此博主还是建议改概念使用英文来记忆比较妥当。)这个概念的并不仅仅出现在虚函数的范围中,在一般成员函数中也有所体现。

具体的说,就是当子类使用了父类中出现过的函数名称时,不论其返回类型、参数列表,是否被const修饰等因素是否相同,子类的该函数会覆盖所有的同名父类函数。

虚函数也不例外,也难逃overwrite的魔爪

#include<iostream>
using namespace std;
class A{
   
public:
	/*基类定义两个版本的函数f*/
    virtual void f(){
   cout << "func f in class A" << endl;}				
    virtual void f(int k){
   cout << "func f in class A and " << k << endl;}
};

class B:public A{
   
public:
	/*派生类重写了其中一个函数f*/
    virtual void f(){
   cout << "func f in class B" << endl;}
    //overwrite了另一个版本的函数,在B类作用域中没有带有一个整型参数的f函数
};

int main()
{
   
    A* pa = new B();
    pa->f();
    pa->f(10);
    
    B pb = new B();
    pb->f();
    pb->f(10);	//这个调用将会报错,B类中没有这个函数
	delete p;
}

虚函数表和虚指针

说了这么多有关虚函数的使用,是时候来介绍虚函数的原理了,书中在介绍动态联编处理虚函数的时候用到了这样的描述:

编译器必须生成能够在程序运行时选择正确虚方法的代码,这称为动态联编(dynamic binding),又称为晚联编(late binding)

这段用来进行动态联编的代码便是虚函数的原理,这其中就包含了虚函数表虚指针

虚函数表

虚函数表叫做Virtual Table,简称V-Table
简单些说,就是放置了一个类中所有虚函数指针的表,是一个函数指针数组

下面来说明各种情况下虚函数表的形态

无继承时

例如对于下面的类:

class 
<think>嗯,用户的问题是关于用C++实现一个计算剩余完整书籍数量的算法,涉及到老鼠啃书的情况。首先,我需要明确问题的具体要求。题目中提到“老鼠每小时啃书”,可能是指老鼠每小时会啃食一定数量的书籍,需要计算经过一定时间后剩余的完整书籍数量。不过用户提供的输入输出样例没有具体给出,所以需要根据常见的类似问题进行推测。 首先,可能的输入包括初始书籍数量、老鼠每小时啃食的数量以及经过的时间。输出则是剩余的完整书籍数量。例如,输入可能是初始书籍N,每小时啃食量K,时间H小时,那么剩余量应该是N - K*H,但不能小于零。所以剩余书籍是max(N - K*H, 0)。这看起来像是一个简单的数学问题,但需要处理负数的情况。 接下来,我需要考虑如何用C++实现这个算法。首先,读取输入数据,可能是三个整数:N(初始数量)、K(每小时啃食量)、H(小时数)。然后计算剩余数量,注意结果不能为负数。使用std::max函数来处理这种情况。 然后,用户可能需要的不仅仅是代码,还有算法思路的讲解。需要分步骤说明,比如输入处理、计算公式、输出结果。同时,代码部分要符合C++语法,并且高效。例如,使用cin读取输入,cout输出结果,确保类型正确,避免溢出。 另外,用户提到的参考引用中有关于C++的内容,比如引用[4]提到泛型算法,但这个问题可能不需要复杂的算法,简单的算术运算即可。不过可能需要考虑边界情况,比如当K或H为零的情况,或者输入无效的情况,但用户可能希望保持简单,假设输入都是有效的正整数。 测试样例方面,假设输入是比如100本书,每小时啃5本,经过20小时,那么剩余是100 -5*20=0。另一个例子,比如初始50,每小时3,经过10小时,剩余50-30=20。如果时间过长,比如初始10,每小时4,经过3小时,则剩余10-12=负数,所以取0。 最后,生成相关问题需要考虑算法优化、应用场景、边界条件处理等。例如,如何处理大数溢出,或者动态变化的老鼠啃食率等扩展问题。</think>### C++实现计算剩余完整书籍数量的算法 #### 问题分析 假设老鼠每小时啃食固定数量的书籍,给定初始书籍数量、啃食速率和时间,计算剩余完整书籍数量。公式为: $$剩余量 = \max(初始数量 - 每小时啃食量 \times 时间, 0)$$ #### 实现步骤 1. **输入处理**:读取初始书籍数$N$、每小时啃食量$K$、时间$H$ 2. **计算剩余量**:应用公式避免负数结果 3. **输出结果** #### 示例代码 ```cpp #include <iostream> #include <algorithm> // 用于max函数 int main() { int N, K, H; std::cin >> N >> K >> H; int remaining = std::max(N - K * H, 0); std::cout << remaining << std::endl; return 0; } ``` #### 输入输出样例 输入: ``` 100 5 20 // 初始100本,每小时啃5本,持续20小时 ``` 输出: ``` 0 ``` 输入: ``` 50 3 10 ``` 输出: ``` 20 ``` #### 算法优化 1. **溢出处理**:当$K \times H$超过int范围时,使用`long long`类型 2. **输入验证**:添加负数输入的检测逻辑 3. **动态速率**:如果啃食速率随时间变化,需使用积分计算[^4]
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值