Lisp程序是用Lisp對象表示的央勒,
但是代碼卻是用文本形式來書寫的梨熙,
Lisp讀取器會通過對象的read syntax來將文本讀取為對象。
變量就是symbol對象的read syntax,
例如:x
是一個變量,它表示一個symbol霉猛。
即尺锚,變量是程序中的一個名字珠闰,它用于表示一個值。
在Emacs Lisp中瘫辩,每一個變量對應(yīng)一個symbol伏嗜,
變量名就是symbol的name,變量的值保存在symbol的value cell中伐厌。
1. 全局變量
使用defvar
和defconst
來定義全局變量承绸。
setq
可用于改變一個變量的值,如果沒有就創(chuàng)建全局變量挣轨。
全局變量在整個Emacs Lisp程序生命周期內(nèi)都有效军熏,除非你改寫它的值。
例如:
(setq x '(a b))
x ; (a b)
(setq x 4)
x ; 4
2. 局部變量
全局變量直到被設(shè)置新的值卷扮,否則一直保持原來的值不變荡澎,
然而,很多時候晤锹,僅在局部給變量使用某個其他的值是有用的摩幔。
這個值只在這一段程序中有效,當控制流離開了這塊代碼鞭铆,變量又恢復(fù)為進入之前的值或衡。
我們說這種行為是,局部變量遮擋(shadow)了它以前的值车遂。
例如封断,當一個函數(shù)被調(diào)用時,它的參數(shù)變量就是局部變量舶担,
局部變量的值只在函數(shù)體中有效澄港,
同一個函數(shù)的不同調(diào)用,參數(shù)變量可能會被賦予不同的值柄沮。
let
也可以用于創(chuàng)建局部變量回梧,
且只在let
體中有效废岂。
3. 變量綁定的作用域規(guī)則
每一個局部變量的綁定都具有兩方面的屬性,
作用域(scope)和生存期(extent)狱意。
作用域表示财骨,在源代碼文本中藏姐,綁定在什么地方(where)有效。
生存期表示捌臊,在程序執(zhí)行的過程中,綁定在什么時候(when)有效糠爬。
Emacs Lisp支持兩種形式的綁定,
動態(tài)綁定(dynamic binding)和靜態(tài)綁定(lexical binding)镀琉。
動態(tài)綁定具有動態(tài)作用域和動態(tài)生存期,
動態(tài)作用域(dynamic scope),任何一段代碼都可能訪問變量的綁定亚侠,
動態(tài)生存期(dynamic extent),只有只有在綁定結(jié)構(gòu)(例如let
)執(zhí)行的過程中串稀,綁定才有效。
靜態(tài)綁定具有詞法作用域和無限的生存期清寇,
詞法作用域(lexical scope),綁定在綁定結(jié)構(gòu)的源代碼文本范圍中有效盔夜,
無限生存期(indefinite extent)绽族,某些情況下,綁定可能永遠有效匈仗。
(1)動態(tài)綁定的具體實現(xiàn)
動態(tài)綁定在Emacs Lisp中通過以下方式實現(xiàn),
每一個symbol都有一個value cell,表示變量的當前值(current dynamic value)何鸡,
當一個symbol被給定一個局部綁定時(dynamic local binding)傍睹,
Emacs會把原來的value cell記錄在一個棧上已亥,然后把新值放入value cell中。
當綁定結(jié)構(gòu)執(zhí)行完后,Emacs進行彈棧操作,取出舊的值放回value cell中珊豹。
例子:
(defvar x 0)
(defun getx ()
x)
(let ((x 1))
(getx)) ; 1
(getx) ; 0
(2)靜態(tài)綁定的具體實現(xiàn)
每一個綁定結(jié)構(gòu)會創(chuàng)建一個詞法環(huán)境(lexical environment),
在這個環(huán)境中保存了劫恒,變量名和它所對應(yīng)值之間的對應(yīng)關(guān)系贩幻,
當Lisp求值器對某個變量求值的時候丛楚,它首先從詞法環(huán)境中尋找值,如果找到了,就用這個值枉圃。
否則就認為這個symbol是一個動態(tài)變量,讀取symbol的value cell作為變量的值孵延。
; -*- lexical-binding: t -*-
(setq test (let ((foo "bar"))
(lambda ()
foo)))
(let ((foo "something-else"))
(funcall test)) ; "bar"
(funcall test) ; "bar"
注:
(1)動態(tài)綁定變量的值總是從symbol的value cell中獲取,
而靜態(tài)綁定變量的值從詞法環(huán)境中獲取尘应,
所以惶凝,無法使用symbol-value
獲取靜態(tài)綁定變量的值。
; -*- lexical-binding: t -*-
(let ((x 1))
(symbol-value 'x)) ; Symbol’s value as variable is void: x
(2)全局變量是動態(tài)綁定的玷犹,
即使啟用了詞法綁定規(guī)則混滔,let
并沒有引入新的靜態(tài)變量x
,
而是歹颓,建立了局部動態(tài)變量x
坯屿,然后用局部動態(tài)變量遮擋了全局動態(tài)變量的值。
; -*- lexical-binding: t -*-
(setq test (let ((x 1))
(lambda ()
x)))
(funcall test) ; 1
; -*- lexical-binding: t -*-
(defvar x 0)
(setq test (let ((x 1))
(lambda ()
x)))
(funcall test) ; 0
在進行試驗時巍扛,需要在全新的buffer中领跛,分別測試,
否則(defvar x 0)
一旦執(zhí)行电湘,即使再重新M-x eval-buffer
隔节,x
的值已經(jīng)被定義了鹅经。
4. Symbol
symbol是一個對象寂呛,它具有唯一的名字,
symbol內(nèi)瘾晃,包含4個組成部分贷痪,稱為cell,
(1)name:symbol的名字
(2)value cell:作為一個動態(tài)變量蹦误,symbol的值
(3)function cell:作為一個函數(shù)劫拢,它的函數(shù)值
(4)property list:屬性列表
defvar
和defconst
會創(chuàng)建一個全局symbol,并設(shè)置value cell强胰。
defun
和demacro
會創(chuàng)建全局symbol舱沧,并設(shè)置function cell。
當Lisp讀取器遇到一個symbol的時候偶洋,它會從源代碼中讀取到symbol的名字熟吏,
然后在一個帶索引的數(shù)據(jù)結(jié)構(gòu)中查找symbol,這個數(shù)據(jù)結(jié)構(gòu)稱為obarray,
其索引是symbol名字的哈希值牵寺。
在Emacs Lisp中悍引,obarray實際上是一個向量(vector),
根據(jù)哈希值帽氓,會查找到obarray的某一個元素趣斤,obarray的元素是一個桶(bucket),
里面包含了用鏈表存儲的具有相同哈希值的symbol黎休。
Emacs默認有一個obarray浓领,用戶也可以創(chuàng)建自己的obarray,
通過make-vector
可以創(chuàng)建一個新的obarray势腮。
(make-vector 7 0) ; LENGTH=7,INIT=0
每個obarray中symbol的名字不能相同镊逝,
相同名字的symbol可以放入不同的obarray中。
obarray中的symbol稱為interned symbol嫉鲸,
還有symbol不在任何obarray中撑蒜,稱為uninterned symbol。
通過make-symbol
可以創(chuàng)建uninterned symbol玄渗,
(setq sym (make-symbol "foo")
(eq sym 'foo) ; nil
通過intern
可以將名字放入默認或指定的obarray中座菠。
(defvar other-obarray
(make-vector 7 0))
(setq sym (intern "foo")
(eq sym 'foo) ; t
(setq sym1 (intern "foo" other-obarray)
(eq sym1 'foo) ; nil
通過unintern
可以從默認或指定的obarray中刪除symbol。