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
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` 查看表达式语法表,其中也基本是按运算符优先级
|
||
|
从低到高排列的,请经常查阅。
|
||
|
|
||
|
虽然由于运算符优先级会引起一些自己意想不到的问题,但回避这类问题的办法也是很简
|
||
|
单的,这里是一些建议:
|
||
|
|
||
|
* 首先按自己的理解去使用运算符,要相信大部分语言的设计都是人性化的,不会故意设
|
||
|
些奇怪的违反常理的规则。
|
||
|
* 对于自己不确定优先级,或者发现运算结果不符合自己所想时,添加小括号组合,使表
|
||
|
达式运算的次序明确化。
|
||
|
* 拆分复杂表达式,借助中间变量,写成多行语句,不要写过长的语句。
|