2.2 选择与比较
2.2 选择与比较
vim 在执行脚本时,一般是按顺序逐条语句执行的。但如果只能像流水帐地顺序执行,未 免太无趣了,功能也弱爆了。本节介绍顺序结构之外的最普遍的选择分支结构,它可以根 据某种条件有选择地执行或不执行语句块(一条或多条语句)。
在 VimL 中通过 :if
命令来表示条件选择,其基本语法结构是:
: 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 语言那样使用连等号赋值),下面这两个语句是非法的:
: 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 如何判定其他值是否有真假,什么是真,什么是假?其转换规则如何? 这直接写代码测试一下吧:
: 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()
取反判断,如:
: if empty('0.23') | echo v:true | endif
: if !empty('a') | echo v:true | endif
综合建议:用 :if !empty({expr})
代替 :if {expr}
,避免逻辑烧脑,并且大部分 情况下应该是你想要的。
比较运算符
两个整数进行相等性的比较,或大小性的比较,结果返回一个或真或假的逻辑值。整数支持 的比较运算符包括:==
, !=
,>
,>=
,<
,<=
。
浮点数支持与整数相同的比较运算,但由于浮点误差,不建议用相等性判断。
字符串也支持与整数相同的那六个比较运算。虽然整数在直接 :if
命令中自动转为数 字处理,但在比较运算中表现良好,就是按正常的编码序逐字符比较。不过有一点特别要 注意的是,字符串比较结果受选项 &ignorecase
的影响,即有可能按忽略大小写的方 式来比较字符串。比如,观察一下如下结果吧:
: set ignorecase
: echo 'abc' == 'ABC'
: set noignorecase
: echo 'abc' == 'ABC'
因此,为了使比较结果不受用户个人的 vimrc
配置 &ignorecase
的影响,VimL 另 外提供两套限定大小写规则的比较运算符。在以上比较运算符之后再加 #
符号就表示 强制按大小写敏感方式比较,后面加上 ?
符号就表示强制按大小写不敏感的方式比较 。比如:
: 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
的选择分支语句就容易多了,其完整语法结构 如下:
: 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
, 这类似于:
: if expr1
: expr2
: else
: expr3
: endif
整个表达式的值是 expr2
或 expr3
的值。至于条件表达式是否可以嵌套,这个我也 不知道,反正我不用,也不建议用。就是条件表达式本身,也只推荐在一些有限的场合用 ,不推荐大量使用。因为一开始以为简单的逻辑判断,也可能以后会被修改的复杂起来, 仍然是用 :if
清晰一些。
然后推荐 :if
的一个特殊技法。VimL 并没有块注释,但是可以把多行语句嵌套放在 :if 0 ... :endif
之间,然后其内的语句就完全不会被执行了,甚至有不合 VimL 语 法的行也没事。然而仍然只建议这样“注释”合法的语句行,因为 :if 0
的潜意识是在 某个时刻可能需要将其改为 :if 1
以重新激活语句。这主要是用于更方便地切换测试 某块语句的运行效果。
: if 0
: 这里被注释了
: endif
: echo 'done'
*运算符优先级
本节为讲叙选择分支语句,也引申讲了不少有关语句、表达式、运算符的相关问题。落到 实处就是各种运算符的使用了,这就需要特别注意运算符的优先级问题。在此并不打算罗 列 VimL 的运算符优先级表,因为到这里可能还有些内容未覆盖到。而且运算符优先级的 问题太过琐碎,只看一遍教程并无多大助益,需要经常查文档,并自行验证。可以通过这 个命令 :help expression-syntax
查看表达式语法表,其中也基本是按运算符优先级 从低到高排列的,请经常查阅。
虽然由于运算符优先级会引起一些自己意想不到的问题,但回避这类问题的办法也是很简 单的,这里是一些建议:
- 首先按自己的理解去使用运算符,要相信大部分语言的设计都是人性化的,不会故意设 些奇怪的违反常理的规则。
- 对于自己不确定优先级,或者发现运算结果不符合自己所想时,添加小括号组合,使表 达式运算的次序明确化。
- 拆分复杂表达式,借助中间变量,写成多行语句,不要写过长的语句。