generated from cotes2020/chirpy-starter
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5e6fd8d
commit 3178c4c
Showing
3 changed files
with
180 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
--- | ||
title: C++ 虚函数表与多态 | ||
date: 2024-12-12 13:20:00 +0800 | ||
categories: [笔记, C++] | ||
tags: [C++, 虚函数, 虚函数表, 多态] | ||
--- | ||
|
||
## 虚函数表指针 | ||
|
||
虚函数引入后类的变化 | ||
|
||
```cpp | ||
class A {}; | ||
A a; | ||
cout << sizeof(a) << endl; // 输出对象的大小是1 | ||
``` | ||
一个空类, 只要对象占用内存空间, 那么这个类的大小至少是1; | ||
```cpp | ||
class A { | ||
void func1() {} | ||
void func2() {} | ||
}; | ||
A a; | ||
cout << sizeof(a) << endl; // 输出对象的大小仍然是1 | ||
``` | ||
|
||
类A的普通成员函数并不会占用类对象的内存空间, 所以类A的大小仍然是1; | ||
|
||
```cpp | ||
class A { | ||
virtual void func1() {} | ||
}; | ||
A a; | ||
cout << sizeof(a) << endl; // 输出对象的大小是4 (Visual Studio解决方案中的×86平台, Linux平台GCC编译的结果可能是8) | ||
``` | ||
当一个或多个虚函数加入到类中后, 编译器会向类中插入一个看不见的成员变量, 这个成员变量类似下方的伪代码形式: | ||
```cpp | ||
class A { | ||
void* vptr; // 虚函数表指针(Virtual Table Pointer) | ||
virtual void func1() {} | ||
}; | ||
``` | ||
|
||
也就是说, 编译器给类中加入的**虚函数表指针(Virtual Table Pointer)**, 它的大小正好是4个字节(32位系统), 这4个字节是占用类对象的内存空间的; | ||
|
||
## 虚函数表 | ||
|
||
```cpp | ||
class A { | ||
void func1() {} | ||
void func2() {} | ||
virtual void vfunc() {} | ||
}; | ||
``` | ||
当类A中存在至少一个虚函数时, 编译器就会给类A生成一个**虚函数表(Virtual Table)**, 这个虚函数表会一直伴随着类A的对象存在; | ||
通过编译链接后生成一个可执行文件之后, 类A和伴随类A的虚函数表会被保存到可执行文件中, 在这个可执行文件执行时, 伴随类A的虚函数表也会被加载到内存中; | ||
## 虚函数表指针被赋值的时机 | ||
- **虚函数表指针(vptr)**: 类中有虚函数的时候, 编译器会在类中插入一个虚函数表指针; | ||
- **虚函数表(vtbl)**: 类中有虚函数的时候, 编译器会生成一个虚函数表; | ||
```cpp | ||
class A { | ||
virtual void vfunc() {} | ||
}; | ||
``` | ||
|
||
对于有虚函数的类A, 在编译时, 编译器会向类A的构造函数中(如果没有构造函数, 编译器则会创建一个构造函数), 安插为`vptr`赋值的代码, 伪代码如下: | ||
|
||
```cpp | ||
class A { | ||
void* vptr; // 虚函数表指针 | ||
virtual void vfunc() {} | ||
A() { // 类A的构造函数 | ||
vptr = &A::vtable; // 为vptr赋值, 使得vptr指向类A的虚函数表vtbl | ||
} | ||
}; | ||
``` | ||
创建类A的对象时, 会调用类A的构造函数, 在构造函数中会为`vptr`赋值, 使得`vptr`指向类A的虚函数表; | ||
## 类对象在内存中的布局 | ||
```cpp | ||
class A { | ||
public: | ||
void func1() {} // 普通成员函数1 | ||
void func2() {} // 普通成员函数2 | ||
virtual void vfunc1() {} // 虚函数1 | ||
virtual void vfunc2() {} // 虚函数2 | ||
virtual ~A() {} // 虚析构函数 | ||
private: | ||
int a; // 成员变量 | ||
int b; | ||
}; | ||
``` | ||
|
||
此时生成了类A对象后, 类A对象在内存中的布局如下: | ||
|
||
![类A对象在内存中的布局](/assets/posts/Cpp-VirtualTable-And-Polymorphism/01.png){:width="700px"} | ||
|
||
函数的普通成员函数`func1`和`func2`不会占用类对象的内存空间; | ||
|
||
所以, 这个类A的对象占用的内存空间大小是`4(虚函数指针) + 4(int a;) + 4(int b;) = 12`字节 | ||
|
||
## 虚函数的工作原理及多态性的体现 | ||
|
||
### 多态性(动态多态) | ||
|
||
- **静态多态**: 函数重载: 同一个类中, 函数名相同, 参数列表不同, 有种说法是**函数重载不算多态** | ||
- **动态多态**: 虚函数: 父类指针指向子类对象, 调用虚函数时, 会调用子类的虚函数 | ||
|
||
通过**父类指针new一个子类对象**或通过**父类引用绑定一个子类对象**的时候 | ||
|
||
如果用父类指针来调用虚函数, 那么会调用子类的虚函数 | ||
|
||
#### 代码实现上的多态 | ||
|
||
```cpp | ||
class A { | ||
public: | ||
virtual void vfunc() {} | ||
A() { | ||
vptr = &A::vtbl; | ||
} | ||
}; | ||
``` | ||
如果是通过`vptr`来找到虚函数表指针`vtbl`, 再通过查询虚函数表指针`vtbl`找到虚函数表的入口地址, 并去执行虚函数`vfunc`, 这就被称之为**多态** | ||
例子: | ||
```cpp | ||
class Base { | ||
public: | ||
virtual void vfunc() {} | ||
}; | ||
Base* pa = new Base(); | ||
pa->vfunc(); // 是多态 | ||
Base base; | ||
base.vfunc(); // 不是多态 | ||
Base* ybase = &base; | ||
ybase->vfunc(); // 是多态 | ||
``` | ||
|
||
#### 表现形式上的多态 | ||
|
||
1. 程序中既要存在父类也要存在子类, 父类中必须要有虚函数, 子类中必须要重写父类的虚函数 | ||
2. 父类指针要指向子类对象或者父类引用要绑定(指向)子类对象 | ||
3. 通过父类指针或引用, 调用子类中重写的虚函数 | ||
|
||
```cpp | ||
class Derive : public Base { | ||
public: | ||
virtual void vfunc() {} // Derive类重写了Base类的虚函数vfunc | ||
}; | ||
Derive derive; | ||
Base* pbase = &derive; // 父类指针指向子类对象 | ||
pbase->vfunc(); // 是多态:通过父类指针调用虚函数,实际调用的是子类的vfunc | ||
|
||
Base& pbase2 = *new Derive(); // 父类引用绑定子类对象, 注意自行delete释放内存 | ||
pbase2.vfunc(); // 是多态:通过父类引用调用虚函数,实际调用的是子类的vfunc | ||
|
||
Derive derive2; | ||
Base& yinbase = derive2; // 父类引用绑定子类对象 | ||
yinbase.vfunc(); // 是多态:通过父类引用调用虚函数,实际调用的是子类的vfunc | ||
|
||
``` |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,9 @@ | |
"nojekyll", | ||
"Rodrigues", | ||
"Simiao", | ||
"vfunc", | ||
"vptr", | ||
"vtbl", | ||
"wangsimiao" | ||
] | ||
} | ||
|