Tcl 中的表达式
最后修改于 2023 年 10 月 18 日
在本 Tcl 教程中,我们将讨论表达式。 在 Tcl 语言中,表达式并未内置到核心语言中。 而是通过 expr
命令来评估表达式。
表达式由操作数和运算符构成。 表达式的运算符指示要对操作数应用的操作。 表达式中运算符的求值顺序由运算符的优先级和关联性决定。
运算符是一个特殊符号,它指示执行某个特定过程。 编程语言中的运算符来自数学。 程序员使用数据。 运算符用于处理数据。 操作数是运算符的输入(参数)之一。
下表显示了 Tcl 语言中使用的一组运算符
类别 | 符号 |
---|---|
符号、位运算、逻辑非 | - + ~ ! |
求幂 | ** |
算术 | + - * / % |
移位 | << >> |
关系 | == != < > <= >= |
字符串比较 | eq ne |
List | in ni |
位运算 | & | ^ |
Boolean | && || |
三元 | ?: |
一个运算符通常有一个或两个操作数。 那些只使用一个操作数的运算符称为一元运算符。 那些使用两个操作数的运算符称为二元运算符。 还有一个三元运算符 ?:
,它使用三个操作数。
基本运算符
基本运算符是常用的运算符。 它们是符号运算符、算术运算符、模数和求幂运算符。
#!/usr/bin/tclsh puts [expr +2] puts [expr -2] puts [expr -(-2)] puts [expr 2+2] puts [expr 2-2] puts [expr 2*2] puts [expr 2/2] puts [expr 2/2.0] puts [expr 2 % 2] puts [expr 2 ** 2]
上面的例子展示了 Tcl 中常用运算符的用法。
puts [expr +2]
在此代码行中,我们使用加号运算符。 它对数字没有影响。 它仅表示该数字为正数。 它可以省略,并且大多数情况下都会省略。
puts [expr -2] puts [expr -(-2)]
减号运算符是强制性的。 它表示该数字为负数。 减号运算符会更改数字的符号。 在第二行中,减号运算符将 -2 更改为正 2。
puts [expr 2+2] puts [expr 2-2] puts [expr 2*2] puts [expr 2/2]
以上几行显示了常用的算术运算符。
puts [expr 2 % 2]
百分号 (%) 是模数或余数运算符。 它找到一个数除以另一个数的余数。 表达式 2 % 2
,2 模 2 等于 0,因为 2 除以 2 等于 1,余数为 0。 因此,代码行将零打印到控制台。
puts [expr 2 ** 2]
这是求幂运算符。 代码行将 4 打印到控制台。
$ ./exp.tcl 2 -2 2 4 0 4 1 1.0 0 4
除法运算符
初学者经常对除法运算感到困惑。 在许多编程语言中,有两种除法运算:整数除法和非整数除法。 这也适用于 Tcl。
% expr 3/2 1 % expr 3/2.0 1.5
注意整数除法和浮点数除法之间的区别。 当至少一个操作数是浮点数时,结果也是浮点值。 结果更精确。 如果两个操作数都是整数,则结果也是整数。
赋值和增量运算符
Tcl 中没有赋值运算符 =
,也没有增量和减量 (++
和 --
) 运算符。 这些运算符在其他计算机语言中很常见。 相反,Tcl 有命令。
% set a 5 5 % incr a 6 % incr a 7 % incr a -1 6
上面的代码显示了用于实现缺失运算符的命令。
% set a 5
在 Python 中,我们会这样做:a = 5
。 在 Tcl 中,我们使用 set
命令为变量赋值。
% incr a 6
在 C、Java 和许多其他语言中,我们会这样将变量递增 1:a++;
。 在 Tcl 中,我们使用 incr
命令。 默认情况下,该值递增 1。
% incr a -1 6
上面的代码展示了如何将变量递减 1,这可以通过基于 C 的语言中的 --
减量运算符来实现。
布尔运算符
在 Tcl 中,我们有以下逻辑运算符
符号 | 名称 |
---|---|
&& | 逻辑与 |
|| | 逻辑或 |
! | 逻辑非 |
布尔运算符也称为逻辑运算符。
#!/usr/bin/tclsh set x 3 set y 8 puts [expr $x == $y] puts [expr $y > $x] if {$y > $x} { puts "y is greater than x" }
许多表达式都产生布尔值。布尔值用于条件语句。
puts [expr $x == $y] puts [expr $y > $x]
关系运算符始终产生布尔值。 这两行打印 0 和 1。 在 Tcl 中,0 为 false,任何非零值都为 true。
if {$y > $x} { puts "y is greater than x" }
仅当括号内的条件满足时,才会执行 if
命令的主体。 $y > $x
返回 true,因此消息 "y is greater than x" 将打印到终端。
#!/usr/bin/tclsh puts [expr 0 && 0] puts [expr 0 && 1] puts [expr 1 && 0] puts [expr 1 && 1]
此示例显示逻辑与 &&
运算符。 仅当两个操作数都为 true 时,它才为 true。
$ ./andoperator.tcl 0 0 0 1
逻辑或 ||
运算符在任一操作数为 true 时,都会得到 true。
#!/usr/bin/tclsh puts [expr 0 || 0] puts [expr 0 || 1] puts [expr 1 || 0] puts [expr 1 || 1]
如果运算符的任一边为 true,则操作的结果为 true。
$ ./oroperator.tcl 0 1 1 1
否定运算符 !
将 true 变为 false,将 false 变为 true。
#!/usr/bin/tclsh puts [expr ! 0] puts [expr ! 1] puts [expr ! (4<3)]
该示例展示了否定运算符的实际应用。
$ ./not.tcl 1 0 1
||
和 &&
运算符是短路求值的。 短路求值表示仅当第一个参数不足以确定表达式的值时,才对第二个参数进行求值:当逻辑与的第一个参数求值为 false 时,总值必须为 false;当逻辑或的第一个参数求值为 true 时,总值必须为 true。 短路求值主要用于提高性能。
一个例子可以更清楚地说明这一点。
#!/usr/bin/tclsh proc One {} { puts "Inside one" return false } proc Two {} { puts "Inside two" return true } puts "Short circuit" if { [One] && [Two] } { puts "Pass" } puts "###################" if { [Two] || [One] } { puts "Pass" }
我们在示例中有两个过程。 (稍后将介绍过程和条件。)它们被用作布尔表达式中的操作数。 我们看看它们是否被调用。
if { [One] && [Two] } { puts "Pass" }
One
过程返回 false。 短路 &&
不会评估第二个过程。 这是不必要的。 一旦一个操作数为 false,逻辑结论的结果始终为 false。 只有 "Inside one" 会打印到控制台。
puts "###################" if { [Two] || [One] } { puts "Pass" }
在第二种情况下,我们使用 ||
运算符,并将 Two
过程用作第一个操作数。 在这种情况下,字符串 "Inside two" 和 "Pass" 将打印到终端。 再次不需要评估第二个操作数,因为一旦第一个操作数求值为 true,逻辑或始终为 true。
$ ./shortcircuit.tcl Short circuit Inside one ################### Inside two Pass
shorcircuit.tcl
脚本的结果。
关系运算符
关系运算符用于比较值。 这些运算符始终产生布尔值。 在 Tcl 中,0 代表 false,1 代表 true。 关系运算符也称为比较运算符。
符号 | 含义 |
---|---|
< | 小于 |
<= | 小于或等于 |
> | 大于 |
>= | 大于或等于 |
== | 等于 |
!= | 不等于 |
该表显示了六个 Tcl 关系表达式。
#!/usr/bin/tclsh puts [expr 3 < 4] puts [expr 3 == 4] puts [expr 4 >= 3] puts [expr 4 != 3]
在 Tcl 中,我们使用 ==
运算符来比较数字。 像 Ada、Visual Basic 或 Pascal 这样的某些语言使用 =
来比较数字。
$ ./rel.tcl 1 0 1 1
该示例打印四个布尔值。
位运算符
十进制数对人类来说是自然的。 二进制数是计算机的固有数。 二进制、八进制、十进制和十六进制符号只是相同数字的表示法。 位运算符处理二进制数的位。 位运算符很少用于 Tcl 等高级语言中。
符号 | 含义 |
---|---|
~ | 按位取反 |
^ | 按位异或 |
& | 按位与 |
| | 按位或 |
按位取反运算符 将每个 1 变为 0,将 0 变为 1。
% puts [expr ~7] -8 % puts [expr ~-8] 7
运算符反转 7 的所有位。 其中一位也决定了该数字是否为负数。 如果我们再次否定所有位,我们将再次得到数字 7。
按位与运算符对两个数字执行逐位比较。只有当操作数中对应的位都为 1 时,结果的该位才为 1。
00110 & 00011 = 00010
第一个数字是 6 的二进制表示,第二个是 3,结果是 2。
% puts [expr 6 & 3] 2 % puts [expr 3 & 6] 2
按位或运算符对两个数字执行逐位比较。当操作数中对应的位任一为 1 时,结果的该位为 1。
00110 | 00011 = 00111
结果是 00110
或十进制的 7。
% puts [expr 6 | 3] 7 % puts [expr 3 | 6] 7
按位异或运算符对两个数字执行逐位比较。当操作数中对应的位任一为 1 但不是两者都为 1 时,结果的该位为 1。
00110 ^ 00011 = 00101
结果是 00101
或十进制的 5。
% puts [expr 6 ^ 3] 5 % puts [expr 3 ^ 6] 5
展开运算符
展开运算符 {*}
将列表中的每个项目作为当前命令的单个参数。 列表是基本的 Tcl 数据结构;它将在后面的章节中介绍。
#!/usr/bin/tclsh set nums {1 2 3 4 5 6} puts $nums puts [tcl::mathfunc::max {*}$nums] puts [tcl::mathfunc::min {*}$nums]
展开运算符与两个数学函数一起使用。
set nums {1 2 3 4 5 6}
创建了一个名为 nums
的数值列表。 列表是有序的值集合。
puts $nums
列表的内容将打印到终端。
puts [tcl::mathfunc::max {*}$nums]
tcl::mathfunc::max
是一个标准数学函数。 它不处理列表。 这些数字应该作为单个参数传递。 展开运算符将列表中的项目转换为单个项目。
$ ./expansion.tcl 1 2 3 4 5 6 6 1
示例输出。
运算符优先级
运算符优先级告诉我们哪些运算符首先被求值。优先级级别对于避免表达式中的歧义是必需的。
以下表达式的结果是 28 还是 40?
3 + 5 * 5
就像在数学中一样,乘法运算符的优先级高于加法运算符。所以结果是 28。
(3 + 5) * 5
我们使用括号来更改求值顺序。 括号内的表达式始终先被求值。
下表显示了按优先级排序的常用 Tcl 运算符(先是最高优先级)
类别 | 符号 | 结合性 |
---|---|---|
符号、位运算、逻辑非 | - + ~ ! |
左 |
求幂 | ** |
左 |
算术 | + - * / % |
左 |
移位 | << >> |
左 |
关系 | == != < > <= >= |
左 |
字符串比较 | eq ne |
左 |
List | in ni |
左 |
位运算 | & | ^ |
左 |
Boolean | && || |
左 |
三元 | ?: |
右 |
表格同一行上的运算符具有相同的优先级。
!/usr/bin/tclsh puts [expr 3 + 5 * 5] puts [expr (3 + 5) * 5] puts [expr ! 1 || 1] puts [expr ! (1 || 1)]
在此代码示例中,我们展示了一些常用的表达式。 每个表达式的结果取决于优先级级别。
puts [expr 3 + 5 * 5]
此行打印 28。 乘法运算符的优先级高于加法。 首先,计算 5*5
的乘积,然后加 3。
puts [expr (3 + 5) * 5]
圆括号可用于更改优先级级别。 在上面的表达式中,将 3 加到 5 上,然后将结果乘以 5。
puts [expr ! 1 || 1]
在这种情况下,否定运算符具有更高的优先级。 首先,将第一个 true (1) 值取反为 false (0),然后 ||
运算符组合 false 和 true,最终得到 true。
$ ./precedence.tcl 28 40 1 0
输出。
结合性
有时,优先级不能令人满意地确定表达式的结果。 还有另一条规则称为关联性。 运算符的关联性决定了使用相同优先级级别的运算符的求值顺序。
9 / 3 * 3
此表达式的结果是多少,9 还是 1? 乘法、除法和模数运算符是左到右关联的。 因此,表达式的求值方式为:(9 / 3) * 3
,结果为 9。
算术、布尔、关系和位运算符都是从左到右关联的。
三元运算符是右关联的。
三元运算符
三元运算符 ?:
是一个条件运算符。 对于我们想要根据条件表达式选择两个值之一的情况,它是一个方便的运算符。
cond-exp ? exp1 : exp2
如果 cond-exp 为 true,则评估 exp1 并返回结果。 如果 cond-exp 为 false,则评估 exp2 并返回其结果。
#!/usr/bin/tclsh set age 32 set adult [expr $age >= 18 ? true : false] puts "Adult: $adult"
在大多数国家/地区,成年以您的年龄为基础。 如果您超过某个年龄,您就是成年人。 这是一个三元运算符的情况。
set adult [expr $age >= 18 ? true : false]
首先,评估赋值运算符右侧的表达式。 三元运算符的第一阶段是条件表达式求值。 因此,如果年龄大于或等于 18,则返回 ?
字符后面的值。 如果不是,则返回 :
字符后面的值。 然后将返回值分配给 adult 变量。
$ ./ternary.tcl Adult: true
一个 32 岁的人是成年人。
计算质数
我们将计算质数。 教程后面将介绍一些功能(列表、循环)。
#!/usr/bin/tclsh # primes.tcl set nums { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 } puts "Prime numbers:" foreach num $nums { if { $num==1 } { continue } if { $num==2 || $num==3 } { puts -nonewline "$num " continue } set i [expr int(sqrt($num))] set isPrime true while { $i > 1 } { if { $num % $i == 0 } { set isPrime false } incr i -1 } if { $isPrime } { puts -nonewline "$num " } } puts ""
在上面的例子中,我们处理许多不同的运算符。 质数(或素数)是一个大于 1 的自然数,它恰好有两个不同的自然数除数:1 和它本身。 我们取一个数字,并将其除以从 1 到选定的数字的数字。 实际上,我们不必尝试所有较小的数字,我们可以除以小于所选数字的平方根的数字。 公式将起作用。 我们使用余数除法运算符。
set nums { 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 }
我们从这个整数列表中计算素数。
if { $num==1 } { continue }
根据定义,1 不是质数。 continue
命令跳到循环的下一次迭代。
if { $num==2 || $num==3 } { puts -nonewline "$num " continue }
我们跳过 2 和 3 的计算。 它们是素数,不需要进一步的计算。 注意相等和条件或运算符的用法。 ==
的优先级高于 ||
运算符。 因此,我们不需要使用括号。
set i [expr int(sqrt($num))]
如果我们只尝试小于目标数字的平方根的数字,那就可以了。
while { $i > 1 } { if { $num % $i == 0 } { set isPrime false } incr i -1 }
在此 while 循环中,i
是数字的计算平方根。 我们使用 incr
命令将 i
递减 1,循环的每个循环周期。 当 i
小于 1 时,循环结束。 例如,我们有数字 9。 9 的平方根是 3。 我们将 9 除以 3 和 2。 这足以进行我们的计算。
if { $isPrime } { puts -nonewline "$num " }
如果求余运算符对于任何 i 值返回 0,则目标数字不是质数。
$ ./primes.tcl Prime numbers: 2 3 5 7 11 13 17 19 23 29 31
在本 Tcl 教程中,我们介绍了 Tcl 表达式。