如何从表达式中正确 return 字符串常量?
How to properly return a string constant from an expression?
假设我有以下内容:
proc one_or_other {v1 v2} {
if {[expr {round(rand())}]} {
expr {$v1}
} else {
expr {$v2}
}
}
它随机returns两个值$v1
或$v2
之一。很简单。它可以正常工作,直到你给它一个像“01232”这样的字符串,它可以被 expr
解释为一个八进制数。所以,one_or_other 1234 01232
给你 666
一半的时间。
如果我希望此函数准确地给出我传递给它的两个字符串之一(例如,它给出“1234”或“01232”),我应该用什么替换 expr {$v1}
?
一般来说,如果你想要一个通用的字符串常量作为一个命令的结果,那个命令最好不要是expr
。问题是 expr
是 定义的 以尽可能将其结果转换为规范的数字形式,即使没有其他操作也是如此。
这意味着如果 x
设置为 0x123
,我 总是 期望 expr {$x}
产生 291
.
让我们稍微揭开面纱,看看 expr {$x}
:
的字节码反汇编
% tcl::unsupported::disassemble script {expr {$x}}
ByteCode 0x0x7f9683041b10, refCt 1, epoch 17, interp 0x0x7f9683024410 (epoch 17)
Source "expr {$x}"
Cmds 1, src 9, inst 5, litObjs 1, aux 0, stkDepth 1, code/src 0.00
Commands 1:
1: pc 0-3, src 0-8
Command 1: "expr {$x}"
(0) push1 0 # "x"
(2) loadStk
(3) tryCvtToNumeric
(4) done
有很多东西我们可以忽略,但最后的操作码是将一个常量(它是变量的名称)压入操作数堆栈,读取操作数堆栈上命名的变量(结合前面的操作,这确实 $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
个实例;您的代码中包含转换。 (另外请注意,代码使用更高效的局部变量 table 操作来读取变量。这很好。)
当您需要一般字符串结果时,请改用其他标准 Tcl 命令。特别地,set x
(即 one 参数)是一个类似于 $x
的命令,string cat 0x123
是一个生成文字字符串 0x123
,而if
(通常被忽略)的结果是脚本在被采取的分支中的结果。然后您的实际脚本变为(没有额外的 expr
s):
proc one_or_other {v1 v2} {
if {round(rand())} {
set v1
} else {
set 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
那是相同的代码……只是没有给您带来麻烦的 tryCvtToNumeric
操作。 (也少了一个空操作。)
就个人而言,我会改用这个稍微更高效的版本:
proc one_or_other {v1 v2} {
if {rand() < 0.5} {
return $v1
} else {
return $v2
}
}
我更喜欢使用显式 return
s,以避免调用我不需要的函数。
假设我有以下内容:
proc one_or_other {v1 v2} {
if {[expr {round(rand())}]} {
expr {$v1}
} else {
expr {$v2}
}
}
它随机returns两个值$v1
或$v2
之一。很简单。它可以正常工作,直到你给它一个像“01232”这样的字符串,它可以被 expr
解释为一个八进制数。所以,one_or_other 1234 01232
给你 666
一半的时间。
如果我希望此函数准确地给出我传递给它的两个字符串之一(例如,它给出“1234”或“01232”),我应该用什么替换 expr {$v1}
?
一般来说,如果你想要一个通用的字符串常量作为一个命令的结果,那个命令最好不要是expr
。问题是 expr
是 定义的 以尽可能将其结果转换为规范的数字形式,即使没有其他操作也是如此。
这意味着如果 x
设置为 0x123
,我 总是 期望 expr {$x}
产生 291
.
让我们稍微揭开面纱,看看 expr {$x}
:
% tcl::unsupported::disassemble script {expr {$x}}
ByteCode 0x0x7f9683041b10, refCt 1, epoch 17, interp 0x0x7f9683024410 (epoch 17)
Source "expr {$x}"
Cmds 1, src 9, inst 5, litObjs 1, aux 0, stkDepth 1, code/src 0.00
Commands 1:
1: pc 0-3, src 0-8
Command 1: "expr {$x}"
(0) push1 0 # "x"
(2) loadStk
(3) tryCvtToNumeric
(4) done
有很多东西我们可以忽略,但最后的操作码是将一个常量(它是变量的名称)压入操作数堆栈,读取操作数堆栈上命名的变量(结合前面的操作,这确实 $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
个实例;您的代码中包含转换。 (另外请注意,代码使用更高效的局部变量 table 操作来读取变量。这很好。)
当您需要一般字符串结果时,请改用其他标准 Tcl 命令。特别地,set x
(即 one 参数)是一个类似于 $x
的命令,string cat 0x123
是一个生成文字字符串 0x123
,而if
(通常被忽略)的结果是脚本在被采取的分支中的结果。然后您的实际脚本变为(没有额外的 expr
s):
proc one_or_other {v1 v2} {
if {round(rand())} {
set v1
} else {
set 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
那是相同的代码……只是没有给您带来麻烦的 tryCvtToNumeric
操作。 (也少了一个空操作。)
就个人而言,我会改用这个稍微更高效的版本:
proc one_or_other {v1 v2} {
if {rand() < 0.5} {
return $v1
} else {
return $v2
}
}
我更喜欢使用显式 return
s,以避免调用我不需要的函数。