Skip to content

可变参数


允许过程接受可变数量的参数。

前言:

普通过程接受固定数量的参数,在定义时需要指定每个参数的数据类型。

每次调用该过程时,都应提供预期数量的参数,且类型须能转换为指定类型。

可变参数允许定义接受可变数量参数的过程。

不同编程语言对可变参数过程的支持差异很大。

可变参数过程可能引发类型安全问题,因为语言对可变参数过程的支持不具备类型安全性:

  • 它允许从栈或其他位置提取任意数量的参数,

  • 同时根据用户传入的参数来确定其类型。

如果可变参数均为相同类型或与相同类型兼容,也可以改用固定大小的数组传递,但这需要调用方做更多处理。

目录

1. 可变参数过程的使用语法

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_* 关键字。

Back to top


2. FB 的 va_* 关键字族

已废弃的 va_* 支持,但保留以兼容部分目标平台。

FB 的 va_* 关键字共有 3 个(va_firstva_argva_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

vb
' 可变参数函数:
'    第一个(固定)参数提供元素数量。
'    可变参数均为字符串,但最后一个必须是 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.13

end GeSHi

start GeSHi

vb
' 可变参数子程序:
'    第一个(固定)参数提供元素数量。
'    可变参数均为 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.302585

end GeSHi

start GeSHi

vb
' 可变参数子程序:
'    第一个(固定)参数为可变参数的类型(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.8

end GeSHi

Back to top


3. C 的 va_* 关键字族

推荐用于所有新代码的 va_* 支持。

C 的 va_* 关键字共有 5 个(cva_listcva_startcva_copycva_argcva_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_startcva_copy "构造" 的 cva_list 数据类型的 argument_list 对象。

cva_listcva_startcva_copycva_argcva_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_listcva_end 之后,可以通过再次调用 cva_start(或 cva_copy)重新使用和初始化 argument_list

cva_start(或 cva_copy)和 cva_end 的调用必须在同一过程中成对出现(以确保跨平台兼容性)。

示例:

start GeSHi

vb
' 可变参数函数:
'    第一个(固定)参数提供元素数量。
'    可变参数均为字符串,但最后一个必须是 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.07

end GeSHi

start GeSHi

vb
' 可变参数子程序:
'    第一个(固定)参数提供元素数量。
'    可变参数均为 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.302585

end GeSHi

start GeSHi

vb
' 可变参数子程序:
'    第一个(固定)参数为可变参数的类型(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.8

end GeSHi

Back to top


另请参阅

  • ... (Ellipsis)
  • va_first
  • va_arg
  • va_next
  • cva_list
  • cva_start
  • cva_copy
  • cva_arg
  • cva_end

返回 目录

基于 FreeBASIC 官方文档翻译 如有侵权请联系我们删除
FreeBASIC 是开源项目,与微软公司无隶属关系