---
title: "虚函数与多态"
author: "Perrin Yong"
author_profile: https://www.pystone.net/profile/
published_by: "Perrin Yong"
canonical: https://www.pystone.net/notes/cpp-virtual-function-polymorphism/
type: note
content_role: unspecified
visibility: public
id_stability: rename-stable
source_path: "10-计算机、信息技术与工程/02-编程语言与运行时/C++/虚函数与多态.md"
content_hash: 65bfd2ceb78150267a13265e0e265c5078f9e228462b6fe54838c4df912410df
knowledge_version: 224c990773de.5fa8af6e39fa
site_commit: 224c990773de166d23a886306577dd90379529ce
notes_commit: 5fa8af6e39fa3891d1b9b4832bfa6c4e0ecaaf0a
---
# 虚函数与多态

﻿# 虚函数与多态

> 创建时间：2020/3/20 0:49

### 多态

**多态** 是指在不同的条件下表现出不同的状态。
编译期间多态：重载函数，根据参数列表的不同寻找合适的函数
运行时多态：虚函数

### 虚函数

  * 纯虚函数：父类没有实现版本，完全交给子类，且必须实现。是虚函数再加上 = 0；
  * 半虚函数：父类可以实现，子类需要重写

>   * 抽象类是指包括至少一个纯虚函数的类。
>

> 以下函数不能作为虚函数
>
>   1. 友元函数，它不是类的成员函数
>   2. 全局函数
>   3. 静态成员函数，它没有this指针
>   4. 构造函数，拷贝构造函数，以及赋值运算符重载
>

> 创建对象的过程：
>  https://www.cnblogs.com/yulei0713/p/11326254.html

#### 实现原理

为每个类对象添加一个隐藏成员，保存了一个指向 **函数地址数组** 的 **指针** ，称为 **虚表指针** （vptr），这种数组成为 **虚函数表** （virtual function table, vtbl），即，每个类使用一个虚函数表，每个类对象用一个虚表指针。

如果类中有N个虚函数，那么其虚函数表将有(N * 4)字节的大小。

> 基类对象包含一个指针，该指针指向基类所有虚函数的地址表，派生类对象将包含一个指向独立地址表的指针，如果派生类提供了虚函数的新定义，该虚函数表将保存新函数的地址，如果派生类没有重新定义虚函数，该虚函数表将保存函数原始版本的地址。如果派生类定义了新的虚函数，则该函数的地址将被添加到虚函数表中，注意虚函数无论多少个都只需要在对象中添加一个虚函数表的地址。
>  调用虚函数时，程序将查看存储在对象中的虚函数表地址，转向相应的虚函数表，使用类声明中定义的第几个虚函数，程序就使用数组的第几个函数地址，并执行该函数。

![assets/0015 - 虚函数与多态__resource-001-62e99b69c530.png](/media/7d16278d7ceea3a9062c.png)

> **关于对象大小问题**
>  有虚函数或虚继承的类实例化后的 **对象大小** 至少为4字节（确切的说是一个指针的字节数；说至少是因为还要加上其他非静态数据成员，还要考虑对齐问题）；没有虚函数和虚继承的类实例化后的对象大小至少为1字节（没有非静态数据成员的情况下也要有1个字节来记录它的地址）。

#### 其他问题

（1）当存在类继承并且析构函数中有必须要进行的操作时（如需要释放某些资源，或执行特定的函数）析构函数需要是虚函数，否则若使用父类指针指向子类对象，在delete时只会调用父类的析构函数，而不能调用子类的析构函数，从而造成内存泄露或达不到预期结果；
（2）内联函数不能为虚函数：内联函数需要在编译阶段展开，而虚函数是运行时动态绑定的，编译时无法展开；
（3）构造函数不能为虚函数：构造函数在进行调用时还不存在父类和子类的概念，父类只会调用父类的构造函数，子类调用子类的，因此不存在动态绑定的概念；但是构造函数中可以调用虚函数，不过并没有动态效果，只会调用本类中的对应函数；
（4）静态成员函数不能为虚函数：静态成员函数是以类为单位的函数，与具体对象无关，虚函数是与对象动态绑定的。

#### 为何构造函数不能是虚函数

  1. 从存储空间角度：
虚函数对应一个vtable，要占存储空间。如果构造函数是虚的，就需要通过 vtable来调用，可是对象还没有实例化，也就是内存空间还没有，无法找到vtable，所以构造函数不能是虚函数。
  2. vbtl在构造函数调用后才建立，因而构造函数不可能成为虚函数
