如何在tcl中将变量参数从一个函数传递到另一个函数

如何在tcl中将变量参数从一个函数传递到另一个函数,tcl,variadic-functions,Tcl,Variadic Functions,我想将在一个函数中获得的变量参数传递给另一个函数,但我不能这样做。函数获取偶数个变量参数,然后必须在数组中进行转换。下面是一个例子 过程abc1获取两个参数(kk),而不是形式abc1过程这些参数必须传递到过程abc,其中列表到数组的转换必须完成。列表到数组的转换在程序1中有效,即abc1,但在第二个程序中无效,即abc 获得的误差如下所示 proc abc {args} { puts "$args" array set arg $args } proc abc1 {args}

我想将在一个函数中获得的变量参数传递给另一个函数,但我不能这样做。函数获取偶数个变量参数,然后必须在数组中进行转换。下面是一个例子

过程
abc1
获取两个参数(
kk
),而不是形式
abc1
过程这些参数必须传递到过程
abc
,其中列表到数组的转换必须完成。列表到数组的转换在程序1中有效,即
abc1
,但在第二个程序中无效,即
abc

获得的误差如下所示

proc abc {args} {
    puts "$args"
    array set arg $args
}

proc abc1 {args} {
    puts "$args"
    array set arg $args
    set l2 [array get arg]
    abc $l2
}

abc1 k k
abc k k
输出:

k k
{k k}
list must have an even number of elements
    while executing
"array set arg $l1"
    (procedure "abc" line 8)
    invoked from within
"abc $l2"
    (procedure "abc1" line 5)
    invoked from within
"abc1 k k"
    (file "vfunction.tcl" line 18)

当您传递
abc$l2
时,您正在将abc1的
args
作为单个参数传递给
abc
。因此在
abc
args
中,保存了一个包含单个项的列表(
{k}

您可以这样做:

proc abc {args} {    
  #the next line assumes that if $args has only a single item, then it is 
  #a list in itself. It's a risky decision to make but should work here.
  if { [llength $args] == 1 } { set args [lindex $args 0] }
  puts "$args"
  array set arg $args
}

两者之间有很大的区别

abc k k

在第一种情况下,传递两个参数,每个参数都是
k
。在第二种情况下,您传递的是一个事物列表—在您的示例中,是一个包含两个k的列表:
kk

Nir有意绕过这个问题,但更好的解决方案是编写
abc1
,以便正确调用
abc

proc abc1 {args} {
  array set arg $args
  set l2 [array get arg]
  eval abc $l2
  # or just
  # eval abc $args
}
最佳解决方案:扩展替换 正确的方法是确保外部过程(在堆栈方面)正确调用内部过程;如果需要多个参数,则应提供多个参数。随着Tcl 8.5的出现,这只需使用一种称为扩展替换的小语言语法即可实现:

proc abc1 {args} {
    puts "$args"
    array set arg $args
    set l2 [array get arg]
    abc {*}$l2
    # Or combine the two lines above into: abc {*}[array get arg]
}
所做的只是说单词的其余部分应该被分解(使用列表语法规则)并用作多个参数,而不是Tcl默认的“一个可视单词形成一个单词”规则。这是理想的选择

旧的解决方案:Eval命令 如果出于某种原因(例如,Tcl 8.4或更早版本),您仍在使用旧版本的Tcl,则使用
eval
命令,而不是上述语法:

eval abc $l2
对于上面的
eval
,您可能会在旧代码中看到一些更有效的方法;例如:

eval [linsert $l2 0 abc]
eval [list abc] [lrange $l2 0 end]
# ... etc ...

但是实际上,它们都被abc{*}$l2所淘汰了,因为它更短、更简单、更快。(它只是在8.4或之前版本中不可用,而且有太多的部署尚未升级。)如果可以,请使用扩展语法。事实上,8.5及更高版本的惯用Tcl代码几乎不需要
eval
;这被证明是正确的程度甚至让语言的维护人员感到相当惊讶。

堆栈跟踪中的行号是错误的;这可能是因为我重新格式化了,也可能是因为在发布之前进行了蒸馏。不管怎样,这对问题都不是很重要。这是一份由用户“angle”提交的答案的逐字副本(随后被删除)。你是从两个不同的账户中回答,还是抄袭了其他人的答案?首先,我报告的“angle”是垃圾邮件,因为他的答案是我答案的逐字逐句的副本(而不是相反,你可以看看时间。第二,关于“有更好的方法解决这个问题”,我不确定:“eval”在我看来,这个选项并不安全,扩展替换只在8.5中出现。如果你能想出一个更好的解决方案,你应该发布它。另一个可能比这两个更好的选择是通过引用传递参数并使用
upvar
访问它们,而不是
{*}
eval
eval [linsert $l2 0 abc]
eval [list abc] [lrange $l2 0 end]
# ... etc ...