到现在才讨论变量似乎有点迟,在过去的文章中我们已经很多次使用变量。
之所以到现在才讨论变量是因为不想各位因为复杂的变量使用规定感到困惑。如果只是基本使用,理解变量确实可以很简单。就像在之前的几篇文章中,我们使用了很多变量,我们不用进行复杂的讨论也可以理解这些变量。变量的简单使用也就是如何赋值,如何取值。
前面的文章我们使用setq函数对变量赋值,使用时直接通过变量名代表对应的值。如下面这样的代码:
(setq my-value 10)
(format *query-io* “value of my-value is : ~a” my-value)
不过,当我们进一步深入讨论函数,函数的参数,甚至是函数的递归调用时,就必须开始展开有关变量的讨论了。
变量名
首先是有关变量名的规定。不同的语言对变量名的使用都有一定的规定,如不能使用括号、分号,不能使用if, while这样的关键字等等。一种语言作出这种规定是必然的,任由程序员使用任意字符作为变量名会导致程序变得不可解释。不过,对于程序员来讲,大可不必详尽地记忆所有规定,简单的做法是尽量保守地使用变量名,如单纯地使用字母组合,像accountname,templist等。值得注意的是,Common Lisp语言允许程序员可以在变量名中使用横线“-”,而且横线的使用被当做Lisp语言的一种风格。所以,如果你想让人觉的你是个Lisp高手,建议使用account-name,temp-list这样的变量名。
如之前的文章提到过的,CLisp环境中允许使用中文字符作为变量名,如“我的变量”这样也是一个合法的变量名。不过,由于中文的特殊性,个人建议不要使用中文字符作为变量名。
还有就是Common Lisp中的全局变量一般以*号开头,以*号结束,比如我们之前调用format函数时使用的*query-io*就是系统定义的一个全局变量。如果你自己有一些全局变量的话,最好也是使用相同的格式,如*my-database*。
变量类型
有关变量类型的一个好消息是Lisp中的变量不需要指定类型,对于一个变量,赋予它什么类型的值它就是什么类型的变量。如(setq i 10),那i就是一个数字类型,而(setq i "my string")会让i变成一个字符串类型的变量。Lisp语言会在变量使用过程中动态检查他们的类型,比如(setq i "my string") (+ i 10)会报错,Lisp在执行“(+ i 10)”的过程中发现变量i是一个字符串,不能进行“加”的操作,所以报错。
对于那些使用JavaScript, Ruby等动态类型语言的程序员来讲,动态类型是一个熟悉的概念。而对于那些习惯于c, c++ , java中静态类型的程序员来讲,Lisp中的这种动态类型变量会有点令人不舒服,感觉语言的不确定性很大,不好把握。不过,动态类型正是Lisp语言的一大优势,对于动态变量的优势我们在以后的文章中继续讨论。目前我们暂时简单地记住Lisp中的变量是动态类型就好了。
变量的作用范围
相比变量名的规定,变量的作用范围对程序员来讲更加重要一些。因为变量名和变量的类型的错误使用比较容易被系统发现,程序员更容易发现问题,解决问题。而系统并不容易识别变量作用范围的错误,如果对变量的作用范围理解不准确,在程序中使用不当,往往程序运行时不报错,但是运行结果是错误的。
在没有任何附加条件下,CLisp中通过“setq”或者是“setf”函数设置的变量是全局变量。其中提到的setf和setq都是设置变量的函数,有关他们的区别我们后面有机会再讨论,现在暂时认为它们是一样的。
也就是说你在函数外面定义一个变量,在函数中可以直接使用。反过来,如果你在函数内定义一个变量,在函数外面也是可以使用的。
如下面的代码,function1函数外定义的变量var-i可以在function1中直接使用,而function1中定义的变量var-j也可以在function1退出后使用:
(defun var-test ()
(setf var-i 1)
(format *query-io* "var-i is: ~a ~%" var-i)
(function1)
(format *query-io* "var-i is: ~a ~%" var-i)
(format *query-io* "var-j is: ~a ~%" var-j))
(defun function1 ()
(setf var-j 10)
(format *query-io* "in function:var-i is: ~a ~%" var-i)
(format *query-io* "in function:var-j is: ~a ~%" var-j))
以上代码执行结果如下:
当一个函数有定义参数时情况会变得复杂一点。
首先有一点是比较明确的,函数的参数只在函数内有效。比如我们定义了函数var-test,带有参数var-f,那么var-f可以作为一个变量在函数var-test中使用。不过,一旦离开函数var-test,var-f就不能使用了。
问题就是如果一个函数的参数和一个全局变量同名会出现什么情况。
如下面的代码,函数function2有一个参数var-j,而函数function2外定义了一个全局变量var-j。
在这种情况下,var-j在function2函数内可以被当做一个临时变量,function2中对var-j的任何修改都不会影响function2以外var-j的值。
(defun var-test2 ()
(setf var-i 1)
(setf var-j 2)
(format *query-io* "var-i is: ~a ~%" var-i)
(format *query-io* "var-j is: ~a ~%" var-j)
(function2 var-j)
(format *query-io* "var-i is: ~a ~%" var-i)
(format *query-io* "var-j is: ~a ~%" var-j))
(defun function2 (var-j)
(format *query-io* "in function:var-i is: ~a ~%" var-i)
(format *query-io* "in function:var-j is: ~a ~%" var-j)
(setf var-i 10)
(setf var-j 20)
(format *query-io* "in function:var-i is: ~a ~%" var-i)
(format *query-io* "in function:var-j is: ~a ~%" var-j))
以上代码中var-j首先被赋值为2,函数function2定义了参数var-j,所以var-j在函数function2中相当于一个临时变量,在函数function2中被赋值为20,一旦离开函数function2,它的值又变回2了。
另外一个变量var-i,因为没有定义为函数function2的参数,所以它一直都是全局变量,在函数function2被赋值为10,离开函数function2后值仍是10。
另外一种定义临时变量的方法是使用let函数,let函数的使用例子如下:
(let ((x 5) (y 10))
(format *query-io* "x is: ~a ~%" x)
(format *query-io* "y is: ~a ~%" y)
;更多语句
)
上面的例子定义了一个作用范围,在这个范围中可以使用临时变量x和y,对x和y的修改一旦离开let函数的范围就失效了。更详细的样例如下:
(defun var-test3 ()
(setf var-i 1)
(setf var-j 2)
(format *query-io* "var-i is: ~a ~%" var-i)
(format *query-io* "var-j is: ~a ~%" var-j)
(let ((var-j 10))
(format *query-io* "var-i in let is: ~a ~%" var-i)
(format *query-io* "var-j in let is: ~a ~%" var-j)
(setf var-i 100)
(setf var-j 200)
(format *query-io* "var-i in let is: ~a ~%" var-i)
(format *query-io* "var-j in let is: ~a ~%" var-j))
(format *query-io* "var-i is: ~a ~%" var-i)
(format *query-io* "var-j is: ~a ~%" var-j))
函数var-test3中定义了两个全局变量var-i 和var-j,分别赋值为1和2
当进入let函数的范围时,var-j被临时赋予了10这个值,在这个范围内var-j就是一个临时变量了。接着var-j被赋值为200,不过一旦离开let的范围,var-j的值就变回2了。
其中对全局变量var-i的赋值操作会在全局范围生效,所以离开let的范围后var-i的值还是100.
以上简单讲了变量的作用范围,希望可以给你一些参考。
简单的总结是:
1. setq, setf可以用于定义全局变量。
2. 函数的参数是临时变量,作用范围只在函数内。
3. let函数定义的变量是临时变量,在let函数内有效。
4. 对临时变量执行setq和setf操作不对全局变量产生影响。
其实,有关变量的作用范围还有很多需要讨论的,因为篇幅的关系,这里暂时就不展开了。
最后要留意的是Common Lisp中有包的概念,引入包的概念以后对于变量的使用会更复杂一点。本篇文章的讨论没有考虑包的情况。