以下内容是我在学习和研究Tcl语言时,对Tcl语言的特性、重点和注意事项的提取、精练和总结,可以做为Tcl语言特性的字典;
目录
- 1. 语法描述
- 2. 命令执行机制
- 3. 置换
- 4. 命令置换
- 5. 双引号和花括号
- 6. 参数扩展
- 7. 注释
- 8. 过程proc
- 9. if语句
- 10. 数组
- 11. list和string
- 12. 局部变量和全局变量
- 13. 引用:upvar
内容
1. 语法描述
在Tcl的语法描述中,有如下规则:
- 两个
?
之间的参数是可选的; -
...
表示重复、可以有无限多个;
2. 命令执行机制
一个 TCL 脚本可以包含一个或多个命令。命令之间必须用换行符或分号隔开,下面的两个脚本
都是合法的:
set a 1
set b 2
或
set a 1;set b 2
TCL 的每一个命令包含一个或几个单词,第一个单词代表命令名,另外的单词则是这个命令的 参数,单词之间必须用空格或 TAB 键隔开。
TCL 解释器对一个命令的求值过程分为两部分:分析和执行。在分析阶段,TCL 解释器运用规 则把命令分成一个个独立的单词,同时进行必要的置换(substitution); 在执行阶段,TCL 解 释器会把第一个单词当作命令名,并查看这个命令是否有定义,如果有定义就激活这个命令对应 的 C/C++过程,并把所有的单词作为参数传递给该命令过程,让命令过程进行处理。
3. 置换
注:在下面的所有章节的例子中,'%'为 TCL 的命令提示符,输入命令回车后,TCL 会在接着的一行输出命令执行结果。'//'后面是我自己加上的说明,不是例子的一部分。 TCL 解释器在分析命令时,把所有的命令参数都当作字符串看待,例如:
%set x 10 //定义变量 x,并把 x 的值赋为 10
10
%set y x+100 //y 的值是 x+100,而不是我们期望的 110 x+100
上例的第二个命令中,x 被看作字符串 x+100 的一部分,如果我们想使用 x 的值'10' ,就必 须告诉 TCL 解释器:我们在这里期望的是变量 x 的值,而非字符'x'。怎么告诉 TCL 解释器呢, 这就要用到 TCL 语言中提供的置换功能。
TCL 提供三种形式的置换:变量置换、命令置换和反斜杠置换。每种置换都会导致一个或多个 单词本身被其他的值所代替。置换可以发生在包括命令名在内的每一个单词中,而且置换可以嵌 套。
4. 命令置换
命令置换是由[]括起来的 TCL 命令及其参数,命令置换会导致某一个命令的所有或部分单词被另一个命令的结果所代替。例如:
%set y [expr $x+100]
y 的值是 110,这里当 TCL 解释器遇到字符'['时,它就会把随后的 expr 作为一个命令名,从 而激活与expr对应的C/C++过程,并把'expr'和变量置换后得到的'10+110'传递给该命令过 程进行处理。
如果在上例中我们去掉[],那么 TCL 会报错。因为在正常情况下,TCL 解释器只把命令行中的 第一个单词作为看作命令,其他的单词都作为普通字符串处理,看作是命令的参数。
注意,[]中必须是一个合法的 TCL 脚本,长度不限。[]中脚本的值为最后一个命令的返回值, 例如:
%set y [expr $x+100;set b 300] //y 的值为 300,因为 set b 300 的返回值为 300 300
有了命令置换,实际上就表示命令之间是可以嵌套的,即一个命令的结果可以作为别的命令的参数。
5. 双引号和花括号
TCL 解释器对双引号中的各种分隔符将不作处理,但是对换行符 及$和[]两种置换符会照常处理。
而在花括号中,所有特殊字符都将成为普通字符,失去其特殊意义,TCL 解释器不会对其作特 殊处理。
6. 参数扩展
{*}
是 Tcl8.5 新增的语法,称为扩展运算符。它在执行语句之前将 字符串 或 列表 list 扩展为单个元素,而不是将 字符串 或 list 作为一个元素传给命令;
如果字符串 或 列表 以 {*}
开头,后跟非空格字符,则会移除开头的 {*}
,并将其余字符串根据空格分隔为多个元素(不执行命令或变量转换;对列表进行常规的反斜杠替换,并且各个内部单词可以用大括号或双引号字符包围),然后分隔后的多个元素作为多个参数来替换原来的字符串。例如,cmd a {*}{b [c]} d {*}{$e f "g h"}
等同于 cmd a b {[c]} d {$e} f "g h"
。
详情请参考 http://tcl.tk/man/tcl8.5/TclCmd/Tcl.htm#M9
7. 注释
TCL 中的注释符是'#','#'和直到所在行结尾的所有字符都被 TCL 看作注释,TCL 解释器对注 释将不作任何处理。不过,要注意的是,'#'必须出现在 TCL 解释器期望命令的第一个字符出现 的地方,才被当作注释。
例如:
%#这是一个注释
%set a 100 # 这不是注释
wrong # args: should be "set varName ?newValue?"
%set b 101 ; # 这是一个注释
101
第二行中'#'就不被当作注释符,因为它出现在命令的中间,TCL 解释器把它和后面的字符当作 命令的参数处理,从而导致错误。而第四行的'#'就被作为注释,因为前一个命令已经用一个分 号结束,TCL 解释器期望下一个命令接着出现。现在在这个位置出现'#',随后的字符就被当作 注释了。
8. 过程proc
Tcl的过程相当于其它语言的函数,是由 proc 命令定义的,proc 的语法为:
proc name args body
示例:
proc add {x y} {expr $x+$y}
或
proc add {x y} {
expr $x+$y
}
proc 命令的第一个参数是你要定义的过程的名字,第二个参数是过程的参数列表,参数之间用 空格隔开,第三个参数是一个 TCL 脚本,代表过程体。 proc 生成一个新的命令,可以象固有 命令一样调用:
add 1 2
虽然Tcl的过程相当于其它语言的函数,但是Tcl过程的参数和过程的脚本代码的定义都是用 花括号 {}
包裹着,很不直观,这样做的原因是:Tcl 并没有为过程定义新的语法,Tcl的过程的定义是由一个叫 proc 的命令来定义,既然 proc 是一个命令,那么,过程的 参数列表 和 脚本代码 都是要作为参数传给 proc 命令的,为了将参数 和 脚本代码 分别作为一个参数传 proc 命令,只有用 花括号 {}
包裹着;类似的还有控制语句,如:if、switch、while、for 等;
在定义过程时,你可以利用 return 命令在任何地方返回你想要的值。 return 命令迅速中断过 程,并把它的参数作为过程的结果。例如:
% proc abs {x} {
if {$x >= 0} { return $x } return [expr -$x]
}
过程的返回值是过程体中最后执行的那条命令的返回值。
9. if语句
TCL中有很多命令都以表达式作为参数。最典型的是 expr 命令 ,另外 if、while、for 等循环控制命令的循环控制中也都使用表达式作为参数。
语法:
if test1 body1 ?elseif test2 body2 elseif.... ? ?else bodyn?
TCL 先把 test1 当作一个表达式求值,如果值非 0,则把 body1 当作一个脚本执行并返回所得 值,否则把 test2 当作一个表达式求值,如果值非 0,则把 body2 当作一个脚本执行并返回所 得值......。例如:
if { $x>0 } {
.....
}elseif{ $x==1 } { .....
}elseif { $x==2 } { ....
}else{
.....
}
注意:
上例中代码块的'{'一定要写在上一行,因为如果不这样,TCL 解释器会认为 if 命令在换行符处已 结束,下一行会被当成新的命令,从而导致错误的结果。在下面的循环命令的书写中也要注意这 个问题。书写中还要注意的一个问题是 if 和{之间应该有一个空格,否则 TCL 解释器会把'if{' 作为一个整体当作一个命令名,从而导致错误。
10. 数组
数组是一些元素的集合。TCL 的数组和普通计算机语言中的数组有很大的区别。在 TCL 中,不 能单独声明一个数组,数组只能和数组元素一起声明。数组中,数组元素的名字包含两部分:数 组名和数组中元素的名字,TCL 中数组元素的名字(下标〕可以为任何字符串。例如:
set day(monday) 1 set day(tuesday) 2
第一个命令生成一个名为 day 的数组,同时在数组中生成一个名为 monday 的数组元素,并把 值置为 1,第二个命令生成一个名为 tuesday 的数组元素,并把值置为 2。
简单变量的置换已经在前一节讨论过,这里讲一下数组元素的置换。除了有括号之外,数组元素 的置换和简单变量类似。例:
set a monday
set day(monday) 1
set b $day(monday) //b 的值为 1,即 day(monday)的值。 set c $day($a) //c 的值为 1,即 day(monday)的值。
TCL 不能支持复杂的数据类型,这是一个很大的缺憾,也是 TCL 受指责很多的方面。但是 TCL 的一个扩展 ITCL 填补了这个缺憾
11. list和string
Tcl中的list和string经常没有很严格的区分。一个变量是list还是string完全取决于使用的场合。
concat: 用空格分隔字符串
concat命令很容易被误解为是把几个list合并成为一个list,看下面的例子就知道实际情况并非如此。
tcl>concat "a { " b " c } d"
a { b c } d
tcl>llength [concat "a { " b " c } d"]
3
tcl>lindex [concat "a { " b " c } d"] 1
b c
第一个concat命令的结果中的多余空白表明concat实际上是把各个参数所代表的字符串用空格分隔组成一个新的字符串;而不是把各个参数所代表的list合并成一个新的list。否则接下来的llength和lindex命令的结果应该是另一种样子。
join: 把list的元素拼成一个字符串
Tcl中的join命令也是用于把list中的元素用指定的分隔符(默认是空格)组成一个字符串。
但在使用默认分隔符(空格)的情况下,会得到一些有趣的结果。
tcl>set list [list a b {c d} e]
tcl>llength $list
4
tcl>join $list :
a:b:c d:e
tcl>join $list
a b c d e
tcl>llength [join $list]
5
用:
拼接字符串时,没什么特别之处。但用默认的空格拼接字符时,拼接后的结果作为list对待时,与之前相比,变得扁平了一层。
12. 局部变量和全局变量
对于在过程中定义的变量,因为它们只能在过程中被访问,并且当过程退出时会被自动删除,所 以称为局部变量;
在所有过程之外定义的变量我们称之为全局变量。
TCL 中,局部变量和全局 变量可以同名,两者的作用域是不相交的:局部变量的作用域是它所在的过程的内部;全局变量 的作用域则不包括所有过程的内部。这一点和 C 语言有很大的不同.
如果我们想在过程内部引用一个全局变量的值,可以使用 global 命令。例如:
% set a 4
4
% proc sample { x } { global a
incr a
return [expr $a+$x]
}
% sample 3
8
%set a
5
全局变量 a 在过程中被访问。在过程中对 a 的改变会直接反映到全局上。如果去掉语句 global a, TCL 会出错,因为它不认识变量 a。
13. 引用:upvar
命令语法:
upvar ?level? otherVar myVar ?otherVar myVar ...?
upvar 命令使得用户可以在过程中对全局变量或其他过程中的局部变量进行访问。 upvar 命令 的第一个参数 otherVar 是我们希望以引用方式访问的参数的名字,第二个参数 myVar 是这个 过程中的局部变量的名字,一旦使用了 upvar 命令把 otherVar 和 myVar 绑定,那么在过程 中对局部变量 myVar 的读写就相当于对这个过程的调用者中 otherVar 所代表的局部变量的 读写。下面是一个例子:
% proc temp { arg } { upvar $arg b
set b [expr $b+2]
}
% proc myexp { var } { set a 4
temp a
return [expr $var+$a]
}
则:
% myexp 7
13
这个例子中,upvar 把$arg(实际上是过程 myexp 中的变量 a)和过程 temp 中的变量 b 绑定, 对 b 的读写就相当于对 a 的读写。
upvar 命令语法中的 level 参数表示:调用 upvar 命令的过程相对于我们希望引用的变量 otherVar 在调用栈中相对位置。例如:
upvar 2 other x
这个命令使得当前过程的调用者的调用者中的变量 other,可以在当前过程中利用 x 访问。缺省 情况下,level 的值为 1,即当前过程(上例中的 temp)的调用者(上例中的 myexp)中的变量(上 例中 myexp 的 a)可以在当前过程中利用局部变量(上例中 temp 的 b)访问。
如果要访问全局变量可以这样写:
upvar #0 other x
那么,不管当前过程处于调用栈中的什么位置,都可以在当前过程中利用 x 访问全局变量 other。
网友评论