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