Lecture4 多态
继承的内存结构
- 顺序结构
Virtual Function
alt text
函数指针 和 类的成员函数地址table
这段代码看起来像是用C或C++编写的。它定义了一个函数指针并使用它来调用一个函数。让我们逐步分析一下这段代码。
首先,
void (*vf)(int)
是定义了一个名为 vf
的函数指针,该指针指向一个接受一个整型参数且返回值类型为 void
的函数。接着,
(void (*)(int))fld
这部分将一个名为 fld
的表达式的值转换(类型强制转换)为 void (*)(int)
类型的指针。这里假设 fld
已经是某个函数的地址或者是一个可以被解释为函数地址的值。然后,
vf = (void (*)(int))fld;
这行代码将 fld
转换后的函数指针赋值给了 vf
。最后,
vf(20);
这一行通过函数指针 vf
调用了这个函数,并传入了整数 20 作为参数。为了确保这段代码能够正确运行,需要满足以下条件:
fld
必须是指向一个接受一个int
参数并且返回void
的函数的地址。
- 函数
fld
必须在之前已经声明和定义过。
如果我们想要编写一个完整的示例程序,我们可以这样做:
在这个例子中,
&myFunction
取得的是 myFunction
的地址,然后将其转换为 void (*)(int)
类型。之后通过 vf
函数指针调用 myFunction(20)
。在C或C++语言中,函数指针是一种特殊的指针类型,它可以存储函数的地址。函数指针允许你将函数当作变量一样传递给其他函数,或者通过指针来调用函数。函数指针的基本定义形式如下:
其中:
return_type
是函数返回类型的说明。
pointer_name
是函数指针的名称。
parameter_list
是函数参数列表,描述函数接收的参数类型。
示例定义:
这定义了一个函数指针
example
,它指向一个接受一个 int
和一个 double
参数,并且返回 void
的函数。使用示例:
假设我们有一个函数
myFunc
定义如下:我们可以这样定义和使用函数指针:
在你的原始代码中:
这里的
void (*vf)(int)
定义了一个函数指针 vf
,它指向一个接受一个 int
参数且返回 void
的函数。接下来 (void (*)(int))fld
是将 fld
强制转换为上述定义的函数指针类型,然后将这个结果赋给了 vf
。为了这段代码能正常工作,
fld
必须是指向一个具有相同签名(即 void (*)(int)
)的函数的地址。如果 fld
是一个函数名,那么它默认就是该函数的地址,可以直接使用,无需取地址符 &
在C++中,绑定(Binding) 是指在程序运行中将函数调用与函数实现关联的过程。根据绑定的时机,绑定分为静态绑定(Static Binding)和动态绑定(Dynamic Binding)。这两者的区别在于函数调用的解析是发生在编译时还是运行时,它们在实现面向对象多态性时起着非常重要的作用。
1. 静态绑定(Static Binding)
- 静态绑定也被称为早绑定(Early Binding)。
- 在编译时就确定了函数调用的具体实现,也就是说编译器在编译阶段就已经决定了程序中所调用的函数。
- 这通常发生在以下场景:
- 普通成员函数(非虚函数)的调用。
- 使用对象名直接调用成员函数时。
- 使用静态类型的指针或引用来调用函数时(没有涉及到虚函数)。
例如:
在上面的例子中,
obj.func()
的调用在编译时已经确定了是 Base
类中的 func()
,因此称为静态绑定。2. 动态绑定(Dynamic Binding)
- 动态绑定也被称为晚绑定(Late Binding)或运行时绑定(Run-time Binding)。
- 在运行时决定具体调用哪个函数。这通常用于实现多态性。
- 动态绑定通过虚函数(virtual function)实现,涉及到基类指针或引用的调用时,动态绑定确保调用的是最具体派生类中的实现。
- 虚函数表(vtable)和虚指针(vptr)通常用于实现动态绑定。
例如:
在这个例子中,
Base* ptr = new Derived();
使得 ptr
指向了一个 Derived
类型的对象。调用 ptr->func()
时,由于 func()
是一个虚函数,所以编译器在编译时无法确定具体调用哪个实现。在运行时,通过虚函数表找到 Derived
类的 func()
,所以这里的调用会输出 "Derived::func()"
。这就是动态绑定。区别总结
特性 | 静态绑定(Static Binding) | 动态绑定(Dynamic Binding) |
别名 | 早绑定(Early Binding) | 晚绑定(Late Binding) |
绑定时机 | 编译时确定 | 运行时确定 |
实现机制 | 普通成员函数、非虚函数 | 虚函数 |
典型场景 | 对象直接调用普通成员函数 | 基类指针/引用调用派生类中的虚函数 |
使用的关键字 | 无需使用任何特殊关键字 | 使用 virtual 关键字定义虚函数 |
多态性支持 | 不支持多态性 | 支持多态性(允许不同类的派生类有不同实现) |
静态绑定和动态绑定的使用场景
- 静态绑定用于程序中的绝大多数场景。它效率较高,因为在编译时已经确定了具体的函数调用,因此在运行时无需通过虚函数表查找,直接调用。
- 动态绑定用于实现多态性,是面向对象编程中非常重要的特性。它允许基类指针或引用在运行时调用派生类的函数,从而实现动态行为变化。
动态绑定的实现机制
在 C++ 中,动态绑定通常是通过虚函数表(vtable)和虚指针(vptr)来实现的:
- 虚函数表(vtable):每个包含虚函数的类有一个虚函数表,表中存储了指向该类所有虚函数的函数指针。
- 虚指针(vptr):每个对象包含一个虚指针,指向它所属类的虚函数表。
- 当调用虚函数时,程序会通过对象的
vptr
访问相应的 vtable
,并通过该表查找实际要调用的函数。例如:
- 在这个例子中,
Base
和Derived
类各自都有虚函数表(vtable
)。
- 当
Derived
类对象通过基类指针调用func1()
或func2()
时,虚指针vptr
会指向Derived
类的虚函数表,最终确保调用到的是Derived
类的实现。
总结
- 静态绑定在编译时确定函数调用,适用于普通成员函数。
- 动态绑定通过虚函数表实现,在运行时确定函数调用,支持多态性。
- 动态绑定是实现面向对象编程中多态的重要机制,允许程序在运行时根据对象的实际类型来选择正确的函数实现。