可变参数
- Source: https://www.freebasic.net/wiki/wikka.php?wakka=ProPgVariadicArguments
- Last revised: 2022-08-06
允许过程接受可变数量的参数。
前言:
普通过程接受固定数量的参数,在定义时需要指定每个参数的数据类型。
每次调用该过程时,都应提供预期数量的参数,且类型须能转换为指定类型。
可变参数允许定义接受可变数量参数的过程。
不同编程语言对可变参数过程的支持差异很大。
可变参数过程可能引发类型安全问题,因为语言对可变参数过程的支持不具备类型安全性:
它允许从栈或其他位置提取任意数量的参数,
同时根据用户传入的参数来确定其类型。
如果可变参数均为相同类型或与相同类型兼容,也可以改用固定大小的数组传递,但这需要调用方做更多处理。
目录
2. FB 的 va_* 关键字族(已废弃的 va_* 支持,但保留以兼容部分目标平台)
3. C 的 va_* 关键字族(推荐用于所有新代码的 va_* 支持)
1. 可变参数过程的使用语法
省略号 "..."(三个点)作为最后一个参数,用于过程声明(及定义)中,表示可变长度参数列表:
- 至少必须始终指定一个固定参数:
固定参数可以通过任何对用户无约束的机制,提供关于可变参数数量的信息。
否则可以在可变长度参数列表末尾添加一个终止参数,但这会保留一个特殊参数值,不允许用于有效的可变参数。
(如果选择通过指针传递所有可变参数,此时一个显而易见的终止参数是空指针)
过程必须使用 C 调用约定
cdecl进行调用。存在两个
va_*关键字族,用于在可变参数过程体中获取可变参数。可变参数过程永远不能被重载:
因此也不能在 UDT 的构造函数和属性中使用,
但可以声明为 abstract、virtual 或 override。
- 对于可变参数成员过程,隐式传递的 this 参数不计为第一个固定参数(至少必须显式指定一个固定参数)。
声明语法
declare { sub | function } proc_name cdecl ( param_list, ... ) { | [ byref ] as return_type }
param_list(参数列表)
用括号括起的逗号分隔固定参数列表。
return_type(返回类型)
Function 的返回类型。
proc_name(过程名)
过程的名称或符号。
调用语法
调用可变参数过程没有特殊之处。
只需像往常一样,先写固定参数,再跟上额外的可变参数即可。
可变参数只支持数值类型和指针:
通过可变参数传递的所有 byte 和 short 类型都会被隐式转换为整型。
通过可变参数传递的所有 single 类型都会被隐式转换为 double 类型。
用户可以直接传递
String(或Wstring),此时内部将以指向字符串(或宽字符串)数据的Zstring Ptr(或Wstring Ptr)作为实际传递的参数。
获取可变参数
可变参数可能使用栈或寄存器,取决于编译器的常规约定:
在过去某些编译器中,普通过程使用寄存器,而可变参数过程(始终)使用栈。
gcc 编译器家族普遍认为,可变参数过程的调用签名与正常指定过程完全相同。这意味着如果参数过多,部分可变参数可能在寄存器中,部分在栈上。
这些 va_* 关键字为在过程体中获取可变参数提供了接口:
- FreeBASIC 最早开发的
va_*关键字族是 FB 的va_*关键字族:
这些 FB 的 va_* 关键字非常针对 x86 架构,只能与 gas x86 GAS 汇编后端配合使用,因为它们使用指向参数栈的指针。
这些 FB 的 va_* 关键字不适用于所有目标平台(如 64 位),因为参数可能通过 CPU 寄存器传递给过程。
这些 FB 的 va_* 关键字现已废弃,但为了兼容部分目标平台而保留。
- 在较新的版本中,FreeBASIC 更新添加了用于可变参数过程参数列表的新关键字族:
这就是与所有目标平台兼容的 C 的 va_* 关键字族。
现在推荐所有新代码使用这些 C 的 va_* 关键字。
2. FB 的 va_* 关键字族
已废弃的 va_* 支持,但保留以兼容部分目标平台。
FB 的 va_* 关键字共有 3 个(va_first、va_arg、va_next):
pointer_variable = va_first( )
va_first 提供一个无类型的指针值,指向传递给过程的第一个可变参数。
variable = va_arg( argument_list, datatype )
va_arg 返回列表 argument_list 中当前参数,期望的数据类型为 datatype。
(在首次使用 va_arg 之前,argument_list 必须用 va_first 命令初始化)
argument_pointer = va_next( argument_list, datatype )
va_next 提供一个 datatype 指针值,指向列表 argument_list 中的下一个参数,datatype 为当前被跨过的参数的类型。
注意:
pointer_variable = va_first( )
计算最后一个固定参数地址之后的栈地址。
variable = va_arg( argument_list, datatype )
等价于:
variable = *cptr( datatype ptr, cptr( any ptr, argument_list ) )
argument_pointer = va_next( argument_list, datatype )
等价于:
argument_pointer = cptr( datatype ptr, cptr( any ptr, argument_list ) ) + 1
示例:
start GeSHi
' 可变参数函数:
' 第一个(固定)参数提供元素数量。
' 可变参数均为字符串,但最后一个必须是 float 类型。
' 用户的字符串参数实际上是通过 zstring 指针在底层传递的。
Function concatenation CDecl (ByVal count As Integer, ...) As String
Dim arg As Any Ptr
Dim s As String
arg = va_first()
For i As Integer = 1 To count
If i < count Then
s &= *va_arg(arg, ZString Ptr)
arg = va_next(arg, ZString Ptr)
Else
s &= va_arg(arg, Double)
End If
Next
Return s
End Function
Print concatenation(6, "Free", "BASIC", " ", "version", " ", 0.13)
Sleep
' 输出:FreeBASIC version 0.13end GeSHi
start GeSHi
' 可变参数子程序:
' 第一个(固定)参数提供元素数量。
' 可变参数均为 UDT Ptr 类型。
Type UDT
Dim As String s
Dim As Single d
End Type
Sub printUDT CDecl (ByVal count As Integer, ...)
Dim arg As Any Ptr
Dim pu As UDT Ptr
arg = va_first()
For i As Integer = 1 To count
pu = va_arg(arg, UDT Ptr)
Print pu->s, pu->d
arg = va_next(arg, UDT Ptr)
Next
End Sub
Dim As UDT u1, u2, u3
u1.s = "4*Atn(1)" : u1.d = 4 * Atn(1)
u2.s = "Sqr(2)" : u2.d = Sqr(2)
u3.s = "Log(10)" : u3.d = Log(10)
printUDT(3, @u1, @u2, @u3)
Sleep
' Output:
' 4*Atn(1) 3.141593
' Sqr(2) 1.414214
' Log(10) 2.302585end GeSHi
start GeSHi
' 可变参数子程序:
' 第一个(固定)参数为可变参数的类型(Integer Ptr 或 Double Ptr)。
' 必须在有效参数之后添加一个终止参数(空指针)。
Sub printNumericList CDecl (ByRef argtype As String, ...)
Dim As Integer datatype = 0
If UCase(argtype) = "INTEGER PTR" Then
datatype = 1
Print "List of integers : ";
ElseIf UCase(argtype) = "DOUBLE PTR" Then
datatype = 2
Print "List of doubles : ";
Else
Print "Invalid argument type"
Exit Sub
End If
Dim arg As Any Ptr
Dim pn As Any Ptr
arg = va_first()
Do
pn = va_arg(arg, Any Ptr)
If pn = 0 Then Exit Do
Print ,
Select Case As Const datatype
Case 1
Print *CPtr(Integer Ptr, pn);
Case 2
Print *CPtr(Double Ptr, pn);
End Select
arg = va_next(arg, Any Ptr)
Loop
Print
End Sub
printNumericList("Integer Ptr", @Type<Integer>(123), @Type<Integer>(456), @Type<Integer>(789), CPtr(Integer Ptr, 0))
printNumericList("Double Ptr", @Type<Double>(1.2), @Type<Double>(3.4), @Type<Double>(5.6), @Type<Double>(7.8), CPtr(Double Ptr, 0))
Sleep
' Output:
' List of integers : 123 456 789
' List of doubles : 1.2 3.4 5.6 7.8end GeSHi
3. C 的 va_* 关键字族
推荐用于所有新代码的 va_* 支持。
C 的 va_* 关键字共有 5 个(cva_list、cva_start、cva_copy、cva_arg、cva_end):
dim variable as cva_list
cva_list 是一种内置数据类型,用于在可变参数过程中操作可变长度参数列表。
cva_start( argument_list, last_param )
cva_start "构造" 声明为 cva_list 数据类型的 argument_list 对象,锚定到过程参数列表中省略号 "..." 之前声明的 last_param。
cva_copy( dst_list, src_list )
cva_copy 从已构造的 src_list 对象出发,"拷贝构造" 声明为 cva_list 数据类型的 dst_list 对象。
variable = cva_arg( argument_list, datatype )
cva_arg 返回 argument_list 中的当前参数,期望的数据类型为 datatype。
(cva_arg 在获取当前参数值后,会自动将 argument_list 递进到列表中的下一个参数)
cva_end( argument_list )
cva_end "析构" 之前由 cva_start 或 cva_copy "构造" 的 cva_list 数据类型的 argument_list 对象。
cva_list、cva_start、cva_copy、cva_arg、cva_end 的实现和生成代码因目标平台、后端和架构而异。
注意:
cva_list 的确切类型和大小取决于 -target、-arch、-gen 命令行选项:
在 -gen gas 下,cva_list 是一个指针。
在 -gen gcc 下,cva_list 是指针、结构体或结构体数组。gcc 中 cva_list 类型被替换为 "__builtin_va_list"。
32 位目标,gas 后端:type cva_list as any alias "char" ptr
32 位目标,gcc 后端:type cva_list as any alias "__builtin_va_list" ptr
Windows 64 位,gcc 后端:type cva_list as any alias "__builtin_va_list" ptr
Linux x86_64,gcc 后端:type cva_list as __va_list_tag alias "__builtin_va_list[]"
arm64,gcc 后端:type cva_list as __va_list alias "__builtin_va_list"
cva_start(或 cva_copy)相当于可变参数 argument_list 对象的构造函数(或拷贝构造函数),最终必须有匹配的 cva_end 调用(相当于析构函数)。
调用 argument_list 的 cva_end 之后,可以通过再次调用 cva_start(或 cva_copy)重新使用和初始化 argument_list。
cva_start(或 cva_copy)和 cva_end 的调用必须在同一过程中成对出现(以确保跨平台兼容性)。
示例:
start GeSHi
' 可变参数函数:
' 第一个(固定)参数提供元素数量。
' 可变参数均为字符串,但最后一个必须是 float 类型。
' 用户的字符串参数实际上是通过 zstring 指针在底层传递的。
Function concatenation CDecl (ByVal count As Integer, ...) As String
Dim s As String
Dim args As cva_list
cva_start(args, count)
For i As Integer = 1 To count
If i < count Then
s &= *cva_arg(args, ZString Ptr)
Else
s &= cva_arg(args, Double)
End If
Next
cva_end(args)
Return s
End Function
Print concatenation(6, "Free", "BASIC", " ", "version", " ", 1.07)
Sleep
' 输出:FreeBASIC version 1.07end GeSHi
start GeSHi
' 可变参数子程序:
' 第一个(固定)参数提供元素数量。
' 可变参数均为 UDT Ptr 类型。
Type UDT
Dim As String s
Dim As Single d
End Type
Sub printUDT CDecl (ByVal count As Integer, ...)
Dim args As cva_list
Dim pu As UDT Ptr
cva_start(args, count)
For i As Integer = 1 To count
pu = cva_arg(args, UDT Ptr)
Print pu->s, pu->d
Next
cva_end(args)
End Sub
Dim As UDT u1, u2, u3
u1.s = "4*Atn(1)" : u1.d = 4 * Atn(1)
u2.s = "Sqr(2)" : u2.d = Sqr(2)
u3.s = "Log(10)" : u3.d = Log(10)
printUDT(3, @u1, @u2, @u3)
Sleep
' Output:
' 4*Atn(1) 3.141593
' Sqr(2) 1.414214
' Log(10) 2.302585end GeSHi
start GeSHi
' 可变参数子程序:
' 第一个(固定)参数为可变参数的类型(Integer Ptr 或 Double Ptr)。
' 必须在有效参数之后添加一个终止参数(空指针)。
Sub printNumericList CDecl (ByRef argtype As String, ...)
Dim As Integer datatype = 0
If UCase(argtype) = "INTEGER PTR" Then
datatype = 1
Print "List of integers : ";
ElseIf UCase(argtype) = "DOUBLE PTR" Then
datatype = 2
Print "List of doubles : ";
Else
Print "Invalid argument type"
Exit Sub
End If
Dim args As cva_list
Dim pn As Any Ptr
cva_start(args, argtype)
Do
pn = cva_arg(args, Any Ptr)
If pn = 0 Then Exit Do
Print ,
Select Case As Const datatype
Case 1
Print *CPtr(Integer Ptr, pn);
Case 2
Print *CPtr(Double Ptr, pn);
End Select
Loop
Print
cva_end(args)
End Sub
printNumericList("Integer Ptr", @Type<Integer>(123), @Type<Integer>(456), @Type<Integer>(789), CPtr(Integer Ptr, 0))
printNumericList("Double Ptr", @Type<Double>(1.2), @Type<Double>(3.4), @Type<Double>(5.6), @Type<Double>(7.8), CPtr(Double Ptr, 0))
Sleep
' Output:
' List of integers : 123 456 789
' List of doubles : 1.2 3.4 5.6 7.8end GeSHi
另请参阅
... (Ellipsis)va_firstva_argva_nextcva_listcva_startcva_copycva_argcva_end
返回 目录