小学生学C++编程语法知识(C++虚函数详解)
2026/8/6 2:42:43 网站建设 项目流程


C++虚函数详解——让“小学生”理解多态的魔法

前面我们讲过:

  • class封装

  • 继承

  • public / private

  • 函数重载

今天讲 C++ 面向对象中非常重要的一个概念:

虚函数(virtual function)

它是 C++ 实现运行时多态(Runtime Polymorphism)的核心。


一、为什么需要虚函数?

先看一个问题。

假设我们有一个动物世界:

#include<iostream> using namespace std; class Animal { public: void speak() { cout<<"动物叫"<<endl; } }; class Dog:public Animal { public: void speak() { cout<<"小狗汪汪叫"<<endl; } }; class Cat:public Animal { public: void speak() { cout<<"小猫喵喵叫"<<endl; } }; int main() { Animal *p; Dog dog; Cat cat; p=&dog; p->speak(); p=&cat; p->speak(); return 0; }

你觉得输出是什么?

很多同学会认为:

小狗汪汪叫 小猫喵喵叫

但是实际:

动物叫 动物叫

为什么?


二、原因:编译器只看指针类型

关键代码:

Animal *p;

p是什么?

它是:

一个Animal类型的指针


虽然:

p=&dog;

实际指向:

Dog对象

但是编译器看到:

p.speak()

它想:

p是Animal指针,那调用Animal::speak()

所以调用:

Animal::speak()

这叫:

静态绑定

也叫:

编译时多态

决定函数的是:

指针或者引用的类型


三、虚函数登场

如果我们希望:

指向谁 调用谁

怎么办?

给函数加:

virtual

例如:

class Animal { public: virtual void speak() { cout<<"动物叫"<<endl; } };

完整:

class Animal { public: virtual void speak() { cout<<"动物叫"<<endl; } }; class Dog:public Animal { public: void speak() { cout<<"小狗汪汪叫"<<endl; } }; class Cat:public Animal { public: void speak() { cout<<"小猫喵喵叫"<<endl; } };

运行:

Animal *p; Dog dog; Cat cat; p=&dog; p->speak(); p=&cat; p->speak();

输出:

小狗汪汪叫 小猫喵喵叫

成功!


四、虚函数解决了什么问题?

一句话:

虚函数让程序在运行时,根据对象真实类型决定调用哪个函数。

也就是:

以前:

看爸爸

现在:

看孩子本人

例如:

Animal *p=&dog;

表面:

Animal指针

实际:

Dog对象

虚函数:

发现真实对象是Dog 调用Dog::speak()

五、生活中的例子

比如:

父类:

交通工具

有:

virtual move()

子类:

汽车:

move() { 开车前进 }

飞机:

move() { 飞起来 }

轮船:

move() { 在水上航行 }

然后:

Vehicle *p; p=&car; p->move(); p=&plane; p->move(); p=&ship; p->move();

结果:

开车前进 飞起来 水上航行

程序不用知道具体是什么交通工具。

这就是:

多态


六、虚函数底层怎么实现?

编译器会给含有虚函数的类增加:

虚函数表(virtual table)

简称:

vtable


例如:

class Animal { public: virtual void speak(); };

编译器偷偷增加:

Animal对象 +-------------+ | vptr | +-------------+ | 成员变量 | +-------------+

其中:

vptr

叫:

虚函数指针。


它指向:

虚函数表

类似:

Animal vtable -------------- speak地址 --------------

Dog:

Dog vtable -------------- Dog::speak地址 --------------

运行:

p->speak();

实际过程:

1. 找对象中的vptr 2. 找vtable 3. 找speak地址 4. 调用对应函数

所以:

运行时决定

七、虚函数必须重写吗?

不是。

例如:

class Animal { public: virtual void speak() { cout<<"动物叫"; } };

Dog:

class Dog:public Animal { };

没有写:

speak()

那么:

Dog dog; dog.speak();

调用:

动物叫

八、什么是纯虚函数?

有时候父类不知道怎么实现。

比如:

动物叫

这个没有意义。

可以写:

class Animal { public: virtual void speak()=0; };

这叫:

纯虚函数

含有纯虚函数的类:

叫:

抽象类

不能创建对象:

错误:

Animal a;

但是可以:

Animal *p;

九、虚函数可以声明为 inline 吗?

这是一个非常经典的问题。

答案:

可以声明,但是通常没有意义。


例如:

class Animal { public: virtual inline void speak() { cout<<"动物叫"; } };

语法合法。


但是:

为什么说没有意义?

因为:

inline 的作用:

建议编译器把函数展开

例如:

普通函数:

add(1,2);

变成:

1+2

减少函数调用开销。


但是虚函数:

p->speak();

运行时才知道调用谁。

例如:

p可能指向Dog p可能指向Cat p可能指向Bird

编译器无法提前确定:

到底展开哪个函数?

所以:

虚函数调用通常不能inline。


十、那么虚函数一定不能inline吗?

更加准确:

不是绝对不能。

有两种情况。


情况1:通过对象调用

例如:

Dog dog; dog.speak();

编译器知道:

一定是Dog::speak()

可以优化:

inline。


情况2:通过基类指针调用

例如:

Animal *p; p->speak();

需要:

vtable查找

通常无法inline。


所以:

调用方式能否inline
Dog对象直接调用可能
Animal引用调用通常不能
Animal指针调用通常不能

十一、虚函数为什么不能是static?

错误:

class Animal { virtual static void speak(); };

原因:

static函数属于:

不是:

对象

而虚函数依赖:

对象 | v vptr | v vtable

没有对象,就没有虚函数。


所以:

static + virtual 冲突

十二、虚函数总结

记住一句话:

普通函数看指针类型,虚函数看对象真实类型。


普通:

Animal *p 调用Animal函数

虚函数:

Animal *p 发现真实对象Dog 调用Dog函数

十三、面向对象三大核心

学到这里:

C++面向对象三个核心:

1. 封装

隐藏细节:

private getter/setter

2. 继承

代码复用:

Dog is Animal

3. 多态

同一个接口:

不同表现:

Animal* | | Dog Cat Bird

虚函数就是实现多态的关键。


对于同学们,可以把虚函数先理解为:

爸爸规定一个动作,孩子可以重新定义;程序运行时,会自动找到真正孩子的动作。


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询