Tcl 编程/expr
算术和逻辑运算(加上一些字符串比较)在 Tcl 中集中在expr命令中。它接受一个或多个参数,将它们评估为一个表达式,并返回结果。expr命令的语言(也用于if、for、while命令的条件参数)基本上等同于 C 的表达式,带有中缀运算符和函数。与 C 不同,对变量的引用必须使用$var。示例
set a [expr {($b + sin($c))/2.}] if {$a > $b && $b > $c} {puts "ordered"} for {set i 10} {$i >= 0} {incr i -1} {puts $i...} ;# countdown
Tcl 语法和expr语法的区别可以这样对比
[f $x $y] ;# Tcl: embedded command f($x,$y) ;# expr: function call, comma between arguments
与 Tcl 语法形成对比的是,"单词"之间的空格是可选的(但为了更好的可读性,仍然建议使用空格)。字符串常量必须始终用引号括起来(或者如果你愿意,用大括号括起来)
if {$keyword eq "foo"} ...
另一方面,Tcl 命令始终可以嵌入表达式中,通常用方括号括起来
proc max {x y} {expr {$x>$y? $x: $y}} expr {[max $a $b] + [max $c $d]}
在包含混合类型数字的表达式中,整数将被强制转换为双精度数
% expr 1+2. 3.0
重要的是要知道,两个整数的除法将作为整数除法进行
% expr 1/2 0
如果至少有一个操作数是double,则可以得到(可能是预期的)浮点除法
% expr 1/2. 0.5
如果你想评估用户输入的字符串,但始终使用浮点除法,只需在调用 expr 之前对其进行转换,将 "/" 替换为 "*1./"(在每次除法之前乘以浮点数 1.)
expr [string map {/ *1./} $input]
在大多数情况下,将单个用大括号括起来的参数传递给expr更安全更高效。例外情况是
- 没有要替换的变量或嵌入式命令
- 要替换的运算符或整个表达式
原因是 Tcl 解析器会解析没有用大括号括起来的表达式,而 expr 会再次解析结果。这可能会导致恶意代码利用成功
% set e {[file delete -force *]} % expr $e ;# will delete all files and directories % expr {$e} ;# will just return the string value of e
用大括号括起来的表达式比没有用大括号括起来的表达式快得多,可以轻松测试
% proc unbraced x {expr $x*$x} % proc braced x {expr {$x*$x}} % time {unbraced 42} 1000 197 microseconds per iteration % time {braced 42} 1000 34 microseconds per iteration
浮点数的字符串表示的精度也由 tcl_precision 变量控制。以下示例返回非零值,因为在创建字符串表示时,第二项被截断为 12 位
% expr 1./3-[expr 1./3] 3.33288951992e-013
而这个用大括号括起来的表达式更像预期那样工作
% expr {1./3-[expr 1./3]} 0.0
算术、位和逻辑运算符与 C 中的类似,条件运算符也与其他语言(尤其是 C)中的类似
- c?a:b -- 如果 c 为真,则评估 a,否则评估 b
条件运算符可用于简洁的函数式代码(请注意,以下示例需要 Tcl 8.5,以便可以在其定义内部调用 fac())
% proc tcl::mathfunc::fac x {expr {$x<2? 1 : $x*fac($x-1)}} % expr fac(5) 120
算术运算符也与 C 中的类似
- + 加法
- -(二元:减法。一元:改变符号)
- * 乘法
- /(如果两个操作数都是整数,则进行整数除法
- %(模运算,仅对整数有效)
- ** 幂运算(从 Tcl 8.5 开始可用)
以下运算符仅对整数有效
- &(AND)
- |(OR)
- ^(XOR)
- ~(NOT)
- << 左移
- >> 右移
以下运算符接受整数(其中 0 被视为假,其他所有值都被视为真),并返回一个真值,0(假)或 1(真)
- &&(and)
- ||(or)
- !(not - 一元)
如果两侧的操作数都是数字,则这些运算符将它们作为数字进行比较。否则,将进行字符串比较。它们返回一个真值,0(假)或 1(真)
- == 等于
- != 不等于
- > 大于
- >= 大于或等于
- < 小于
- <= 小于或等于
由于真值是整数,因此可以将它们作为整数用于进一步计算,如符号函数所示
proc sgn x {expr {($x>0) - ($x<0)}} % sgn 42 1 % sgn -42 -1 % sgn 0 0
以下运算符在其操作数的字符串表示形式上进行操作
- eq 字符串相等
- ne 不字符串相等
"等于"和"字符串相等"的区别示例
% expr {1 == 1.0} 1 % expr {1 eq 1.0} 0
从 Tcl 8.5 开始,以下运算符也可用
- a in b - 如果 a 是列表 b 的成员,则为 1,否则为 0
- a ni b - 如果 a 不是列表 b 的成员,则为 1,否则为 0
在 8.5 之前,很容易编写一个等效的函数
proc in {list el} {expr {[lsearch -exact $list $el]>=0}}
使用示例
if [in $keys $key] ...
一旦 8.5 在你的工作要运行的任何地方都可用,你可以用以下代码重写它
if {$key in $keys} ...
以下函数是内置的
- abs(x) - 绝对值
- acos(x) - 反余弦。acos(-1) = 3.14159265359(Pi)
- asin(x) - 反正弦
- atan(x) - 反正切
- atan2(y,x)
- ceil(x) - 下一个最高的整数
- cos(x) - 余弦
- cosh(x) - 双曲余弦
- double(x) - 转换为浮点数
- exp(x) - e 的 x 次方。exp(1) = 2.71828182846(欧拉数,e)
- floor(x) - 下一个最低的整数
- fmod(x,y) - 浮点模运算
- hypot(y,x) - 斜边(sqrt($y*$y+$x*$x),但精度更高)
- int(x) - 转换为整数(32 位)
- log(x) - 以 e 为底的对数
- log10(x) - 以 10 为底的对数
- pow(x,y) - x 的 y 次方
- rand() - 随机数 > 0.0 且 < 1.0
- round(x) - 将数字四舍五入到最接近的整数
- sin(x) - 正弦
- sinh(x) - 双曲正弦
- sqrt(x) - 平方根
- srand(x) - 用种子 x 初始化随机数生成
- tan(x) - 正切
- tanh(x) - 双曲正切
- wide(x) - 转换为宽(64 位)整数
使用info functions找出哪些函数可用
% info functions round wide sqrt sin double log10 atan hypot rand abs acos atan2 srand sinh floor log int tanh tan asin ceil cos cosh exp pow fmod
如果你不想每次需要进行少量计算时都编写 [[expr {$x+5}]],可以轻松地将运算符导出为 Tcl 命令
foreach op {+ - * / %} {proc $op {a b} "expr {\$a $op \$b}"}
之后,你就可以像在 LISP 中一样调用这些运算符
% + 6 7 13 % * 6 7 42
当然,可以至少为 + 和 * 允许可变参数,或者为 - 允许单个参数情况来改进它
proc - {a {b ""}} {expr {$b eq ""? -$a: $a-$b}}
类似地,可以公开 expr 函数
foreach f {sin cos tan sqrt} {proc $f x "expr {$f($x)}"}
在 Tcl 8.5 中,可以在::tcl::mathop命名空间中调用运算符作为命令
% tcl::mathop::+ 6 7 13
你可以将它们导入到当前命名空间中,以便使用简写数学命令
% namespace import ::tcl::mathop::* % + 3 4 ;# way shorter than [expr {3 + 4}] 7 % * 6 7 42
从 Tcl 8.5 开始,你可以在::tcl::mathfunc命名空间中提供 proc,然后可以在expr表达式中使用它们
% proc tcl::mathfunc::fac x {expr {$x < 2? 1: $x * fac($x-1)}} % expr fac(100) 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000
这对递归函数或参数需要一些 expr 计算的函数特别有用
% proc ::tcl::mathfunc::fib n {expr {$n<2? 1: fib($n-2)+fib($n-1)}} % expr fib(6) 13