Lecture4 多态

继承的内存结构

  • 顺序结构

Virtual Function

notion image
notion image
notion image
notion image
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 作为参数。
为了确保这段代码能够正确运行,需要满足以下条件:
  1. fld 必须是指向一个接受一个 int 参数并且返回 void 的函数的地址。
  1. 函数 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,并通过该表查找实际要调用的函数。
例如:
  • 在这个例子中,BaseDerived 类各自都有虚函数表(vtable)。
  • Derived 类对象通过基类指针调用 func1()func2() 时,虚指针 vptr 会指向 Derived 类的虚函数表,最终确保调用到的是 Derived 类的实现。

总结

  • 静态绑定在编译时确定函数调用,适用于普通成员函数。
  • 动态绑定通过虚函数表实现,在运行时确定函数调用,支持多态性。
  • 动态绑定是实现面向对象编程中多态的重要机制,允许程序在运行时根据对象的实际类型来选择正确的函数实现。
Loading...