1.3 弱类型强作用域

优质
小牛编辑
117浏览
2023-12-01

1.3 弱类型强作用域

“弱类型”不是 VimL 的特点,是几乎所有脚本语言的特点。准确地说是变量无类型,但值 有类型。创建变量时不必定义类型,直接赋值就行,也可以认为是变量临时获得了值的类 型。关于 VimL 的变量与类型,将在下一章的基础语法中详解。

变量作用域是编程的另一个重要概念,也几乎每个语言都要管理的任务。这里说 VimL 具 有“强作用域”的特点,是指它提供了一种简明的语法,让用户强调变量的作用域范围。

VimL 语言级的作用域 g: l: s: a:

变量作用域的意义是指该变量在什么范围内可见,可被操作(读值或赋值)。在 VimL 中 ,每个变量都可以加上一个冒号前缀,表示该变量的作用域。不过另有两条规则:

  1. 在一些上下文环境中,可以省略作用域前缀,等效于加上了默认的作用域前缀
  2. 有一些作用域前缀只在特定的上下文环境中使用。

从 VimL 语言角色看,主要有以下几种作用域:

  1. g: 全局作用域。全局变量就是在当前 vim 会话环境中,在任何脚本,任何 ex 命 令行中都可以引用的变量。所有在函数之外的命令语句,都默认是全局变量。
  2. l: 局部作用域。只可在当前执行的函数体内使用的变量,在函数体内的变量默认为 局部变量,l:局部变量也只能在函数体内使用。
  3. s: 脚本作用域。只有当前脚本内可引用的变量,包括该脚本的函数体内。
  4. a: 参数作用域。特指函数的参数,在函数体内,要引用传入的实参,就得加上 a: 前缀,但定义函数时的形参,不能加 a: 前缀。a: 还隐含一个限定是只读 性,即不能在函数体内不能修改参数。

这几种作用域前缀所对应的英文单词,可认为是 global, local, scriptargument。不过 s: 也可认为是 static,因为在 C 语言中,static 也表示只 在当前文件中有效的意思。

变量作用域的应用原则:

  1. 尽量少用全局变量,因为容易混乱语用,难于管理。不过在 ex 命令行或 Ex 模式下 只为临时测试的语句,为了方便省略前缀,是全局变量,当然在此命令中也只能是全 局变量。在写 vim 脚本文件时,若要使用全局变量,不要省略 g: 前缀。同时全局 变量名尽量取得特殊点,比如全是大写,或带个插件名的长变量名,以减少被冲突的 概率。
  2. 局部变量的前缀 l: 一般可省略。但我建议也始终加上,虽然多敲了两个字符,但 编程的效率来源于思路清晰,不在于多少那几个字符。同时在 VimL 编程时,坚持习 惯了作用域前缀,就能在头脑中无形地加强这种意识,然后对作用域的把握也更加精 准。另外,显然地,在函数体内要引用全局就是必须加上 g: 前缀。
  3. 在写 vim 脚本时,函数外的代码,能用 s: 变量就尽量用 s: 变量。对于比较大 的脚本变量(如字典),想对外分享,也宁可先定义为 s: 变量,再定义一个全局 可访问的函数来返回这个脚本变量。
  4. 参数变量,a:是语法强制要求,漏写了 a: 往往是个错误,(如果它没报错,恰 好与同名局部变量冲突了,那是更糟糕与难以觉察的错误)也是初写 VimL 函数最容 易犯的语法错误。

Vim 实体作用域 b: w: t:

Vim 作为一个可视化的编辑器,给用户呈现的,能让用户交互地操作的实体对象主要有 buffer(缓冲文件),window(窗口),tabpage(标签页)。可以把它们想象为 互有关系的容器:

  • 缓冲对应着一个正在编辑中的文件,在不必究的情况下可认为与文件等同。(不过不一 定对应着硬盘上的一个文件,比较新建的尚未保存的文件,以及一些特殊缓冲文件)缓 冲也可认为是容纳着文件中所有文本行的容器,就像是简单的字符串列表了。
  • 窗口是用于展示缓冲文件的一部分在屏幕上的容器。Vim 可编辑的文件很大,极有可能 在一个屏幕窗口中无法显示文件的所有内容吧,所以窗口对应于缓冲文件还有个可视范 围。一个窗口在一个时刻只能容纳一个缓冲文件,但在不同时刻可以对应不同的缓冲文 件。
  • 标签页是可以同时容纳不同的窗口的另一层更大的容器。原始的 Vi 没有标签页,标 签页是 Vim 的扩展功能。标签页极大增强 Vim 的可视范围,可认为窗口是平面的 ,再叠上标签页就是(伪)立体的了。
  • 一个缓冲文件可以展示在不同的窗口或(与)标签页中。所有已展示在某个窗口(包括 在其他标签页的窗口)的缓冲文件都是“已加载”状态,其他曾经被编辑过但当前不可见 的缓冲文件则是“未加载”状态,不过 Vim 仍然记录着所有这些缓冲文件的列表。

然后,Vim 还有个“当前位置”的概念。也就是光标所在的位置,决定了哪个是“当前缓冲 文件”,“当前窗口”与“当前标签页”。

有了这些概念,对 VimL 中的另外三个作用域前缀 b: w: t: 就容易理解了。其意 即指一个变量与特定的缓冲文件、窗口或标签页相关联的,以 b: 举例说明。

  • b:varname 表示在当前缓冲文件(实体对象)中存在一个名为 "varname" 的变量。
  • VimL 语句在执行过程中,只能直接引用当前缓冲文件的 b: 变量,如果要引用其他 缓冲文件的变量,要么先用其他命令将目标缓冲文件切换为当前编辑的缓冲文件,或者 调用其他的内置函数来访问。
  • 如果一个缓冲文件“消失”了,那么与之关联的所有 b: 变量也消失了。
  • 窗口与标签页的“消失”能比较形象与容易地理解,关闭了就算消失了。但 Vim 内容对 缓冲的管理比较复杂,未必是从窗口上不见了就代表“消失”了。
  • 不过在一般 VimL 编程中,可暂不必深究缓冲文件什么时候“消失”。只要记着一个 b: 变量必定与一个缓冲文件关联着,不同的缓冲文件使用相同的 b: 变量是安全的, 它们互不影响。

作用域前缀其实是个字典

以上介绍的各种作用前缀,不仅是种语法约定的标记,它们本身也是个变量,是可以容纳 保存其他变量的字典类型变量。关于字典,在后续章节再详述。这里只能介绍几个演示示 例来体会一下这种特性。

为了操作环境一致,也假设按上节的“裸装” Vim 启动:(不过其实不太影响,也不必太 拘泥)

$ cd ~/.vim/vimllearn
$ vim -u NONE helloworld.txt

现在已用 vim 打开了一个 helloworld.txt 文件。在命令行输入以下 ex 命令:

: let x = 1
: echo x
: echo g:x
: echo g:.x
: echo g:['x']
: echo g:

你可以每次按冒号进入命令行逐行输入,也可以先进入 Ex 模式,连续输入这几行,效 果是一样的(以后不再注明)。

首先用 :let 命令定义了一个 x 变量,命令行语句默认是全局变量。然后用 :echo 命令使用几种不同写法来引用读取这个变量的值,这几种写法都是等效的。最后 将 g: 当作一个整体变量打印显示。它就是个全局字典,能里面包含了 x 键,值就 是 1。(如果按正常的 vim 启动,你的 vimrc 以及各种插件可能会提供很多全局变量 ,那么 echo g: 的内容可能很多,不只 x 哟)

然后我们再写个脚本观察下 s: 变量。:e hello2.vim,输入以下内容并保存:

" File: ~/.vim/vimllearn/hello2.vim
let s:hello = 1
let s:world = 2
let s:hello_world = s:hello + s:world
echo s:

脚本写完了,在 ex 命令行输入以下几条测试下:

: source %
: echo s:
: echo s:hello

可见,: source % 命令能正常执行,脚本内的 :echo s: 打印出了该脚本内定义的 所有 s: 脚本变量。但在命令行直接试图访问 s: 变量则报错了。

在脚本中也可以访问全局变量。可以自行尝试在 hello2.vim 中加入对刚才在命令行定 义的 g:x 变量的访问。不过在实际的编程中,可千万别在脚本中依赖在命令行建立的 全局变量。

然后再测试下 b: 变量,直接在命令行执行以下语句吧:

: let b:x = 2
: echo b:x
: echo x
: echo g:x
: e #
: echo b:x
: echo b:
: echo x
: e #
: echo b:x
: echo b:
: echo x

这里,:e # 表示切换编辑另一个文件。在实际工作中,或者用快捷键 <Ctrl-^> 更 方便,不过在本教程中,为说明方便,采用 ex 命令 :e # 。在本例中,vim 启动时 打开的文件是 helloworld.txt,后来又编辑了 hello2.vim;此时用 :e # 命令就 切回编辑 helloworld.txt了,再执行 :e # 就再次回 hello2.vim 中,这是轮换 的效果。

这个示例结果表明,在编辑 hello2.vim 时定义了一个 b:x 变量,这与全局的的 x 变量是互不冲突的。但是在换到编辑 helloworld.txt 时,b:x 变量就不存在了,因 为并未在该缓冲文件中定义 b:x 变量呀。重新回到编辑 hello2.vim 文件时,b:x 变量又能访问了。这也说明当缓冲文件“不可见”时,vim 内部管理它的对象实体其实并未 “消失”呢。而全局变量 g:xx 是始终能访问的。

最后要指出的是,局部作用域 l: 与参数作用域 a: 不能像 s:b: 这样当作 整数的字典变量,是两个例外。VimL 这样处理的原因,可能一是没必要,二是没效率。 函数体内的局部作用域与参数作用域,太窄,没必要将局部变量另外保存一个字典;而且 有效时间太短,函数在栈上反复重建销毁,额外维护一个字典没有明显好处就不浪费了。

其他特殊变量前缀 $ v: &

这几个符号其实并不是作用域标记。不过既然也是变量前缀,也就一道说明一下,也好加 以区分。

$ 前缀的变量是环境变化。除了 vim 在启动时会从父进程(如 shell)自动继承一 些环境变量,这些变量在使用上与全局变量没什么区别。不过要谨慎使用,一般建议只读 ,不要随便修改,没必要的话也不要随便创建多余的环境变量。

v: 前缀的变量是 vim 内部提供的预定义常量或变量。用户不能增删这类特殊变量 ,也不能修改其类型与含义。比如 v:truev:false 分别用于表示逻辑值“真”与“ 假”。Vim 所支持的这类 v: 变量往往随着版本功能的增加而增加。从与时俱进的角度 讲,vim 脚本中鼓励使用这类变量增加程序的可读性,但若想兼容低版本,还是考虑慎用 。要检查当前 vim 版本是否支持某个 v: 变量,只要用 :help 命令查阅一下即可。 而且 v: 本身也是个字典集合变量,可用 :echo v: 命令查看所有这类变量。

& 前缀的变量表示选项的,相当于把选项变量化,以便于在 VimL 中编程。所支持 的选项集,也是由 Vim 版本决定的,用户当然无法定义与使用不存在的选项。这部分内 容在后面讲选项设置时再行讨论。