# 第二章 VimL 语言基本语法 ## 2.1 变量与类型 VimL 语言的变量规则与其他大多数语言一样,可以(只允许)由字母、数字与下划线组 成,且不能以数字开头。特殊之处在于还可以在变量名之前添加可选的作用域前缀,如 `g: l: s: b: w: t:`(`a:`又有点特殊,在定义函数参数时不要前缀,而在使用参数时 需要前缀),这在第一章有专门讨论,此不再叙说。 VimL 所支持的变量(值)类型可由帮助(`:help type()`)查看。其中最主要最常用的 有数字(number)、字符串(string)、列表(list)与字典(dictionary)四种,或者 可以再进一步归纳为三种,因为前两种(数字与字符串)在绝大数情况下自动转换,在使 用时几乎不必考虑其类型差别,只须知道它表示“一个值”,所以也称作标量。而列表与字 典变量则是“多个值”的集合,所不同在于获取其中某个值的索引方式不同。 ### 标量:数字与字符串 数字是(number)直译,其实就是其他大多数语言所称的整数(int)。数字是有符号的 ,包括正负数与 0 ,其取值范围与 Vim 的编译版本有关。经笔者的测试,vim8.0 支持 8 字节的有符号整数,低版本只支持 4 字节的有符号整数。数字经常(或常规功能)是 用于命令的地址参数表示行号,或命令的重复次数,一般情况下不必考虑数字溢界的问题 。当你需要用到 VimL 来表达很大的整数时,才要小心这个潜在的问题。 字符串也简单,用单引号或双引号括起来即可,它们的语义是完全一致的。不过有以下使 用建议原则: * 一般使用单引号表示字符串,如 `'string'`,毕竟双引号还可用于行末注释,尽量避 免混淆。 * 如果需要使用转义如 `\n` `\t`,则使用双引号,单引号不支持转义。 * 如果字符串包含一种引号,则使用另一种引号括起整个字符串。 * 如果有包含一层引用,则内外层用不同的引号。 数字变量支持常见的数学运算(加减乘除模,`+-*/%`),字符串只支持连接运算符,用 点号(`.`)表示。此外,一些内建函数与命令也会要求其参数是数字或字符串。也就是 数字与字符串有不同的使用环境,VimL 便能依据上下文环境将数字或字符串进行自动转 换,规则如下: * 数字转字符串表示是显而易见的,就是十进制数的10个数字字符表示法; * 字符串转数字时,只截前面像数字的子串,若不以数字字符开头,则转为数字 0 。 请测试: ```vim : echo 'sring' . 123 : echo '123' : echo '123' + 1 : echo '123string' + 1 : echo '1.23string' + 1 : echo 'string123' + 1 ``` 需要特别注意的是字符串 `'1.23'` 只会自动转为字数 `1` ,而不是浮点数 `1.23`。 在 VimL 中输入数字常量时,也支持按二进制、八进制、十六进制的表示法,不过自动转 字符串时只按十进制转换。请自行观察以下结果,如果不懂其他进制可无视。 ```vim : echo 0xff : echo 020 : echo 0b10 : echo 0b10 + 3 : echo 'string' . 0xff ``` ### 列表:有序集合 列表,在其他语言中也有的叫数组,就是许多值的有序集合。VimL 的列表有以下要点: * 创建列表语法, 中括号整体,逗号分隔元素: `:let list = [0, 1, 2, 3]` * 用中括号加数字索引访问元素:`:echo list[1]` * 索引从 0 开始,支持负索引,-1 表示最后一个元素,访问不存在索引时报错。 * 索引也可以用整数变量。 * 不限长度,在需要时会自动扩展容量。 在列表创建后,用内建函数 `add()` 与 `remove()` 动态增删元素,用 `len()` 函数取 得列表长度(元素个数)。例如: ```vim : let list = [0, 1, 2, 3] : echo list : call add(list, 4) : call add(list, 5) : echo list : call remove(list, -1) | echo list : call remove(list, 1) | echo list ``` ### 字典:无序集合 字典,在其他语言中可能叫 Hash 表或散列表,就是许多“键-值”对的集合。与列表最大 的不同在于,它不是用数字索引来访问其内的元素,而是用字符串索引(键)来访问元素 。字典在内部存储方式是无序的,但通过键访问元素的速度极快。 定义与使用字典的语法示例如下: ```vim : let dict = {'x': 1, 'y': 2, 'z': 3,} : echo dict : echo dict['x'] : echo dict.y : let var = 'z' : echo dict[var] : let dict['u'] = 4 : let dict.v = 5 : echo dict ``` 语法要点: * 字典用大括号 `{}` 括号整体。 * 每个键值对用逗号分隔,键与值用冒号分隔,键一般有引号表字符串。 * 大括号内的空白是可选的,最后一个逗号也是可选的。 * 访问字典内某个元素时,仍是中括号 `[]` 索引,键放在中括号中。 * 在创建字典或访问元素时,键既可用引号引起的常量字符串,也可用字符串变量,数字 变量自动转换为字符串。 * 当一个键是普通常量字符串(可用作变量名的字符串)时,可不用中括号加引号索引, 而简洁地用点号索引,二者等价。 * 不能访问不存在的键,否则报错。 * 能直接对不存在的键赋值,表示对字典增加一个键值对元素。 ### 删除变量 创建(或叫定义)变量用 `:let` 命令,相应的也就有 `:unlet` 命令用于删除一个变量 。一般情况下没必要删除一个标量,因为它也占不了多少内存,需要重定义时也可以重新 赋值。但对于列表与字典,有时比较在意其集合意义,可以用 `:unlet` 删除其中一个值 ,加上对应的索引即可,如果不加索引,则表示删除整个列表或字典。 例如:假设 `list` 与 'dict' 变量已如上定义: ```vim : unlet list[1] | echo list : unlet list[-1] | echo list : unlet dict['u'] | echo dict : unlet dict.v | echo dict ``` 如果要删除的变量或字典(列表)不存在的索引,`:unlet` 会报错。如果想绕过该错误 检测,则可用 `:unlet!` 命令。 在 vim8.0 版本之前,标量、列表、字典三者是不互通的。如果 `list` 已被定义成了一 个列表变量,那么它就不能用 `:let` 重赋值为一个字典或字符串或其他什么,但允许重 赋值为另一个列表变量。如果一定要改变 `list` 的变量类型,只能先 `:unlet` 它,再 重新 `:let` 它为其他任意变量。 在 vim8.0 版本之后,不再有这个限制,不会再报诸如“类型不匹配”的错误了,更好地体 现了动态弱类型的特点。然而,良好的命名规范要求变量名望文生义,在同一个范围的同 一个变量名,前后用之于表达完全不同类型的变量,并不是个好习惯。 ### 浮点数 虽然浮点数在 VimL 中用的比较少,但毕竟还是支持的。 * 浮点数也叫小数,支持科学记数法。 * 数字(整数)可自动转为浮点数。 * 浮点不能自动转为整数,也不能自动转为字符串。 * 整数运算结果仍是整数,浮点数运算结果仍是浮点数。 * 浮点数取整后仍是浮点数,不是整数。 请看以下示例: ```vim : echo 1.23e3 : let int = 123 | let float = 1.23 | let str = 'string' : echo str . int : echo str . float |"错误 : echo str . 5 / 3 : echo str . 5 % 3 : echo str . 5 / 3.0 |"错误 : echo str . 5 % 3.0 |"错误 : echo round(5/3.0) : echo round(5/3.0) == 2 : echo round(5/3.0) . str |"错误 ``` 最后一行语句说明,虽然一个浮点数取整后看似与一个整数相等,但它仍然不是整数,所 以不能与字符串自动连接。 要将一个字符串“显式”转换为整数,可以与 `0` 相加;同理,要将整数“显式”转换为字 符串,可与空串 `""` 相连接。VimL 还提供了另一个内建函数 `string()` 将任意其他 类型转换为可打印字符串。于是,想将一个浮点数转换为“真正”的整数,可用如下操作: ```vim : echo 0 + string(round(5/3.0)) : echo type(round(5/3.0)) : echo type(0 + string(round(5/3.0))) ``` 注:行末注释可用一个双引号 `"` 开始,但建议用 `|"` 更有适用性。`|` 表示分隔语 句,只是后面一个语句是只有注释的空语句。 ### 类型判断 从 Vim8.0 开始,有一系列 vim 变量专门地用来表示各种变量(值)类型。比如 `v:t_list` 表示列表类型。如果要判断一个变量是否为列表类型,可用以下三种写法中任 何一种(但之前的低版本 Vim 只能用后两种): ```vim if type(var) == v:t_list if type(var) == 3 if type(var) == type([]) ``` 关于具有选择分支功能的 `:if` 语句,在下一节继续讲解。