Tcl 如何正确地从表达式返回字符串常量?

Tcl 如何正确地从表达式返回字符串常量?,tcl,Tcl,假设我有以下几点: proc one_or_other {v1 v2} { if {[expr {round(rand())}]} { expr {$v1} } else { expr {$v2} } } 它随机返回两个值$v1或$v2中的一个。很简单。它可以正常工作,直到你给它一个像01232这样的字符串,它可以被expr解释为一个八进制数。所以,一个或另一个1234 01232给你666一半的时间 如果我想让这个函数正好给出我传递给它

假设我有以下几点:

proc one_or_other {v1 v2} {
    if {[expr {round(rand())}]} {
        expr {$v1}
    } else {
        expr {$v2}
    }
}
它随机返回两个值$v1或$v2中的一个。很简单。它可以正常工作,直到你给它一个像01232这样的字符串,它可以被expr解释为一个八进制数。所以,一个或另一个1234 01232给你666一半的时间


如果我想让这个函数正好给出我传递给它的两个字符串中的一个,例如,它给我1234或01232,我应该用什么替换expr{$v1}?

一般来说,如果你想要一个通用字符串常量作为命令的结果,该命令最好不是expr。问题是expr的定义是,如果可能的话,将其结果转换为规范的数值形式,即使没有其他操作

这意味着如果x设置为0x123,我总是希望expr{$x}生成291

让我们稍微向后看一下,看看expr{$x}的字节码反汇编:

有很多东西我们可以忽略,但是最后的操作码是一个常量,它是操作数堆栈上的变量名,读取操作数堆栈上的变量并结合上一个操作,这是$x,一个tryCvtToNumeric,稍后会更详细,还有一个done来标记这个小脚本的结束

那么tryCvtToNumeric在做什么呢?它实现了expr的结果语义,它总是放在那里,除非编译器可以证明它不是必需的,这对大多数代码来说都是正确的。没有办法把它关掉

分解过程会显示它。我将跳过这里可以忽略的部分

(0) push1 0     # "tcl::mathfunc::round"
(2) push1 1     # "tcl::mathfunc::rand"
(4) invokeStk1 1 
(6) invokeStk1 2 
(8) nop 
(9) nop 
(10) jumpFalse1 +16     # pc 26
(12) startCommand +12 1     # next cmd at pc 24, 1 cmds start here
(21) loadScalar1 %v0    # var "v1"
(23) tryCvtToNumeric 
(24) jump1 +14  # pc 38
(26) startCommand +12 1     # next cmd at pc 38, 1 cmds start here
(35) loadScalar1 %v1    # var "v2"
(37) tryCvtToNumeric 
(38) done 
如您所见,这里有tryCvtToNumeric实例;您的代码中包含转换。还要注意,代码使用了更高效的局部变量表ops来读取变量。那很好

当需要常规字符串结果时,请改用其他标准Tcl命令。特别是,set x,即一个参数是一个类似$x的命令,string cat 0x123是一个生成文本字符串0x123的命令,if-frequency-ignored的结果是执行的分支中脚本的结果。然后,您的实际脚本将变得没有额外的表达式:

让我们通过分解来检查:

(0) push1 0     # "tcl::mathfunc::round"
(2) push1 1     # "tcl::mathfunc::rand"
(4) invokeStk1 1 
(6) invokeStk1 2 
(8) nop 
(9) jumpFalse1 +15  # pc 24
(11) startCommand +11 1     # next cmd at pc 22, 1 cmds start here
(20) loadScalar1 %v0    # var "v1"
(22) jump1 +13  # pc 35
(24) startCommand +11 1     # next cmd at pc 35, 1 cmds start here
(33) loadScalar1 %v1    # var "v2"
(35) done 
这是相同的代码…除了没有给你带来麻烦的tryCvtToNumeric ops。还少了一个禁止操作

就我个人而言,我会使用这个稍微高效一点的版本:

proc one_or_other {v1 v2} {
    if {rand() < 0.5} {
        return $v1
    } else {
        return $v2
    }
}

我更喜欢使用显式返回,并避免不需要的函数调用。

谢谢。如果我想返回一个可能是01232的常量值呢?为了完整性,您可能想使用if-else表达式变量:expr{rand<0.5?$v1:$v2}至于可以解释为八进制的值,使用Donal所示的显式if命令调用更安全。expr会意外地解释任何看起来像八进制的东西。如果您想坚持使用expr,则必须清理v1和v2引用的值,例如使用。
(0) push1 0     # "tcl::mathfunc::round"
(2) push1 1     # "tcl::mathfunc::rand"
(4) invokeStk1 1 
(6) invokeStk1 2 
(8) nop 
(9) jumpFalse1 +15  # pc 24
(11) startCommand +11 1     # next cmd at pc 22, 1 cmds start here
(20) loadScalar1 %v0    # var "v1"
(22) jump1 +13  # pc 35
(24) startCommand +11 1     # next cmd at pc 35, 1 cmds start here
(33) loadScalar1 %v1    # var "v2"
(35) done 
proc one_or_other {v1 v2} {
    if {rand() < 0.5} {
        return $v1
    } else {
        return $v2
    }
}