VIRTUAL
- 来源: https://www.freebasic.net/wiki/wikka.php?wakka=KeyPgVirtual
- 最后更新: 2023-07-09
声明虚方法
语法
Type typename Extends base_typename
Declare Virtual Sub|Function|Property|Operator|Destructor ...
End Type描述
虚方法是可以被从声明它们的类型派生的数据类型重写的方法,从而实现动态多态性。与 Abstract(抽象)方法不同,虚方法必须有实现,当虚方法未被重写时将使用该实现。
派生类型可以通过声明具有相同标识符和签名的非静态方法来重写在其基类型中声明的虚方法,签名相同意味着参数数量和类型相同(不变参数)、调用约定相同,以及(如果有的话)返回类型相同(或对于按引用或按指针返回的协变返回类型):
- 如果仅在参数传递模式、调用约定或返回类型方面有所不同,则在编译时返回重写错误,
- 否则,对于任何其他签名差异(对应于两个方法在同一类型中可以重载的情况),只允许遮蔽。
虚方法的特性不会被派生类型中的重写方法隐式继承。如果该重写方法还需要在更低层次的派生类型中被重写,则也必须将其声明为虚方法。
另一方面,由于派生的静态方法永远无法重写基类的虚/抽象方法,因此它可以遮蔽任何具有相同标识符的基类方法(包括虚/抽象),而与签名无关。
当调用虚方法时,编译器可能需要通过 vtable 查找来确定对于给定对象应调用哪个方法。这需要在每个具有虚方法的类型顶部添加一个额外的隐藏 vtable 指针字段。此隐藏的 vptr 由内置的 Object 类型提供。因此,虚方法只能在直接或间接 extends(继承)Object 的类型中声明。
通过重写过程实现动态多态性:
- 通常,即使基类型引用/指针所引用的对象是从
typename派生的,也只有typename过程(或继承层次中更高的过程)可以通过基类型引用/指针访问。 - 但当过程是虚方法时,这将告知运行时程序通过 vtable 查找(运行时动态绑定)解析与真实对象类型最相关的最派生重写过程,而不是通常可从引用/指针的原始基类型访问的过程(编译时静态绑定)。
构造函数不能是虚方法,因为构造函数创建对象,而虚方法需要一个已存在的具有特定类型的对象。构造函数调用的类型在编译时从代码中确定。
此外,在构造函数内调用虚方法时,仅使用与此构造函数类型的对象对应的方法版本。这是因为 vptr 尚未被派生类型构造函数设置,只被本地类型构造函数设置。
在通过指向基类型的指针删除对象时,析构函数通常必须是虚方法,这样析构从最派生类型开始,一路向下到基类型。为此,可能需要在任何尚未要求显式析构的地方添加带有空主体的虚析构函数,以取代编译器构建的每个非虚隐式析构函数。
另一方面,当在析构函数(无论是否为虚)中调用虚(或抽象)方法时,仅使用与此析构函数类型的对象对应的方法版本,因为 vptr 在析构函数顶部根据其自身类型的 vtable 被重置。这避免了访问子方法,从而避免引用由子析构函数执行销毁的子成员。
对于在声明中包含 Virtual 的成员方法,也可以在对应的方法体上指定 Virtual,以提高代码可读性。
注意:在多级继承中,同名方法(相同标识符和签名)可以在每个继承层次级别声明为 Abstract、Virtual 或普通(无修饰符)。当混合使用修饰符时,通常的顺序是从继承层次顶部到底部:abstract -> virtual -> normal。
重写方法的访问控制(Public/Protected/Private)不会被内部多态过程考虑,仅在编译时的初始调用时才考虑。
Base (member access).method() 始终调用基类自己的方法,而不是重写方法。
注意:Virtual 也用作 Procptr 的限定符,以请求返回虚/抽象成员过程或成员运算符在 vtable 中的索引。
Procptr ( identifier, Virtual [ Any|user_proctype ] )
示例
start GeSHi
'' 使用重写子例程的示例
Type Hello Extends Object
Declare Virtual Sub hi( )
End Type
Type HelloEnglish Extends Hello
Declare Sub hi( ) '' 重写子例程
End Type
Type HelloFrench Extends Hello
Declare Sub hi( ) '' 重写子例程
End Type
Type HelloGerman Extends Hello
Declare Sub hi( ) '' 重写子例程
End Type
Sub Hello.hi( )
Print "hi!"
End Sub
Sub HelloEnglish.hi( ) '' 重写子例程
Print "hello!"
End Sub
Sub HelloFrench.hi( ) '' 重写子例程
Print "Salut!"
End Sub
Sub HelloGerman.hi( ) '' 重写子例程
Print "Hallo!"
End Sub
Randomize( Timer( ) )
Dim As Hello Ptr h
For i As Integer = 0 To 9
Select Case( Int( Rnd( ) * 4 ) + 1 )
Case 1
h = New HelloEnglish
Case 2
h = New HelloFrench
Case 3
h = New HelloGerman
Case Else
h = New Hello
End Select
h->hi( )
Delete h
Next
Sleepend GeSHi
start GeSHi
'' 使用重写析构函数以及
'' 具有协变返回值的重写函数的示例
Type myBase Extends Object
Declare Virtual Function clone () As myBase Ptr
Declare Virtual Sub Destroy ()
End Type
Function myBase.clone () As myBase Ptr
Dim As myBase Ptr pp = New myBase(This)
Print "myBase.clone() As myBase Ptr", pp
Function = pp
End Function
Sub myBase.Destroy ()
Print "myBase.Destroy()", , @This
Delete @This
End Sub
Type myDerived Extends myBase
Declare Function clone () As myDerived Ptr '' 具有协变返回值的重写成员函数
Declare Sub Destroy () '' 重写成员子例程
End Type
Function myDerived.clone () As myDerived Ptr '' 具有协变返回值的重写成员函数
Dim As myDerived Ptr pc = New myDerived(This)
Print "myDerived.clone() As myDerived Ptr", pc
Function = pc
End Function
Sub myDerived.Destroy () '' 重写成员子例程
Print "myDerived.Destroy()", , @This
Delete @This
End Sub
Dim As myDerived c
Dim As myBase Ptr ppc = @c
Dim As myDerived Ptr pcc = @c
Dim As myBase Ptr ppc1 = ppc->clone() '' 使用基类指针和多态性
Dim As myDerived Ptr pcc1 = pcc->clone() '' 使用派生指针和返回值协变
Print
ppc1->Destroy() '' 使用基类指针和多态性
pcc1->Destroy() '' 使用派生指针
Sleepend GeSHi
方言差异
- 仅在 -lang fb 方言中可用。
与 QB 的区别
- FreeBASIC 新增
参见
TypeObjectExtendsExtends ZstringExtends WstringAbstractOverride
返回 目录