You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

248 lines
12 KiB

2 years ago
# 第二章 VimL 语言基本语法
## 2.2 选择与比较
vim 在执行脚本时,一般是按顺序逐条语句执行的。但如果只能像流水帐地顺序执行,未
免太无趣了,功能也弱爆了。本节介绍顺序结构之外的最普遍的选择分支结构,它可以根
据某种条件有选择地执行或不执行语句块(一条或多条语句)。
在 VimL 中通过 `:if` 命令来表示条件选择,其基本语法结构是:
```vim
: if {expr}
: " todo
: endif
```
如果满足表达式 `{expr}` ,或说其值为“真”,则执行其后至 `:endif` 之间的语句。貌
似突然迸进了许多新概念,得先理一理。
### 表达式与语句
什么叫表达式?这可难说了。我只能先描叙下在 VimL 中什么是与什么不是表达式:
* 单独的变量就是表达式,常量也是表达式,选项值(`&option`)也是,但选项本身不是;
* 函数调用是表达式;
* 表达式有值,表达式之间的合法运算的结果也还是表达式。
* 但表达式不是可执行语句,它只是语句的一部分。
至于语句,在第一章也讲过。VimL 语句就是 Vim 的 ex 命令行。笼统地说,有时说到
`ex 命令` 是指整个命令行,不过狭义地说,是指它第一个单词所指代的关键命令。于是
,VimL 语言的大部分语句,可认为遵循以下范式:
```
VimL 语句 = ex 命令 + 表达式
```
为什么说大部分呢?因为我们已经很熟悉的赋值语句如 `:let i=1` 就不完全适合。在这
里,`:let` 是个命令,`1` 是个表达式。但 `=` 只是依附于 `:let` 命令的特殊语义符
号,它不是个表达式,也不是个运算符。变量 `i` 在被创建之前,也还算不上表达式。
`i=1` 写在一起,或为了增加可读性加些空白 `i = 1`,它也不是表达式,因为它没
有值,(并不能像 C 语言那样使用连等号赋值),下面这两个语句是非法的:
```vim
: let i = j = 1
: let i = (j = 1)
```
在 VimL 中的常用语句中,除了这个基础得有点平淡无奇的赋值语句,其他大多是
`命令 + 表达式` 范式的。比如已经大量使用的 `:echo` 语句,以及上节介绍过的给列
表添加一个元素的函数调用语句 `:call add(list, item)`
然而,其实也不必太拘泥于这些概念名词。理解就好。我们归纳出概念也不外是为了更好地
理解。
### 逻辑值与条件表达式
`:if` 命令(以及下一节要介绍的 `:while`命令)后面的表达式,就是一个条件表达式
。它期望这个表达式的值的类型是逻辑值,即 `type()` 的结果是 `v:t_bool(=6)` 的值
。如果值的类型不是逻辑值,则会自动将其他值转换为一个逻辑值。逻辑类型只有唯二的
两个值,`v:true` 表示真,`v:false` 表示假。
所以关键在于 VimL 如何判定其他值是否有真假,什么是真,什么是假?其转换规则如何?
这直接写代码测试一下吧:
```vim
: if 1 | echo v:true | endif
: if 0 | echo v:true | endif
: if -1 | echo v:true | endif
: if '0' | echo v:true | endif
: if '1' | echo v:true | endif
: if '' | echo v:true | endif
: if 'a' | echo v:true | endif
: if 1.23 | echo v:true | endif |" 非法用法
: if 0.23 | echo v:true | endif |" 非法用法
: if '0.23' | echo v:true | endif
: if '1.23' | echo v:true | endif
" 以下四条也都是非法用法
: if [1, 2, 3] | echo v:true | endif
: if [] | echo v:true | endif
: if {'x':1, 'y':2} | echo v:true | endif
: if {} | echo v:true | endif
```
注:由于语句比较简单,就将 `:if``:endif` 直接写在一行了,用 `|` 分隔子语句
。正常代码建议写在不同行上且缩进布局。
结果归纳于下:
* 数字 `0` 为假,其他正数或负数为真;
* 字符串先自动转为数字,转为 `0` 的话认为假,能转为其他数字认为真;
* 浮点数不能转为逻辑值,无法判断真假;
* 列表与字典也不能直接判断真假。
其实可进一步归纳为一句话,在 VimL 中,只能对整数值判断真假,`0` 是假的,其他都
是真的,字符串先自动转为数字再判断真假。其他类型的值不能直接判断真假。(至
vim8.0 版本是此规则,后面是否会改就不得而知了)
然而,我们还是经常需要判断其他类型的值的某种状态。这时可以利用一个内建函数
`empty()` 来帮忙。它可以接收任何类型的一个参数,如果它是“空”的,就返回真(
`v:true`),否则返回假('v:false')。在很大程度上,它可以代替直接使用 `:if`
条件表达式,只不过在值上恰好是逻辑取反;优点则是写法统一,适用于所有类型。
在上面这个例子中,可以都再次尝试把 `:if` 后面的表达式作为 `empty()` 的参数执
行看看结果,或用 `!empty()` 取反判断,如:
```vim
: if empty('0.23') | echo v:true | endif
: if !empty('a') | echo v:true | endif
```
综合建议:用 `:if !empty({expr})` 代替 `:if {expr}`,避免逻辑烧脑,并且大部分
情况下应该是你想要的。
### 比较运算符
两个整数进行相等性的比较,或大小性的比较,结果返回一个或真或假的逻辑值。整数支持
的比较运算符包括:`==`, `!=`,`>`,`>=`,`<`,`<=`。
浮点数支持与整数相同的比较运算,但由于浮点误差,不建议用相等性判断。
字符串也支持与整数相同的那六个比较运算。虽然整数在直接 `:if` 命令中自动转为数
字处理,但在比较运算中表现良好,就是按正常的编码序逐字符比较。不过有一点特别要
注意的是,字符串比较结果受选项 `&ignorecase` 的影响,即有可能按忽略大小写的方
式来比较字符串。比如,观察一下如下结果吧:
```vim
: set ignorecase
: echo 'abc' == 'ABC'
: set noignorecase
: echo 'abc' == 'ABC'
```
因此,为了使比较结果不受用户个人的 `vimrc` 配置 `&ignorecase` 的影响,VimL 另
外提供两套限定大小写规则的比较运算符。在以上比较运算符之后再加 `#` 符号就表示
强制按大小写敏感方式比较,后面加上 `?` 符号就表示强制按大小写不敏感的方式比较
。比如:
```vim
: echo 'abc' ==# 'ABC'
: echo 'abc' ==? 'ABC'
```
所以,强烈建议在进行字符串比较时,只用 `==#``==?` 系的比较运算符。当然由于
弱类型,字符串变量与数字变量其实是不可分的,所以将 `==#``==?` 之类的运用于
数字上比较,也是完全没有关系的。
此外,字符串除了相等性比较,还有匹配性比较,即用 `=~` `!~` 运算符判断一个字符
串是否匹配另一个作为正则表达式的字符串。正则表达式是另一个高级话题,这里不再展
开。当然,匹配运算符也有限定大小写是否敏感的衍生运算符,而且一般建议用 `=~#`
`!~#` 匹配,毕竟正则表达本身有表达大小写的能力。
对于列表与字典变量,可进行相等性比较,但不能进行大小性比较。如果两个列表或字典
的对应元素都相等,则认为它们相等。此外,列表与字典还另外有个同例性比较运算符,
`is``isnot`。注意,这两个是类似 `==` 的运算符号,不是关键词,虽然它们用英
文单词来表示。同样地,也有 `is#``isnot?` 的衍生运算,不过这主要为了语法的
统一整齐,其实 `is#` `is?``is` 的结果是一致的。同例性比较的具体含义涉及实
例引用的概念,这留待后面的章节继续展开。
### 逻辑运算符
在 VimL 中的逻辑值所支持的或、且、非运算并无意外,分别用符号 `||` `&&` `!`
示就是,而且也支持短路计算特性。
*`expr1 || expr2 `, 只要 `expr1``expr2` 其中一个是真,整个表达式就是真
,两个都是假才是假。如果 `expr1` 已经是真的,`expr2` 不必计算就直接获得真的
结果。
*`expr1 && expr2`,只有两个表达式都是真,结果才是真。如果 `expr1` 是假,则
不必计算 `expr2` 就返回结果假。
*`!expr` ,对表达式真假取反。
### if 分支流程
在了解这些逻辑值判断之后,理解 `:if` 的选择分支语句就容易多了,其完整语法结构
如下:
```vim
: if {expr}
: {block_if}
: elseif {expr}
: {block_elseif}
: elseif {expr}
: {block_elseif}
: ......
: else
: {block_else}
: endif
```
* 首先执行的是 `:if` 后面的 `{expr}` 表达式,它可能只是个简单表达式,也可能是
多个逻辑值的复合运算,或者是很多表达式运算后得到的一个数字结果或逻辑值。只要
它最终能被解释为真,就执行其后的 `{block_if}` 语句块。
* 如果 `:if` 的表达式为假,则依次寻找下一个表达式为真的 `:elseif` 语句块。
* 最后如果没有真的 `:if``:elseif` 条件满足,就执行 `:else` 语句块。
* 只有 `:if``:endif` 关键命令是必须的,`:elseif` 与 `:else` 及其语句块是可
选的。
* 在任一条件下,最多只有一个语句块被执行,然后流程跳转到 `:endif` 之后,结束整
个选择分支流程。
* 如果没有 `:else` 语句块,则在没有任何一个条件满足时,就不会执行任何一个语句块
。在有 `:else` 时,则至少会执行一个语句块。
注意:`elseif` 是直接将 `else``if` 这两个单词拼在一起的,中间没有空格,也
没有缩写。在许多不同的语言中,`else if` 的写法可能是变化最多的。
在 VimL 中,目前也没有 `switch case` 的类似语句,如果要实现多分支,只能叠加
`:elseif`
在非常简单的 `if else endif` 语句中,也可以用条件表达式 `expr1 : expr2 ? expr3`
这类似于:
```vim
: if expr1
: expr2
: else
: expr3
: endif
```
整个表达式的值是 `expr2``expr3` 的值。至于条件表达式是否可以嵌套,这个我也
不知道,反正我不用,也不建议用。就是条件表达式本身,也只推荐在一些有限的场合用
,不推荐大量使用。因为一开始以为简单的逻辑判断,也可能以后会被修改的复杂起来,
仍然是用 `:if` 清晰一些。
然后推荐 `:if` 的一个特殊技法。VimL 并没有块注释,但是可以把多行语句嵌套放在
`:if 0 ... :endif` 之间,然后其内的语句就完全不会被执行了,甚至有不合 VimL 语
法的行也没事。然而仍然只建议这样“注释”合法的语句行,因为 `:if 0` 的潜意识是在
某个时刻可能需要将其改为 `:if 1` 以重新激活语句。这主要是用于更方便地切换测试
某块语句的运行效果。
```vim
: if 0
: 这里被注释了
: endif
: echo 'done'
```
### \*运算符优先级
本节为讲叙选择分支语句,也引申讲了不少有关语句、表达式、运算符的相关问题。落到
实处就是各种运算符的使用了,这就需要特别注意运算符的优先级问题。在此并不打算罗
列 VimL 的运算符优先级表,因为到这里可能还有些内容未覆盖到。而且运算符优先级的
问题太过琐碎,只看一遍教程并无多大助益,需要经常查文档,并自行验证。可以通过这
个命令 `:help expression-syntax` 查看表达式语法表,其中也基本是按运算符优先级
从低到高排列的,请经常查阅。
虽然由于运算符优先级会引起一些自己意想不到的问题,但回避这类问题的办法也是很简
单的,这里是一些建议:
* 首先按自己的理解去使用运算符,要相信大部分语言的设计都是人性化的,不会故意设
些奇怪的违反常理的规则。
* 对于自己不确定优先级,或者发现运算结果不符合自己所想时,添加小括号组合,使表
达式运算的次序明确化。
* 拆分复杂表达式,借助中间变量,写成多行语句,不要写过长的语句。