# Tcl 中的表达式

> 原文： [https://zetcode.com/lang/tcl/expressions/](https://zetcode.com/lang/tcl/expressions/)

在 Tcl 教程的这一部分中，我们将讨论表达式。 在 Tcl 语言中，表达式未内置到核心语言中。 而是使用`expr`命令对表达式求值。

表达式是根据操作数和运算符构造的。 表达式的运算符指示将哪些运算应用于操作数。 表达式中运算符的求值顺序由运算符的优先级和关联性确定。

运算符是特殊符号，表示已执行某个过程。 编程语言的运算符来自数学。 程序员处理数据。 运算符用于处理数据。 操作数是运算符的输入（参数）之一。

下表显示了 Tcl 语言中使用的一组运算符：

| 类别 | 符号 |
| --- | --- |
| 按位，符号，逻辑非 | `- + ~ !` |
| 求幂 | `**` |
| 算术 | `+ - * / %` |
| 移位 | `<< >>` |
| 关系 | `== != < > <= >=` |
| 字符串比较 | `eq ne` |
| 列表 | `in ni` |
| 按位 | `& &#124; ^` |
| 布尔 | `&& &#124;&#124;` |
| 三元 | `?:` |

一个运算符通常有一个或两个操作数。 那些仅使用一个操作数的运算符称为一元运算符。 那些使用两个操作数的对象称为二进制运算符。 还有一个三元运算符`?:`，它可以处理三个操作数。

## 基本运算符

基本运算符是常用的运算符。 这些是符号运算符，算术运算符，模和幂运算符。

```tcl
#!/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 中常用运算符的用法。

```tcl
puts [expr +2]

```

在此代码行中，我们使用加号运算符。 它对数量没有影响。 它仅表示该数字为正。 可以将其省略，并且在大多数情况下可以将其省略。

```tcl
puts [expr -2]
puts [expr -(-2)]

```

负号运算符是强制性的。 它说数字是负数。 减号运算符更改数字的符号。 在第二行中，负号运算符将 -2 更改为正 2。

```tcl
puts [expr 2+2]
puts [expr 2-2]
puts [expr 2*2]
puts [expr 2/2]

```

上面的几行显示了常用的算术运算符。

```tcl
puts [expr 2 % 2]

```

% 是取模或余数的运算符。 它找到一个数除以另一个的余数。 表达式`2 % 2`，2 模 2 为 0，因为 2 一次变成 2，余数为 0。因此代码行向控制台输出零。

```tcl
puts [expr 2 ** 2]

```

这是求幂运算符。 代码行将 4 打印到控制台。

```tcl
$ ./exp.tcl
2
-2
2
4
0
4
1
1.0
0
4

```

示例的输出。

## 除法运算符

刚入门的程序员通常会因除法运算而感到困惑。 在许多编程语言中，有两种除法运算：整数和非整数。 这也适用于 Tcl。

```tcl
% expr 3/2
1
% expr 3/2.0
1.5

```

注意整数除法和浮点除法之间的区别。 当至少一个操作数是浮点数时，结果也就是浮点值。 结果更加准确。 如果两个操作数均为整数，则结果也为整数。

## 赋值和增量运算符

Tcl 中没有赋值运算符`=`，也没有增量和减量（`++`和`--`）运算符。 这些运算符在其他计算机语言中很常见。 取而代之的是，Tcl 具有命令。

```tcl
% set a 5
5
% incr a  
6
% incr a
7
% incr a -1
6

```

上面的代码显示了哪些命令用于实现缺少的运算符。

```tcl
% set a 5

```

在 Python 中，我们将执行`a = 5`。 在 Tcl 中，我们使用`set`命令将值设置为变量。

```tcl
% incr a  
6

```

在 C，Java 和许多其他语言中，我们将通过以下方式将变量增加一个：`a++;`。 在 Tcl 中，我们使用`incr`命令。 默认情况下，该值增加 1。

```tcl
% incr a -1
6

```

上面的代码显示了如何将变量减 1，这由`--`减量运算符以基于 C 的语言完成。

## 布尔运算符

在 Tcl 中，我们具有以下逻辑运算符：

| 符号 | 名称 |
| --- | --- |
| `&&` | 逻辑与 |
| <code>&#124;&#124;</code> | 逻辑或 |
| `!` | 否定 |

布尔运算符也称为逻辑运算符。

```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"
}

```

许多表达式导致布尔值。 布尔值用于条件语句中。

```tcl
puts [expr $x == $y]
puts [expr $y > $x]

```

关系运算符始终导致布尔值。 这两行显示 0 和 1。在 Tcl 中，0 为`false`，任何非零值为`true`。

```tcl
if {$y > $x} {

    puts "y is greater than x"
}

```

仅当满足括号内的条件时，才执行`if`命令的主体。 `$y > $x`返回 true，因此消息`"y`大于`x"`被打印到终端。

```tcl
#!/usr/bin/tclsh

puts [expr 0 && 0]
puts [expr 0 && 1]
puts [expr 1 && 0]
puts [expr 1 && 1]

```

此示例显示了逻辑和`&&`运算符。 仅当两个操作数均为`true`时，它的求值结果为`true`。

```tcl
$ ./andoperator.tcl 
0
0
0
1

```

如果两个操作数中的任何一个为`true`，则逻辑或`||`运算符的计算结果为`true`。

```tcl
#!/usr/bin/tclsh

puts [expr 0 || 0]
puts [expr 0 || 1]
puts [expr 1 || 0]
puts [expr 1 || 1]

```

如果运算符的任一侧为真，则操作的结果为真。

```tcl
$ ./oroperator.tcl 
0
1
1
1

```

否定运算符`!`将`true`设为`false`，并将`false`设为`false`。

```tcl
#!/usr/bin/tclsh

puts [expr ! 0]
puts [expr ! 1]
puts [expr ! (4<3)]

```

该示例显示了否定运算符的作用。

```tcl
$ ./not.tcl 
1
0
1

```

`||`和`&&`运算符经过短路求值。 短路求值意味着仅当第一个参数不足以确定表达式的值时才求值第二个参数：当逻辑的第一个参数的值等于`false`时，总值必须为`false`; 当逻辑或的第一个参数为`true`时，总值必须为`true`。 短路求值主要用于提高性能。

一个例子可以使这一点更加清楚。

```tcl
#!/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"
}

```

在示例中，我们有两个过程。 （过程和条件将在后面描述。）它们在布尔表达式中用作操作数。 我们将看看它们是否被调用。

```tcl
if { [One] && [Two] } {

    puts "Pass"
}

```

`One`过程返回`false`。 短路 `&&`不求值第二步。 没有必要。 一旦操作数为假，逻辑结论的结果始终为假。 控制台上仅打印`"Inside one"`。

```tcl
puts "###################"

if { [Two] || [One] } {

    puts "Pass"
}

```

在第二种情况下，我们使用`||`运算符，并使用`Two`过程作为第一个操作数。 在这种情况下，`"Inside two"`和`"Pass"`字符串将打印到终端。 同样，也不必求值第二个操作数，因为一旦第一个操作数计算为`true`，则逻辑或始终为`true`。

```tcl
$ ./shortcircuit.tcl
Short circuit
Inside one
###################
Inside two
Pass

```

`shorcircuit.tcl`脚本的结果。

## 关系运算符

关系运算符用于比较值。 这些运算符总是产生布尔值。 在 Tcl 中，0 代表`false`，1 代表`true`。 关系运算符也称为比较运算符。

| 符号 | 含义 |
| --- | --- |
| `<` | 小于 |
| `<=` | 小于或等于 |
| `>` | 大于 |
| `>=` | 大于或等于 |
| `==` | 等于 |
| `!=` | 不等于 |

该表显示了六个 Tcl 关系表达式。

```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）使用`=`比较数字。

```tcl
$ ./rel.tcl
1
0
1
1

```

该示例打印四个布尔值。

## 按位运算符

小数对人类是自然的。 二进制数是计算机固有的。 二进制，八进制，十进制和十六进制符号仅是相同数字的符号。 按位运算符使用二进制数的位。 像 Tcl 这样的高级语言很少使用按位运算符。

| 符号 | 含义 |
| --- | --- |
| `~` | 按位取反 |
| `^` | 按位异或 |
| `&` | 按位与 |
| <code>&#124;</code> | 按位或 |

按位取反运算符分别将 1 更改为 0，将 0 更改为 1。

```tcl
% puts [expr ~7] 
-8
% puts [expr ~-8]
7

```

运算符将 7 的所有位都还原。这些位之一还确定该数字是否为负。 如果我们再一次对所有位取反，我们将再次得到 7。

按位，运算符在两个数字之间进行逐位比较。 仅当操作数中的两个对应位均为 1 时，位位置的结果才为 1。

```tcl
      00110
   &  00011
   =  00010

```

第一个数字是二进制表示法 6，第二个数字是 3，结果是 2。

```tcl
% puts [expr 6 & 3]
2
% puts [expr 3 & 6]
2

```

按位或运算符在两个数字之间进行逐位比较。 如果操作数中的任何对应位为 1，则位位置的结果为 1。

```tcl
     00110
   | 00011
   = 00111

```

结果为`00110`或十进制 7。

```tcl
% puts [expr 6 | 3]
7
% puts [expr 3 | 6]
7

```

按位互斥或运算符在两个数字之间进行逐位比较。 如果操作数中对应位中的一个或另一个（但不是全部）为 1，则位位置的结果为 1。

```tcl
      00110
   ^  00011
   =  00101

```

结果为`00101`或十进制 5。

```tcl
% puts [expr 6 ^ 3]
5
% puts [expr 3 ^ 6]
5

```

## 扩展运算符

扩展运算符`{*}`使列表中的每个项目成为当前命令的单独参数。 列表是基本的 Tcl 数据结构； 在下一章中将进行介绍。

```tcl
#!/usr/bin/tclsh

set nums {1 2 3 4 5 6}
puts $nums
puts [tcl::mathfunc::max {*}$nums]
puts [tcl::mathfunc::min {*}$nums]

```

扩展运算符与两个数学函数一起使用。

```tcl
set nums {1 2 3 4 5 6}

```

将创建一个名为`nums`的数字列表。 列表是值的有序集合。

```tcl
puts $nums

```

列表的内容被打印到终端。

```tcl
puts [tcl::mathfunc::max {*}$nums]

```

`tcl::mathfunc::max`是标准的数学函数。 它不处理列表。 数字应作为单独的参数传递。 扩展运算符将项目列表转换为单个项目。

```tcl
$ ./expansion.tcl 
1 2 3 4 5 6
6
1

```

示例输出。

## 运算符优先级

运算符优先级告诉我们首先求值哪个运算符。 优先级对于避免表达式中的歧义是必要的。

以下表达式 28 或 40 的结果是什么？

```tcl
 3 + 5 * 5

```

像数学中一样，乘法运算符的优先级高于加法运算符。 结果是 28。

```tcl
(3 + 5) * 5

```

我们使用括号来更改求值顺序。 括号内的表达式始终首先被求值。

下表显示了按优先级排序的常见 Tcl 运算符（最高优先级优先）：

| 类别 | 符号 | 关联性 |
| --- | --- | --- |
| 按位，逻辑非 | `- + ~ !` | 左 |
| 求幂 | `**` | 左 |
| 算术 | `+ - * / %` | 左 |
| 移位 | `<< >>` | 左 |
| 关系 | `== != < > <= >=` | 左 |
| 字符串比较 | `eq ne` | 左 |
| 列表 | `in ni` | 左 |
| 按位 | `& &#124; ^` | 左 |
| 布尔 | `&& &#124;&#124;` | 左 |
| 三元 | `?:` | 右 |

表的同一行上的运算符具有相同的优先级。

```tcl
!/usr/bin/tclsh

puts [expr 3 + 5 * 5]
puts [expr (3 + 5) * 5]

puts [expr ! 1 || 1]
puts [expr ! (1 || 1)]

```

在此代码示例中，我们显示一些常见的表达式。 每个表达式的结果取决于优先级。

```tcl
puts [expr 3 + 5 * 5]

```

该行打印 28。乘法运算符的优先级高于加法。 首先，计算`5*5`的乘积，然后加 3。

```tcl
puts [expr (3 + 5) * 5]

```

圆括号可用于更改优先级。 在上面的表达式中，将 3 加到 5，然后将结果乘以 5。

```tcl
puts [expr ! 1 || 1]

```

在这种情况下，否定运算符具有更高的优先级。 首先，第一个`true`（1）值取反为`false`（0），然后`||`运算符将`false`和`true`组合在一起，最后得到`true`。

```tcl
$ ./precedence.tcl 
28
40
1
0

```

输出。

## 关联性

有时，优先级不能令人满意地确定表达式的结果。 还有另一个规则称为关联性。 运算符的关联性确定优先级与相同的运算符的求值顺序。

```tcl
9 / 3 * 3

```

此表达式的结果是 9 还是 1？ 乘法，删除和模运算符从左到右关联。 因此，该表达式的计算方式为：`(9 / 3) * 3`，结果为 9。

算术，布尔，关系和按位运算符都是从左到右关联的。

三元运算符是正确关联的。

## 三元运算符

三元运算符`?:`是条件运算符。 对于要根据条件表达式选择两个值之一的情况，它是一个方便的运算符。

```tcl
cond-exp ? exp1 : exp2

```

如果`cond-exp`为`true`，则求值`exp1`并返回结果。 如果`cond-exp`为`false`，则求值`exp2`并返回其结果。

```tcl
#!/usr/bin/tclsh

set age 32
set adult [expr $age >= 18 ? true : false]

puts "Adult: $adult"

```

在大多数国家/地区，成年取决于您的年龄。 如果您的年龄超过特定年龄，则您已经成年。 对于三元运算符，这是一种情况。

```tcl
set adult [expr $age >= 18 ? true : false]

```

首先，求值赋值运算符右侧的表达式。 三元运算符的第一阶段是条件表达式求值。 因此，如果年龄大于或等于 18，则返回`?`字符后的值。 如果不是，则返回`:`字符后的值。 然后将返回值分配给成人变量。

```tcl
$ ./ternary.tcl
Adult: true

```

32 岁的成年人是成人。

## 计算素数

我们将计算素数。 本教程的稍后部分将介绍一些功能（列表，循环）。

```tcl
#!/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 到拾取的数字。 实际上，我们不必尝试所有较小的数字，我们可以将数字除以所选数字的平方根。 该公式将起作用。 我们使用余数除法运算符。

```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
}

```

我们从该整数列表中计算素数。

```tcl
if { $num==1 } { continue }

```

根据定义，1 不是质数。 `continue`命令跳到循环的下一个迭代。

```tcl
if { $num==2 || $num==3 } {

    puts -nonewline "$num "
    continue
}

```

我们跳过 2 和 3 的计算。它们是质数，不需要进一步的计算。 请注意等式和条件或运算符的用法。 `==`的优先级高于`||`运算符。 因此，我们不需要使用括号。

```tcl
set i [expr int(sqrt($num))]

```

如果我们仅尝试小于所讨论数字的平方根的数字，那么我们可以。

```tcl
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。这对于我们的计算就足够了。

```tcl
if { $isPrime } {

    puts -nonewline "$num "
}

```

如果余数除法运算符针对任何 i 值返回 0，则说明该数字不是质数。

```tcl
$ ./primes.tcl 
Prime numbers:
2 3 5 7 11 13 17 19 23 29 31 

```

这是`primes.tcl`脚本的输出。

在 Tcl 教程的这一部分中，我们介绍了 Tcl 表达式。