跳至内容

Tcl 编程/expr

来自维基教科书,自由的教科书

算术和逻辑运算(加上一些字符串比较)在 Tcl 中集中在expr命令中。它接受一个或多个参数,将它们评估为一个表达式,并返回结果。expr命令的语言(也用于ifforwhile命令的条件参数)基本上等同于 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 功能

[编辑 | 编辑源代码]

如果你不想每次需要进行少量计算时都编写 [[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
华夏公益教科书