特性 | 重载(Overloading) | 覆盖(Override) |
---|---|---|
关系类型 | 同一个类中的水平关系 | 父子类之间的垂直关系 |
方法数量 | 多个方法之间的关系 | 一个方法或一对方法之间的关系 |
决定机制 | 根据调用的实参表和形参表选择方法体 | 根据对象类型(对象对应存储空间类型)决定 |
确定时间 | 编译期确定 | 运行时确定 |
目的 | 提供多种实现同一功能的方法,适应不同参数类型或数量 | 通过多态性允许子类提供与父类不同的行为 |
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
class Base {
public:
virtual void print() const {
std::cout << "Base class" << std::endl;
}
};
class Derived : public Base {
public:
void print() const override {
std::cout << "Derived class" << std::endl;
}
};
C++ 运行时多态(动态绑定)的核心在于:
函数调用的确定时间不同:
实现方式:
#include
class Base {
public:
virtual void show() { std::cout << "Base::show()" << std::endl; }
virtual void display() { std::cout << "Base::display()" << std::endl; }
virtual ~Base() {} // 析构函数设为虚函数,保证子类对象析构正确
};
class Derived : public Base {
public:
void show() override { std::cout << "Derived::show()" << std::endl; }
void display() override { std::cout << "Derived::display()" << std::endl; }
};
int main() {
Base* b = new Derived();
b->show(); // 调用 Derived::show()
b->display(); // 调用 Derived::display()
delete b; // 确保正确调用 Derived 析构函数
return 0;
}
Derived::show()
Derived::display()
成员 | 地址/偏移量 | 说明 |
---|---|---|
vptr 指针 | 0x1000 |
指向 Base 的虚函数表 |
Base::show() | 0x2000 |
在 vtable 中的第 1 个槽位 |
Base::display() | 0x2008 |
在 vtable 中的第 2 个槽位 |
成员 | 地址/偏移量 | 说明 |
---|---|---|
vptr 指针 | 0x3000 |
指向 Derived 的虚函数表 |
Derived::show() | 0x4000 |
覆盖 Base::show() |
Derived::display() | 0x4008 |
覆盖 Base::display() |
说明:
Base
及Derived
的对象中,都有一个隐藏的虚指针(vptr),用于指向该类的虚函数表(vtable)Base
和Derived
各自的vtable
存储了类的虚函数地址,按声明顺序存储vtable
替换了父类vtable
中对应函数的地址当 Base* b = new Derived();
时:
b
实际存储的是 Derived 对象,但指针类型是Base*
b
指向Derived
的vptr
,即b->vptr = &Derived_vtable
b->show();
的步骤:
b
通过vptr
找到Derived
的vtable
vtable
第 1 个函数地址指向Derived::show()
Derived::show()
,而不是Base::show()
同理,b->display();
也会调用Derived::display()
Base
的析构函数需要是 virtual
?Base* b = new Derived();
delete b; // 调用 ~Base() 还是 ~Derived()?
如果 ~Base()
不是虚函数:
delete b;
只会调用Base::~Base()
,不会调用Derived::~Derived()
,可能导致内存泄漏如果 ~Base()
是虚函数:
vtable
记录Derived::~Derived()
的地址,delete b;
时会正确调用Derived::~Derived()
,确保正确析构额外的内存开销:
额外的函数调用开销:
如果在性能关键代码中,可以:
final
禁止继承优化)inline
+ constexpr
)C 语言的函数调用依赖**栈(Stack)**来管理参数传递、局部变量存储以及返回地址保存。每次函数调用都会创建一个栈帧(Stack Frame),它是管理函数执行所需信息的结构。
一个函数的栈帧通常包括以下部分(从高地址到低地址):
栈帧组成部分 | 作用 |
---|---|
返回地址 | 存储调用者的返回地址(即call 指令保存的地址) |
上一帧指针(EBP) | 记录前一个函数的EBP ,用于恢复调用者的栈环境 |
函数参数 | 存储传递给被调用函数的参数(按调用约定决定具体位置) |
局部变量 | 存储该函数的局部变量,占用栈上的空间 |
临时寄存器保存区 | 存放函数调用前需要保存的寄存器值(如EBX , EDI , ESI 等) |
栈的增长方向:
假设我们有如下代码:
#include
void func(int a, int b) {
int sum = a + b; // 局部变量
printf("sum: %d\n", sum);
}
int main() {
func(3, 5);
return 0;
}
当main()
调用func(3, 5)
时,栈的变化如下(以x86
体系结构为例):
main()
开始执行)ESP(栈指针)
和EBP(帧指针)
仅用于main
函数本身的运行,假设栈的初始状态如下:
高地址 → ────────────────────────
| main() 的返回地址 |
EBP → | main() 的上一帧指针 |
ESP → | main() 局部变量 |
低地址 → ────────────────────────
func(3, 5)
被main()
调用当main()
调用func(3, 5)
时,CPU 会执行call func
,在栈上创建func
的栈帧:
高地址 → ───────────────────────────
| main() 的返回地址 |
EBP → | main() 的上一帧指针 |
| main() 局部变量 |
| ───────────────────────── |
| `func` 返回地址 (main 的下一条指令) |
| `func` 的上一帧指针(原 EBP) |
ESP → | `func` 的参数:b=5 |
| `func` 的参数:a=3 |
低地址 → ───────────────────────────
ESP
递减,为func
分配空间EBP
保存了main
的栈帧起始位置,方便返回时恢复func
开始执行func
内部操作
int sum = a + b; // 分配局部变量 sum
栈帧更新如下:
高地址 → ───────────────────────────
| main() 的返回地址 |
EBP → | main() 的上一帧指针 |
| main() 局部变量 |
| ───────────────────────── |
| `func` 返回地址 (main 的下一条指令) |
| `func` 的上一帧指针(原 EBP) |
| `func` 的参数:b=5 |
| `func` 的参数:a=3 |
ESP → | `func` 局部变量:sum=8 |
低地址 → ───────────────────────────
总结:
EBP
指向func
栈帧的起始位置,作为稳定访问基点ESP
指向当前栈顶(sum 变量的地址),局部变量从ESP
递减分配func
执行完毕,准备返回函数返回前:
EBP
还原(恢复main
的EBP
)ESP
还原(回到func
调用前的位置)main()
继续执行高地址 → ────────────────────────
| main() 的返回地址 |
EBP → | main() 的上一帧指针 |
ESP → | main() 局部变量 |
低地址 → ────────────────────────
ESP
和EBP
恢复到main
之前的状态,main()
继续执行。
main()
运行中)[ main() 栈帧 ]
┌────────────────┐
│ 返回地址 │
├────────────────┤
│ 旧 EBP │
├────────────────┤
│ 局部变量 │ ← ESP
└────────────────┘
func(3, 5)
[ func() 栈帧 ]
┌────────────────┐
│ 返回地址 │
├────────────────┤
│ 旧 EBP │
├────────────────┤
│ 参数 b = 5 │
├────────────────┤
│ 参数 a = 3 │
├────────────────┤
│ sum = a + b │ ← ESP
└────────────────┘
func
执行完毕,返回main()
[ main() 栈帧 ] (恢复到原状态)
┌────────────────┐
│ 返回地址 │
├────────────────┤
│ 旧 EBP │
├────────────────┤
│ 局部变量 │ ← ESP
└────────────────┘
vptr
,指向其类的vtable
,vtable
记录虚函数的地址,从而实现动态绑定vptr
查找vtable
,获取函数地址并执行,实现运行时的多态vtable
会替换相应的函数地址,确保Base*
指向Derived
时调用Derived
的实现virtual
,否则会导致内存泄漏final
、CRTP
等方式优化栈帧用于管理函数调用的状态:
栈指针ESP
:
帧指针EBP
:
EBP
,方便恢复调用者栈帧函数调用和返回的流程:
call
指令 → 参数入栈 → 跳转到目标函数ret
指令 → 恢复EBP
→ 弹出返回地址 → 回到调用处