TCL 语言非常简单,下面我们通过一个简单的例子,让我们对TCL 有一个大概的印象和了解。如果你会使用C Shell
脚本或者有过C/C++的经验,那么TCL 对你而言,应该非常容易。先看看下面的程序complex.TCL:
#File complex.TCL
#
package require Itcl
package require control
itcl::class Complex {
public variable
m_r
;#实数
public variable
m_i
;#虚数
constructor {r i} {
set m_r $r
set m_i $i
}
public method + { c }
{
;#复数的加法
set r [expr "$m_r + [$c cget -m_r]"]
set i [expr "$m_i + [$c cget -m_i]"]
return [code [Complex #auto $r $i]]
}
public method GetReal { } { ;#复数的减法
return $m_r
}
public method GetImag { } {
return $m_i
}
public method - { c }
}
itcl::body Complex::- {c} {
set r [expr "$m_r - [$c cget -m_r]"]
set i [expr "$m_i - [$c cget -m_i]"]
return [itcl::code [Complex #auto $r $i]]
}
pro main {}
{
;#定义了过程main
set r 100;set i 200
Complex a $r $i
Complex b 50 50
set c [a - b]
control::control assert enabled 1
puts “c.real = [$c GetReal] ; c.imag = [$c GetImag]”
control::assert "[$c GetReal]==50"
control::assert "[$c GetImag]==150"
}
main
;#调用过程main
上面的代码实现了一个简单的复数类,并且实现了几个成员函数,完成加法和减法操作。最后的代码创建两个对象,并且相减得到另外一个对象。然后使用assert
来断定我们的操作是正确的。
启动解释器
我们可以在Dos 提示符中输入命令tclsh 来启动TCL 解释器,并且进入交互式模式,然后使用source
命令来执行我们刚才创建的脚本,如下:
C:\>tclsh
% cd e:/work/script
% source complex.TCL
c.real = 50 ; c.imag = 150
%
交互式模式下,我们每输入一次命令,TCL
解释器就执行这命令,然后把命令的执行结果给打印出来。如果出现了语法错误或者异常,就把异常信息给打印出来。
我们也可以在DOS 提示符下输入tclsh e:/work/script 直接执行我们的脚本,如下:
C:\>tclsh
e:/work/script/complex.tcl
c.real = 50 ; c.imag = 150
C:\>
ActiveTCL
软件包中还有一个程序tkCon,可以在启动菜单中找到。这是一个使用TCL/Tk 开发的图形界面的脚本解释器,使用起来更加方便。
变量和表达式
TCL
中也存在变量的概念,我们可以创建变量,赋值,引用,删除变量,比如上面的代码中:
% set r 100;set i 200 ;#创建了两个变量r 和i,并且初始化为100
和200
200
% Complex m $r
$i
;#引用变量r 和i,创建了一个Complex 对象
m
%
TCL
中的变量没有类型,或者我们换一种说法,所有的变量都是同一种类型:“字符串”类型。比如上面例子种的变量r 和i,他们的值分别是100
和200,这里100 和200 都是字符串,没有整数这么一说。可能我们会迷惑了,如果我要计算这两个数的和,怎么办?
% set sum
$r+$i
;#企图计算r 和i 两个变量的和,但是事与愿违;
100+200
% set sum [expr $r+$i] ;#只有这样才行
300
%
看看上面的例子就明白了,第一种方法计算sum,结果sum 的值是字符串“100+200”。只有通过第二种方式,使用expr
命令来计算表达式$r+$i,sum 的值才是我们期望的300。在TCL
中,所有的数学计算都是通过expr 命令来实现的。例如通过计算正弦来得到2 的平方根:
% set PI
3.1415926535897932
;#创建变量PI
3.1415926535897932
% expr
"cos($PI/4)*2"
;#计算得到sqrt(2)
1.41421356237
% expr
"sqrt(2)"
;#直接计算得到 sqrt(2)
1.41421356237
TCL 中表达式的写法和C 语言是比较类似的,并且支持常用的数学函数。
定义函数
Pascal、VB
语言中存在函数和过程的区别,并且函数和过程的定义方式不一样。在TCL语言中,函数和过程的定义方式没有差别,就像C/C++一样,所以后面的章节中针对TCL而言,“函数”和“过程”两种提法是等价的。TCL
中的过程分成两类:
1. TCL 语言自带的核心命令。
2. 用户编写的扩展命令。
TCL
的核心命令只有80 多条,比如set 命令;我们还可以使用TCL
脚本定义自己的过程,也可以使用C/C++语言来实现一些和操作系统等紧密相关的过程。前面的例子代码中,我们就定义了一个过程main;下面是另外一个过程定义:
#过程Factorial,计算参数n 的阶乘。
proc Factorial {n} {
if {$n<=1} {
return 1
}
return [expr $n*[Factorial [expr $n-1]]]
}
puts "10! = [Factorial 10]" ;#调用过程Factorial,计算10!
puts "5! = [Factorial 5]" ;#调用过程Factorial,计算5
!
可见,TCL
中的过程是可以递归调用的。
循环和控制
TCL 的核心命令中提供了常见的控制结构。而且TCL
和其他语言相比很大的不同是:你甚至可以编写你自己的控制结构!这是后话。TCL 中循环结构包括for 循环和while
循环,
例如:
#使用for 循环实现阶乘
proc Factorial1 {n} {
set result 1
for {set i 1} {$i<=$n} {incr i} {
set result [expr $result*$i]
}
return $result
}
#使用while 循环来实现阶乘
proc Factorial2 {n} {
set result 1
set i 1
while {$i<=$n} {
set result [expr $result*$i]
incr i
}
return $result
}
puts "10! = [Factorial1 10]"
puts "5! = [Factorial2 5]"
还有一个很有用的foreach 循环,功能和VB 中的foreach
类似,但是要强大和灵活的多。后面我们详细讨论;
可以使用if 来进行判断分支结构,使用switch
实现多路匹配选择。例如前面采用递归方式定义的Factorial 函数中,就使用了if 结构。switch
可以指定字符串匹配模式来进行匹配选择,例如:
set c "http://www.microsoft.com"
#根据正则表达式匹配方式来进行switch 选择
switch -regexp $c {
"http://.+" {puts "$c is a http
url"}
.+@.+
{puts "$c is a email address"}
ftp://.+
{puts "$c is a ftp url"}
default
{puts "Other ..."}
}
和C/C++类似,TCL 中也有break 和continue
语句。主要用来在循环中控制循环:break跳出最里层的循环,continue
掠过本次循环中下面没有执行到的语句,继续下一次循环。要注意的是,switch
中各个子句中没有必要象C/C++一样,加上一个break。
列表和数组
TCL
中变量分成简单变量和组合变量。组合变量分成两种:列表和数组。这两种组合变量的数据结构虽然简单,但是功能强大。很多人抱怨,TCL
中怎么不能够象C/C++那样定义struct 或者union 这样的结构?这是拿C
语言的思维来使用高级脚本,是行不通的。实际上,有了列表和数组,基本上很少有实际问题无法解决。
列表是一个多个元素的有序的集合,每一个元素通过下标来进行操作,下标从0 开始,列表中的元素可以是另外一个列表,例如:
set students {
{LeiYuhou
Mail
27}
{Lily
Femail
25}
{Tiger
Mail
2}
}
set index 1
foreach s $students {
foreach {name sex age} $s {
puts "$index ->
$name" ;#打印出序号和名字
}
incr index
}
puts [lindex $students end]
上面的代码中,students 就是一个列表,里面三个元素同时也还是列表。通过foreach
循环把名字给打印出来。最后的语句通过lindex 命令取出列表的最后一个元素,并且打印出来。
TCL 中的数组则是多个元素的无序的集合,每一个元素都包含两个值:下标(key)和值(value
)。在一个数组中,所有元素的下标都是互不相同的,元素通过下标来进行索引和操作。元素值可以是字符串,也可以是列表。例如:
array set DAY {
0 Sunday
6 Saturday
5 Friday
4 Thirsday
3 Wednesday
2 Tuesday
}
;#初始化数组变量DAY
set Day(1)
Monday
;#设置DAY 数组的下标为1 的元素
puts "5 - $DAY(5)" ;#输出第五天的名字
puts "keys - [array names DAY]" ;#输出所有的下标
可以看到,TCL
中的数组和我们C 语言中熟悉的数组完全不是一回事,倒是和VBScript中的对象Dictionary 非常的类似。TCL
中没有多维数组的概念,但是后面我们会讲到如何使用多维数组。
输入输出
TCL 核心中提供了文件输入输出的命令,其中标准输入和标准输出可以看成特殊的文
件:它们在进程启动的时候自动打开。TCL 中的输入命令是gets,输出命令是puts:
set v 1
set fh [open "C:/a.txt"
w]
;#打开C:\a.txt 文件
fconfigure $fh -translation crlf ;#配置成文本模式
while { $v!="" } {
puts -nonewline "Please input you name:" ;#输出提示信息
gets stdin
v
;#从标准输入读入一个字符串
puts $fh "your name
$v"
;#写入到文件中去
}
close
$fh
;#关闭文件句柄;
gets
和puts 不仅可以操作磁盘文件,还可以操作串口,操作socket 句柄等。TCL
来源于UNIX,众所周知,UNIX
里面的文件是不区分文本和二进制的,但是为了兼容多种操作系统,这里增加了一个命令fconfigure
用来配置文件句柄属性,包括模式,缓冲区大小等。
类和面向对象
这里我们不介绍面向对象编程的概念,如果你还不知道什么是面向对象,可以找一本C/C++中相关部分的介绍先了解一下面向对象的基本概念。
TCL
是基于命令的语言,一个TCL 程序是由多个命令的线性组合,本来它是不支持面向对象编程的。但是TCL
的一个重要特点就是具有非常良好的扩展性,所以网络上出现了不少面向对象的扩展包,其中最有名的就是ITCL,我们前面的例子中复数类就是采用ITCL扩展包写成的一个类。ITCL
使TCL具备了完备的面向对象特性,并且在形式和语法上和C/C++非常类似:
1.
封装:每一个成员变量或者函数,都可以指定三种保护方式的一种:public、protected
和private;其意义和C++中完全一致;
2.
继承:一个类可以从多个类中派生,也就是说,一个类可以有几个基类;
3. 多态:ITCL
中类的任何一个成员函数都是虚函数!所以显然支持多态性了。
除了这三点,ITCL 类还支持构造函数和析构函数,其功能和C++的类似。
看看下面一个多态性的例子:
#使用ITCL 必须引入ITCL 扩展包
package require Itcl
namespace import itcl::*
#定义了基类CPerson
class CPerson {
protected variable
m_name
;#成员变量,保护类型,可以被继承
protected variable m_sex
constructor {name sex}
{
;#构造函数
set m_name $name
set m_sex $sex
}
public method PrintInfo {} {
;#public 方法,输出对象信息,可以被继承
puts "CPerson [GetInfo]"
;#调用了成员函数GetInfo
}
public method GetInfo {} {
return "name=$m_name;
sex=$m_sex"
;#返回对象信息
}
}
class CStudent {
inherit
CPerson
;#表示本类从CPerson 继承
protected variable m_age
constructor {name sex age} {
;#构造函数
CPerson::constructor $name $sex}
{
;#调用基类的构造函数
set m_age $age
}
public method GetInfo {} { ;#返回对象信息
return "name=$m_name; sex=$m_sex; age=$m_age"
}
}
CPerson a "LeiYuhou"
M
;#构造CPerson 对象实例
CStudent b "Lily" F
20
;#构造CStudent 对象实例
a PrintInfo
;#分别输出两个对象的信息
b PrintInfo
上面的代码中,声明了两个对象,CPerson 是CStudent 类的基类,CPerson 基类中声明了一个函数PrintInfo
用来输出一些信息,这个函数在CStudent
中也可以被调用。这个函数在基类CPerson
中定义,调用了另外的一个函数GetInfo,这个函数在基类和继承类中都有各自不同的定义;PrintInfo
究竟应该调用哪一个GetInfo,就看调用这个函数的对象类型。
上面的代码中,a
是CPerson 类型,b 是CStudent 类型,所以上面的代码执行结果是:
CPerson name=LeiYuhou; sex=M
CPerson name=Lily; sex=F; age=20
C++对象的多态性必须通过对象指针或者引用来体现,也就是说,只有通过对象指针来调用虚函数,C++的多态性才能够体现出来。这样的原因在于C++是通过虚表(virtual
table
的方式来实现多态性的。ITCL中没有指针这么一说,那么多态性如何实现的呢?这个问题我没有深入研究,但是一般脚本语言的多态性都是通过查表回溯的方式来实现,比如Python。TCL
应该也是采用类似的方法实现面向对象的多态性的。
函数库程序包
一个大型程序往往不是由一个文件组成的,一般需要很多其他的函数库。C/C++语言中,先逐个编译程序单元,再连接(link)所有的obj
文件以及使用到的库文件,最后生成目标程序。TCL 有两种自己特有的处理方式:函数库和程序包。
函数库是比较古老的方式,和C 语言的函数库比较类似。TCL
解释器在执行命令的时候,如果碰到不认识的命令,就会通过一定的索引方式在特定的位置寻找它。这种方式不支持版本升级,现在使用的比较少了,一般情况下我们使用另外一种方式:程序包。
程序包可以使用TCL
语言编写,也可以使用C/C++来实现;比如我们刚才提高的面向对象,就是通过扩展包ITcl
来实现的。一个脚本在使用某一个扩展包之前,必须先将扩展包引入,语法如下:
package require ?-exact? package ?version?
例如,为了使用http 协议处理包:
package require –exact http 2.4.5
其中包名字参数是必须的。如果指定选项-exact,表示加载指定的版本。在网络上存在很多很多Tcl
的扩展包,并且大部分是免费的,安装也非常方便:只要把相关的文件复制到特定的目录中即可。所以建议采用package 的形式来开发TCL
扩展代码。
总结
这一章我们介绍了TCL 的基本语法和简单的用法,给大家一个对TCL
粗略的认识。但是要真正掌握TCL
的使用,还有很多细节性的东西需要进一步了解。后面的章节中我们针对这一章出现的各个概念进行详细的解释。
加载中,请稍候......