原文: https://zetcode.com/lang/tcl/procedures/

在本教程的这一部分中,我们将介绍 Tcl 过程。

过程是包含一系列命令的代码块。 在许多编程语言中,过程都称为函数。 对于程序仅执行一个特定任务,这是一种良好的编程习惯。 程序为程序带来了模块化。 正确使用程序会带来以下优点:

  • 减少代码重复
  • 将复杂的问题分解成更简单的部分
  • 提高代码的清晰度
  • 重用代码
  • 信息隐藏

过程有两种基本类型:内置过程和用户定义的过程。 内置过程是 Tcl 核心语言的一部分。 例如,rand()sin()exp()是内置程序。 用户定义的过程是使用proc关键字创建的过程。

proc关键字用于创建新的 Tcl 命令。 术语过程和命令通常可以互换使用。

我们从一个简单的例子开始。

  1. #!/usr/bin/tclsh
  2. proc tclver {} {
  3. set v [info tclversion]
  4. puts "This is Tcl version $v"
  5. }
  6. tclver

在此脚本中,我们创建一个简单的tclver过程。 该过程将打印 Tcl 语言的版本。

  1. proc tclver {} {

使用proc命令创建新过程。 {}字符表明该过程没有参数。

  1. {
  2. set v [info tclversion]
  3. puts "This is Tcl version $v"
  4. }

这是tclver过程的主体。 它在我们执行tclver命令时执行。 该命令的正文位于大括号之间。

  1. tclver

通过指定其名称来调用该过程。

  1. $ ./version.tcl
  2. This is Tcl version 8.6

样本输出。

过程参数

参数是传递给过程的值。 过程可以采用一个或多个参数。 如果过程使用数据,则必须将数据传递给过程。

在下面的示例中,我们有一个采用一个参数的过程。

  1. #!/usr/bin/tclsh
  2. proc ftc {f} {
  3. return [expr $f * 9 / 5 + 32]
  4. }
  5. puts [ftc 100]
  6. puts [ftc 0]
  7. puts [ftc 30]

我们创建一个ftc程序,将华氏温度转换为摄氏温度。

  1. proc ftc {f} {

该过程采用一个参数。 该程序的主体将使用其名称f

  1. return [expr $f * 9 / 5 + 32]

我们计算摄氏温度值。 return命令将值返回给调用方。 如果该过程未执行显式返回,则其返回值是在过程主体中执行的最后一条命令的值。

  1. puts [ftc 100]

执行ftc过程。 它以 100 为参数。 这是华氏温度。 返回的值由puts命令使用,该命令将其打印到控制台。

  1. $ ./fahrenheit.tcl
  2. 212
  3. 32
  4. 86

示例的输出。

接下来,我们将有一个接受两个参数的过程。

  1. #!/usr/bin/tclsh
  2. proc maximum {x y} {
  3. if {$x > $y} {
  4. return $x
  5. } else {
  6. return $y
  7. }
  8. }
  9. set a 23
  10. set b 32
  11. set val [maximum $a $b]
  12. puts "The max of $a, $b is $val"

maximum过程将两个值的最大值相减。

  1. proc maximum {x y} {

该方法有两个参数。

  1. if {$x > $y} {
  2. return $x
  3. } else {
  4. return $y
  5. }

在这里,我们计算哪个更大。

  1. set a 23
  2. set b 32

我们定义了两个要比较的变量。

  1. set val [maximum $a $b]

我们计算两个变量的最大值。

  1. $ ./maximum.tcl
  2. The max of 23, 32 is 32

这是maximum.tcl脚本的输出。

可变数量的参数

一个过程可以接受和处理可变数量的参数。 为此,我们使用特殊的args参数。

  1. #!/usr/bin/tclsh
  2. proc sum {args} {
  3. set s 0
  4. foreach arg $args {
  5. incr s $arg
  6. }
  7. return $s
  8. }
  9. puts [sum 1 2 3 4]
  10. puts [sum 1 2]
  11. puts [sum 4]

我们定义一个sum过程,将其所有参数加起来。

  1. proc sum {args} {

sum过程具有一个特殊的args参数。 它具有传递给该过程的所有值的列表。

  1. foreach arg $args {
  2. incr s $arg
  3. }

我们遍历列表并计算总和。

  1. puts [sum 1 2 3 4]
  2. puts [sum 1 2]
  3. puts [sum 4]

我们调用sum过程 3 次。 在第一种情况下,它采用 4 个参数,在第二种情况下为 2,在最后一种情况下为 4。

  1. $ ./variable.tcl
  2. 10
  3. 3
  4. 4

variable.tcl脚本的输出。

隐式参数

Tcl 过程中的参数可能具有隐式值。 如果未提供显式值,则使用隐式值。

  1. #!/usr/bin/tclsh
  2. proc power {a {b 2}} {
  3. if {$b == 2} {
  4. return [expr $a * $a]
  5. }
  6. set value 1
  7. for {set i 0} {$i<$b} {incr i} {
  8. set value [expr $value * $a]
  9. }
  10. return $value
  11. }
  12. set v1 [power 5]
  13. set v2 [power 5 4]
  14. puts "5^2 is $v1"
  15. puts "5^4 is $v2"

在这里,我们创建一个power过程。 该过程具有一个带有隐式值的参数。 我们可以使用一个和两个参数来调用该过程。

  1. proc power {a {b 2}} {

第二个参数b具有隐式值 2。如果仅提供一个参数,则power过程会将a的值返回到幂 2。

  1. set v1 [power 5]
  2. set v2 [power 5 4]

我们用一个和两个参数调用幂过程。 第一行计算 5 的幂 2。第二行计算 5 的幂 4。

  1. $ ./implicit.tcl
  2. 5^2 is 25
  3. 5^4 is 625

示例的输出。

返回多个值

return命令将一个值传递给调用方。 通常需要返回多个值。 在这种情况下,我们可以返回一个列表。

  1. #!/usr/bin/tclsh
  2. proc tworandoms {} {
  3. set r1 [expr round(rand()*10)]
  4. set r2 [expr round(rand()*10)]
  5. return [list $r1 $r2]
  6. }
  7. puts [tworandoms]
  8. puts [tworandoms]
  9. puts [tworandoms]
  10. puts [tworandoms]

我们有一个tworandoms程序。 它返回 1 到 10 之间的两个随机整数。

  1. set r1 [expr round(rand()*10)]

计算一个随机整数并将其设置为r1变量。

  1. return [list $r1 $r2]

借助list命令返回两个值。

  1. $ ./tworandoms.tcl
  2. 3 7
  3. 1 3
  4. 8 7
  5. 9 9

样本输出。

递归

在数学和计算机科学中,递归是一种定义函数的方法,其中所定义的函数在其自己的定义内应用。 换句话说,递归函数调用自身以完成其工作。 递归是解决许多编程任务的一种广泛使用的方法。 递归是诸如 Scheme,OCalm 或 Clojure 之类的函数式语言的基本方法。

递归调用在 Tcl 中有一个限制。 递归调用不能超过 1000 个。

递归的典型示例是阶乘的计算。 阶乘n!是所有小于或等于 n 的正整数的乘积。

  1. #!/usr/bin/tclsh
  2. proc factorial n {
  3. if {$n==0} {
  4. return 1
  5. } else {
  6. return [expr $n * [factorial [expr $n - 1]]]
  7. }
  8. }
  9. # Stack limit between 800 and 1000 levels
  10. puts [factorial 4]
  11. puts [factorial 10]
  12. puts [factorial 18]

在此代码示例中,我们计算三个数字的阶乘。

  1. return [expr $n * [factorial [expr $n - 1]]]

factorial过程的主体内部,我们使用修改后的参数调用factorial过程。 该过程将自行调用。

  1. $ ./recursion.tcl
  2. 24
  3. 3628800
  4. 6402373705728000

这些就是结果。 如果我们尝试计算 100 的阶乘,则会收到“嵌套求值过多”的错误。

作用域

在过程内部声明的变量具有过程作用域。 名称的作用域是程序文本的区域,在该区域内可以引用名称声明的实体而无需对该名称进行限定。 在过程内部声明的变量具有过程作用域; 它也称为本地作用域。 该变量仅在此特定过程中有效。

  1. #!/usr/bin/tclsh
  2. proc test {} {
  3. puts "inside procedure"
  4. #puts "x is $x"
  5. set x 4
  6. puts "x is $x"
  7. }
  8. set x 1
  9. puts "outside procedure"
  10. puts "x is $x"
  11. test
  12. puts "outside procedure"
  13. puts "x is $x"

在前面的示例中,我们在测试过程的内部和外部定义了一个x变量。

  1. set x 4
  2. puts "x is $x"

在测试过程中,我们定义了x变量。 该变量具有局部作用域,仅在此过程内有效。

  1. set x 1
  2. puts "outside procedure"
  3. puts "x is $x"

我们在过程外部定义x变量。 它具有全球作用域。 变量没有冲突,因为它们具有不同的作用域。

  1. $ ./scope.tcl
  2. outside procedure
  3. x is 1
  4. inside procedure
  5. x is 4
  6. outside procedure
  7. x is 1

示例的输出。

可以在过程内部更改全局变量。

  1. #!/usr/bin/tclsh
  2. proc test {} {
  3. upvar x y
  4. puts "inside procedure"
  5. puts "y is $y"
  6. set y 4
  7. puts "y is $y"
  8. }
  9. set x 1
  10. puts "outside procedure"
  11. puts "x is $x"
  12. test
  13. puts "outside procedure"
  14. puts "x is $x"

我们定义一个全局x变量。 我们在测试过程中更改变量。

  1. upvar x y

我们使用upvar命令通过名称y引用全局x变量。

  1. set y 4

我们为本地y变量分配一个值,并更改全局x变量的值。

  1. $ ./scope2.tcl
  2. outside procedure
  3. x is 1
  4. inside procedure
  5. y is 1
  6. y is 4
  7. outside procedure
  8. x is 4

从输出中我们可以看到测试过程更改了x变量。

使用global命令,我们可以从过程中引用全局变量。

  1. #!/usr/bin/tclsh
  2. proc test {} {
  3. global x
  4. puts "inside test procedure x is $x"
  5. proc nested {} {
  6. global x
  7. puts "inside nested x is $x"
  8. }
  9. }
  10. set x 1
  11. test
  12. nested
  13. puts "outside x is $x"

在上面的示例中,我们有一个测试过程以及在该测试过程中定义的嵌套过程。 我们从这两个过程中都引用了全局x变量。

  1. global x
  2. puts "inside test procedure x is $x"

使用global命令,可以引用在测试过程之外定义的全局x变量。

  1. proc nested {} {
  2. global x
  3. puts "inside nested x is $x"
  4. }

可以创建嵌套过程。 这些是在其他过程中定义的过程。 我们使用global命令引用全局x变量。

  1. test
  2. nested

我们称之为测试过程及其嵌套过程。

  1. $ ./scope3.tcl
  2. inside test procedure x is 1
  3. inside nested x is 1
  4. outside x is 1

示例的输出。

在 Tcl 教程的这一部分中,我们介绍了 Tcl 过程。