静态作用域与动态作用域
2013-11-10 12:06 youxin 阅读(2674) 评论(0) 编辑 收藏 举报看一个问题http://www.zhihu.com/question/20032419
词法域是否等同静态作用域?
问题来源:《Scheme 程序语言介绍》(http://www.ibm.com/developerworks/cn/linux/l-scheme/part2/)中「一是把 Lisp 从 Dynamic scope 变成了 Lexical scope」
词法作用域(lexical scope)等同于静态作用域(static scope)。所谓的词法作用域其实是指作用域在词法解析阶段既确定了,不会改变。
我们要知道js是遵循静态作用域的。举个例子:
var foo=1;
function static(){
alert(foo);
}
!function(){
var foo=2;
static();
}();
在js中,会弹出1而非2,因为static的scope在创建时,记录的foo是1。
如果js是动态作用域,那么他应该弹出2。请体会一下两者的区别。
1. 例子
首先定义函数foo,然后调用:
;;;; field.lisp (let ((y 7)) (defun foo (x) (print x) (print y))) (let ((y 5)) (foo 1))
使用sbcl和emacs来执行:
> sbcl --script field.lisp 1 7 > emacs --script field.lisp 1 5
很明显,这两个解释器的执行结果是不同的。
造成以上结果不同的原因和变量的绑定(binding)有关。 绑定代表了变量在运行时期的存在,存在相应就有作用域(scope)和生存期(extent)。 简单说:
- common lisp采用的是静态作用域。在foo中寻找y的绑定时,它检查函数foo的定义环境。
- emacs lisp采用的是动态作用域。在foo中寻找y的绑定时,它检查函数foo的执行环境。
2. 静态作用域
典型:C, C++, Python, Java, 大多数现在程序设计语言都是采用静态作用域规则。
静态作用域又叫做词法作用域,使用词法作用域的变量叫词法(lexical)变量。 词法变量都有一个确定的作用域和不确定的生存期。 词法变量的作用域可以是一个函数或block,使得其在这段代码区域内都有效。 但是词法变量的生存期取决于该变量需要引用(reference)多久(?)。
词法作用域里,对于函数体中的一个符号,不会逐层检查函数的调用链,而是检查函数定义时的外部环境,即捕捉的是函数定义时该符号的绑定。
在例子中的第一个let
表里,定义了一个变量,符号名为y
并绑定了值7,那么这个y
的作用域就是这个let
表区域。 foo
函数定义在这个区域内,其内部会使用到一个符号名为y
的变量。 那么在词法作用域的情况下,当foo
被调用时,其会查找其定义的环境有没有符号名y
的变量可以绑定,如果有则把foo
中符号y
的值绑定,在这里就是7。 并且这里foo
中的y
和外部let
中的y
共享一个值,都是对这个值的引用,并不是拷贝了一个新值。把例子中的代码做如下改变:
;;;; field2.lisp (let ((y 7)) (defun foo (x) (print x) (print y) (setq y (+ y 2)))) (let ((y 5)) (foo 1) (foo 1)) (let ((y 5)) (foo 2))
> sbcl --script field2.lisp 1 7 1 9 2 11
这说明前后两次调用foo
的过程中,y
共享着同一个值。
最后要说明的是,Common Lisp中的词法变量虽然类似于某些语言中的局部变量,这些局部变量并没有提供类似Common Lisp词法变量的所有功能,尤其是并非所有语言都提供了支持闭包的词法作用域变量。
3. 动态作用域
典型:Emacs Lisp, Common Lisp(两种都有), Perl(两种都有)。
使用动态作用域的变量叫做动态(dynamic)变量,有时也叫做特殊(special)变量。 动态变量具有不确定的作用域,只取决于在什么地方绑定它。 同时从绑定开始,到绑定时的代码段执行完毕,这就是动态变量的生存期。
动态作用域里,函数执行遇到一个符号,会由内向外逐层检查函数的调用链,并打印第一次遇到的那个绑定的值。显然,最外层的绑定即是全局状态下的那个值。
在例子中的第二个let
表里,同样定义一个变量,符号名为y
并绑定到值5。那么这个y
的作用域是在这个(第二个)let
表及其调用的函数里。 在动态作用域下,其内部调用的函数foo
中的y
并不是检查其定义的环境,而是检查调用的环境,首先检查foo
内部有没有y
的定义,没有的话检查调用其的第二个let
区域内有没有y
的定义,发现有,则绑定到值5上面。为了说明问题,把代码也做改变:
;;; field3.lisp (let ((y 7)) (defun foo (x) (print x) (print y) (setq y (+ y 2)))) (let ((y 5)) (foo 1) (foo 1)) (let ((y 5)) (foo 2))
> emacs --script field3.lisp 1 5 1 7 2 5
第二个let
区域内y
的值是共享的,所以第二次调用foo
会使用第一次+2的结果。 而第三个let
区域重新定义了y
值,而这次调用foo
会绑定到这个新值。
参考
转自http://www.findfunaax.com/notes/file/126
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
2012-11-10 Excel引用分类
2012-11-10 Excel生成sql脚本
2011-11-10 vs2010Express 和vs2010旗舰版在功能上有什么具体的不同?