ZetCode

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 表达式。