声明:以下内容全部转载自:
7. 过程(procedure)
TCL 支持过程的定义和调用, 在 TCL 中, 过程可以看作是用 TCL 脚本实现的命令, 效果与 TCL 的固有命令相似. 我们可以在任何时候使用proc
命令定义自己的过程, TCL 中的过程类似于 C 中的函数.
7.1 过程定义和返回值
TCL 中过程是由proc
命令产生的. 例如:
% proc add {x y } {expr $x+$y}
proc
命令的第一个参数是你要定义的过程的名字, 第二个参数是过程的参数列表, 参数之间用空格隔开, 第三个参数是一个 TCL 脚本, 代表过程体. proc
生成一个新的命令, 可以象固有命令一样调用:
% add 1 2
3
在定义过程时, 你可以利用return
命令在任何地方返回你想要的值. return
命令迅速中断过程, 并把它的参数作为过程的结果. 例如:
% proc abs {x} {
if {$x >= 0} { return $x }
return [expr -$x]
}
过程的返回值是过程体中最后执行的那条命令的返回值.
7.2 局部变量和全局变量
对于在过程中定义的变量, 因为它们只能在过程中被访问, 并且当过程退出时会被自动删除, 所以称为局部变量; 在所有过程之外定义的变量我们称之为全局变量. 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
.
7.3 缺省参数和可变个数参数
TCL 还提供三种特殊的参数形式:
首先, 你可以定义一个没有参数的过程, 例如:
proc add {} { expr 2+3 }
其次, 可以定义具有缺省参数值的过程, 我们可以为过程的部分或全部参数提供缺省值, 如果调用过程时未提供那些参数的值, 那么过程会自动使用缺省值赋给相应的参数. 和 C\C++中具有缺省参数值的函数一样, 有缺省值的参数只能位于参数列表的后部, 即在第一个具有缺省值的参数后面的所有参数, 都只能是具有缺省值的参数.
例如:
proc add {val1 {val2 2} {val3 3}} {
expr $val1+$val2+$val3
}
则:
add 1 //值为 6
add 2 20 //值为 25
add 4 5 6 //值为 15
另外, TCL 的过程定义还支持可变个数的参数, 如果过程的最后一个参数是args
, 那么就表示这个过程支持可变个数的参数调用. 调用时, 位于args
以前的参数像普通参数一样处理, 但任何附加的参数都需要在过程体中作特殊处理, 过程的局部变量args
将会被设置为一个列表, 其元素就是所有附加的变量. 如果没有附加的变量, args
就设置成一个空串, 下面是一个例子:
proc add { val1 args } {
set sum $val1
foreach i $args {
incr sum $i
}
return $sum
}
则:
add 2 //值为 2
add 2 3 4 5 6 //值为 20
7.4 引用: 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
命令的过程相对于我们希望引用的变量myVar
在调用栈中相对位置. 例如:
upvar 2 other x
这个命令使得当前过程的调用者的调用者中的变量other
, 可以在当前过程中利用x
访问. 缺省情况下, level
的值为1
, 即当前过程(上例中的temp
)的调用者(上例中的myexp
)中的变量(上例中myexp
的a
)可以在当前过程中利用局部变量(上例中temp
的b
)访问.
如果要访问全局变量可以这样写:
upvar #0 other x
那么, 不管当前过程处于调用栈中的什么位置, 都可以在当前过程中利用x
访问全局变量other
.
8. 字符串操作
因为 TCL 把所有的输入都当作字符串看待, 所以 TCL 提供了较强的字符串操作功能, TCL 中与字符串操作有关的命令有: string
, format
, regexp
, regsub
, scan
等.
8.1 format
命令
语法: format formatstring ?vlue value...?
format
命令类似于 ANSIC 中的sprintf
函数和 MFC 中CString
类提供的Format
成员函数. 它按formatstring
提供的格式, 把各个value
的值组合到formatstring
中形成一个新字符串, 并返回. 例如:
%set name john
John
%set age 20
20
%set msg [format "%s is %d years old" $name $age]
john is 20 years old
8.2 scan
命令
语法: scan string format varName ?varName ...?
scan
命令可以认为是format
命令的逆, 其功能类似于 ANSI C 中的scanf
函数. 它按format
提供的格式分析string
字符串, 然后把结果存到变量varName
中,注意除了空格和 TAB 键之外, string
和format
中的字符和%
必须匹配. 例如:
% scan "some 26 34" "some %d %d" a b
2
% set a
26
% set b
34
% scan "12.34.56.78" "%d.%d.%d.%d" c d e f
4
% puts [format "the value of c is %d,d is %d,e is %d ,f is %d" $c $d $e $f]
the value of c is 12,d is 34,e is 56 ,f is 78
scan
命令的返回值是匹配的变量个数. 而且, 我们发现, 如果变量varName
不存在的话, TCL 会自动声明该变量.
8.3 regexp
命令
语法: regexp ?switchs? ?--? exp string ?matchVar?\ ?subMatchVar subMatchVar...?
regexp
命令用于判断正规表达式exp
是否全部或部分匹配字符串string
, 匹配返回1
, 否则0
.
在正规表达式中, 一些字符具有特殊的含义, 下表一一列出, 并给予了解释.
字符 | 意义 |
---|---|
. | 匹配任意单个字符 |
^ | 表示从头进行匹配 |
$ | 表示从末尾进行匹配 |
\x | 匹配字符x, 这可以抑制字符x的含义 |
[chars] | 匹配字符集合chars中给出的任意字符, 如果chars中的第一个字符是^, 表示匹配任意不在chars中的字符, chars的表示方法支持a-z之类的表示 |
(regexp) | 把regexp作为一个单项进行匹配 |
| 对前面的项0进行次或多次匹配 |
+ | 对+前面的项进行1次或多次匹配 |
? | 对?前面的项进行0次或1次匹配 |
regexp1|regexp2 | 匹配regexp1或regexp2中的一项 |
下面的一个例子是从《Tcl and Tk ToolKit》中摘下来的, 下面进行说明:
^((0x)?[0-9a-fA-F]+|[0-9]+)$
这个正规表达式匹配任何十六进制或十进制的整数.
两个正规表达式以|
分开(0x)?[0-9a-fA-F]+
和[0-9]+
, 表示可以匹配其中的任何一个, 事实上前者匹配十六进制, 后者匹配十进制.
^
表示必须从头进行匹配, 从而上述正规表达式不匹配jk12
之类不是以0x
或数字开头的串.
$
表示必须从末尾开始匹配, 从而上述正规表达式不匹配12jk
之类不是数字或a-fA-F
结尾的串.
下面以(0x)?[0-9a-fA-F]+
进行说明, (0x)
表示0x
一起作为一项, ?
表示前一项(0x)
可以出现0次或多次, [0-9a-fA-F]
表示可以是任意0到9之间的单个数字或a到f或A到F之间的单个字母, +
表示象前面那样的单个数字或字母可以重复出现一次或多次.
% regexp {^((0x)?[0-9a-fA-F]+|[0-9]+)$} ab
1
% regexp {^((0x)?[0-9a-fA-F]+|[0-9]+)$} 0xabcd
1
% regexp {^((0x)?[0-9a-fA-F]+|[0-9]+)$} 12345
1
% regexp {^((0x)?[0-9a-fA-F]+|[0-9]+)$} 123j
0
如果regexp
命令后面有参数matchVar
和subMatchVar
, 则所有的参数被当作变量名, 如果变量不存在, 就会被生成. regexp
把匹配整个正规表达式的子字符串赋给第一个变量, 匹配正规表达式的最左边的子表达式的子字符串赋给第二个变量, 依次类推, 例如:
% regexp { ([0-9]+) *([a-z]+)} " there is 100 apples" total num word
1
% puts " $total ,$num,$word"
100 apples ,100,apples
regexp
可以设置一些开关(switchs), 来控制匹配结果:
-
-nocase
: 匹配时不考虑大小写 -
-indices
: 改变各个变量的值, 这使各个变量的值变成了对应的匹配子串在整个字符串中所处位置的索引. 例如:% regexp -indices { ([0-9]+) *([a-z]+)} " there is 100 apples" total num word
1
% puts " $total ,$num,$word"
9 20 ,10 12,15 20
正好子串100 apples
的序号是 9-20, 100
的序号是 10-12, apples
的序号是 15-20
-
-about
: 返回正则表达式本身的信息, 而不是对缓冲区的解析. 返回的是一个 list, 第一个元素是子表达式的个数, 第二个元素开始存放子表达式的信息. -
-expanded
: 启用扩展的规则, 将空格和注释忽略掉, 相当于使用内嵌语法(?x
) -
-line
: 启用行敏感匹配. 正常情况下^
和$
只能匹配缓冲区起始和末尾, 对于缓冲区内部新的行是不能匹配的, 通过这个开关可以使缓冲区内部新的行也可以被匹配. 它相当于同时使用-linestop
和-lineanchor
开关, 或者使用内嵌语法(?n
) -
-linestop
: 启动行结束敏感开关. 使^
可以匹配缓冲区内部的新行. 相当于内嵌语法(?p
) -
-lineanchor
: 改变^
和$
的匹配行为, 使可以匹配缓冲区内部的新行. 相当于内嵌语法(?w
) -
-all
: 进最大可能的匹配 -
-inline
: Causes the command to return, as a list, the data that would otherwise be placed in match variables. When using -inline, match variables may not be specified. If used with -all, the list will be concatenated at each iteration, such that a flat list is always returned. For each match iteration, the command will append the overall match data, plus one element for each subexpression in the regular expression. Examples are:regexp -inline — {\w(\w)} " inlined "
=> {in n}
regexp -all -inline — {\w(\w)} " inlined "
=> {in n li i ne e} -
-start index
: 强制从偏移为index
开始的位置进行匹配. 使用这个开关之后,^
将不能匹配行起始位置,\A
将匹配字符串的index
偏移位置. 如果使用了-indices
开关, 则indices
表示绝对位置,index
表示输入字符的相对位置. -
--
: 表示这后面再没有开关(switchs)了, 即使后面有以-
开头的参数也被当作正规表达式的一部分.
8.4 regsub
命令
语法: regsub ?switchs? exp string subSpec varname
regsub
的第一个参数是一个整个表达式, 第二个参数是一个输入字符串, 这一点和regexp
命令完全一样, 也是当匹配时返回 1, 否则返回 0. 不过regsub
用第三个参数的值来替换字符串string
中和正规表达式匹配的部分, 第四个参数被认为是一个变量, 替换后的字符串存入这个变量中. 例如:
% regsub there "They live there lives " their x
1
% puts $x
They live their lives
这里there
被用their
替换了.
regsub
命令也有几个开关(switchs):
-
-nocase
: 意义同regexp
命令中. -
-all
: 没有这个开关时,regsub
只替换第一个匹配, 有了这个开关,regsub
将把所有匹配的地方全部替换. -
--
: 意义同regexp
命令中.
8.5 string
命令
string
命令的语法: string option arg ?arg...?
string
命令具有强大的操作字符串的功能, 其中的option
选项多达 20 个. 下面介绍其中常用的部分.
8.5.1 string compare ?-nocase? ?-length int? string1 string2
把字符串string1
和string2
进行比较, 返回值为-1, 0 或 1, 分别对应string1
小于, 等于或大于string2
. 如果有-length
参数, 那么只比较前int
个字符, 如果int
为负数, 那么这个参数被忽略. 如果有-nocase
参数, 那么比较时不区分大小写.
8.5.2 string equal ?-nocase? ?-length int? string1 string2
把字符串string1
和string2
进行比较, 如果两者相同, 返回值为 1, 否则返回 0. 其他参数与 8.5.1 同.
8.5.3 string first string1 string2 ?startindex?
在string2
中从头查找与string1
匹配的字符序列, 如果找到, 那么就返回匹配的第一个字母所在的位置(0-based). 如果没有找到, 那么返回-1. 如果给出了startindex
变量, 那么将从startindex
处开始查找. 例如:
% string first ab defabc
3
% string first ab defabc 4
-1
8.5.4 string index string charIndex
返回string
中第charIndex
个字符(0-based). charIndex
可以是下面的值:
-
整数 n
: 字符串中第 n 个字符(0-based) -
end
: 最后一个字符 -
end-整数 n
: 倒数第 n 个字符.string index "abcd" end-1
返回字符c
如果charIndex
小于 0, 或者大于字符串string
的长度, 那么返回空.
例如:
% string index abcdef 2 c
% string index abcdef end-2 d
8.5.5 string last string1 string2 ?startindex?
参照 8.5.3, 唯一的区别是从后往前查找
8.5.6 string length string
返回字符串string
的长度.
8.5.7 string match ?-nocase? pattern string
如果pattern
匹配string
, 那么返回 1, 否则返回 0. 如果有-nocase
参数, 那么就不区分大小写.
在pattern
中可以使用通配符:
-
*
: 匹配 string 中的任意长的任意字符串, 包括空字符串. -
?
: 匹配 string 中任意单个字符 -
[chars]
: 匹配字符集合 chars 中给出的任意字符,其中可以使用 A-Z 这种形式 -
\x
: 匹配单个字符 x, 使用\
是为了让 x 可以为字符*,-,[,].
例子:
% string match abcdef
1
% string match a abcdef
1
string match a?cdef abcdef
1
% string match {a[b-f]cdef} abcdef //注意一定要用'{',否则 TCL 解释器会把 b-f 当作命令名
1 //从而导致错误
% string match {a[b-f]cdef} accdef
1
8.5.8 string range string first last
返回字符串string
中从第first
个到第last
个字符的子字符串(0-based). 如果first
<0, 那么first
被看作 0, 如果last
大于或等于字符串的长度, 那么last
被看作end
, 如果first
比last
大, 那么返回空.
8.5.9 string repeat string count
返回值为: 重复了string
字符串count
次的字符串. 例如:
% string repeat "abc" 2
abcabc
8.5.10 string replace string first last ?newstring?
返回值为: 从字符串string
中删除了第first
到第last
个字符(0-based)的字符串, 如果给出了newstring
变量, 那么就用newstring
替换从第first
到第last
个字符. 如果first
<0, 那么first
被看作 0, 如果last
大于或等于字符串的长度, 那么last
被看作end
, 如果first
比last
大或者大于字符串string
的长度或者last
小于 0, 那么原封不动的返回string
.
8.5.11 string tolower string ?first? ?last?
返回值为: 把字符串string
转换成小写后的字符串, 如果给出了first
和last
变量, 就只转换first
和last
之间的字符.
8.5.12 string toupper string ?first? ?last?
同 8.5.11. 转换成大写.
8.5.13 string trim string ?chars?
返回值为: 从string
字符串的首尾删除掉了字符集合chars
中的字符后的字符串. 如果没有给出chars
, 那么将删除掉 spaces, tabs, newlines, carriage returns 这些字符. 例如:
% string trim "abcde" {a d e}
bc
% string trim " def
> "
def
8.5.14 string trimleft string ?chars?
同 8.5.13. 不过只删除左边的字符.
8.5.15 string trimright string ?chars?
同 8.5.13. 不过只删除右边的字符.