跳至內容

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
華夏公益教科書