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.
198 lines
8.2 KiB
198 lines
8.2 KiB
2 years ago
|
# 第五章 VimL 函数进阶
|
||
|
|
||
|
在第二章中,我们已经讲叙了基本的函数定义与调用方法,以及一些函数属性的作用。但
|
||
|
正如大多数编程语言一样,函数是如此普遍且重要的元素。因而本章继续讨论一些有关函
|
||
|
数的较为高级的用法。
|
||
|
|
||
|
## 5.1 可变参数
|
||
|
|
||
|
### 可变参数的意义
|
||
|
|
||
|
一般情况下,在定义函数时指定形参,在调用函数时传入实参,且参数个数必须要与定义
|
||
|
时指定的参数数量相等。但在一些情况下,我们将要实现的函数功能,它的参数个数可能
|
||
|
是不确定的,或者有些参数是可选的,可缺省使用默认值。这时,在函数定义中引入可变参数就
|
||
|
非常方便了。相对于可变参数,常规的形参也就是命名参数。
|
||
|
|
||
|
* 在函数头中,用三个点号 `...` 表示可变参数,可变参数必须用于最后一个形参,如
|
||
|
果有其他命名参数,则必须位于 `...` 之前。
|
||
|
* 在函数体中,分别用 `a:1` `a:2` …… 等表示第一个、第二个可变参数。用 `a:0` 表
|
||
|
示可变参数的数量,`a:000` 是由所有可变参数组成的列表变量。
|
||
|
* 命名参数最多允许 20 个,虽然大部分情况也够用了。可变参数的数量没有明确限制。
|
||
|
* 调用函数时,传入的实参数量至少不低于命名参数的数量,但传入的可变参数数量可以
|
||
|
为 0 或多个。当没有传入可变参数时,`a:0` 的值为 0。
|
||
|
|
||
|
需要强调的是,只有定义了 `...` 可变参数,才能在函数体中使用 `a:0` `a:000` `a:1`
|
||
|
等特殊变量。较好的实践是先用 `a:0` 判断可变参数个数,然后视情况使用 `a:1` `a:2`
|
||
|
等每个可变参数。如果只传入一个实参,却使用了 `a:2` 变量,会发生运行时错误。此
|
||
|
外 `a:000` 就当作普通列表变量使用好了,`a:000[0]` 就是 `a:1`,因为列表元素索引
|
||
|
从 0 开始。
|
||
|
|
||
|
例如,可用以下函数展示可变参数的使用方法:
|
||
|
```vim
|
||
|
function! UseVarargin(named, ...)
|
||
|
echo 'named argin: ' . string(a:named)
|
||
|
|
||
|
if a:0 >= 1
|
||
|
echo 'first varargin: ' . string(a:1)
|
||
|
endif
|
||
|
if a:0 >= 2
|
||
|
echo 'second varargin: ' . string(a:2)
|
||
|
endif
|
||
|
|
||
|
echo 'have varargin: ' . a:0
|
||
|
for l:arg in a:000
|
||
|
echo 'iterate varargin: ' . string(l:arg)
|
||
|
endfor
|
||
|
endfunction
|
||
|
```
|
||
|
|
||
|
你可以用 `:call` 调用这个函数,尝试传入不同的参数,观察其输出。可见有两种写法
|
||
|
获取某个可变参数,比如用 `a:1` 或 `a:000[0]`,视业务具体情况用哪种更方便。而且
|
||
|
`a:000` 还可用列表迭代方法获取每个可变参数。
|
||
|
|
||
|
### 不定参数示例
|
||
|
|
||
|
在 2.4 节,我们已经定义了一个演示之用的函数 `Sum` 可计算两个数之和,简化重新
|
||
|
截录于下:
|
||
|
```vim
|
||
|
function! Sum(x, y)
|
||
|
let l:sum = a:x + a:y
|
||
|
return l:sum
|
||
|
endfunction
|
||
|
```
|
||
|
|
||
|
现假设要计算任意个数之和,则可改为如下定义:
|
||
|
```vim
|
||
|
function! Sum(x, y, ...)
|
||
|
let l:sum = a:x + a:y
|
||
|
for l:arg in a:000
|
||
|
let l:sum += l:arg
|
||
|
endfor
|
||
|
return l:sum
|
||
|
endfunction
|
||
|
```
|
||
|
|
||
|
这里认为调用 `Sum()` 时必须提供两个参数,否则求和没有意义。其实也可以定义为
|
||
|
`Sum(...)`,将函数实现中的 `l:sum` 初始化为 0 即可。
|
||
|
|
||
|
若一个函数用 `Fun(...)` 定义,只声明了可变参数,则可用任意个参数调用,非常通用
|
||
|
。然而过于通用也表明意义不明确,良好的实践是,除非有必要,尽可能用命名参数,少
|
||
|
用可变参数。使用合适的参数变量名,函数的可读性增强,使用可变参数时,最好加以注
|
||
|
释;同时也建议在函数前面部分判断可变参数数量与类型,第一时间分别赋于另外的局部
|
||
|
变量,也能增加函数的可读性。
|
||
|
|
||
|
调用这个求和函数时,用 `:call Sum(1, 2, 3, 4)` 方式。事实上,只为这个需求的话
|
||
|
,不必用可变参数,直接用一个列表变量作为参数可能更方便。如改写为:
|
||
|
```vim
|
||
|
function! SumA(args)
|
||
|
let l:sum = 0
|
||
|
for l:arg in a:args
|
||
|
let l:sum += l:arg
|
||
|
endfor
|
||
|
return l:sum
|
||
|
endfunction
|
||
|
```
|
||
|
|
||
|
这个函数的意义是为一个列表变量内所有元素求和,以 `:call Sum([1, 2, 3, 4])` 方
|
||
|
式调用。
|
||
|
|
||
|
然而需要注意的是,并非所有用可变参数的函数,都适合将可变参数改为一个列表变量。
|
||
|
|
||
|
### 默认参数示例
|
||
|
|
||
|
在 VimL 的内置函数中,格式化字符串的 `printf()` 就是接收任意个参数的例子。另外
|
||
|
还有大量内置函数是支持默认参数的,如将列表所有元素连接成一个字符串的 `join()`
|
||
|
。这种情况与不定参数略有不同,它能接收的有效参数个数是确实的,只是在调用时后面
|
||
|
一个或几个参数可以省略不传,不传实参的话就自动采用了某个默认值而已。
|
||
|
|
||
|
比如我们也可以自己实现一个类似的函数 `Join()`:
|
||
|
```vim
|
||
|
function! Join(list, ...)
|
||
|
if a:0 > 0
|
||
|
let l:sep = a:1
|
||
|
else
|
||
|
let l:sep = ','
|
||
|
endif
|
||
|
return join(a:list, l:sep)
|
||
|
endfunction
|
||
|
```
|
||
|
|
||
|
虽然可以(更低效率)用循环连接字符串,但这时为简明说明问题,直接调用内置的
|
||
|
`join()` 完成实际工作了。关键点是提供了另一个逗号作为默认分隔字符,通过 `a:0`
|
||
|
来判断传入的可变参数个数,再给分隔字符赋以合适的初始值。 其实这个 `if` 分
|
||
|
支可以直接用 `get()` 函数代替:`let l:sep = get(a:000, 0, ',')`。 这用起来更为
|
||
|
简洁,不过用 `if` 分支明确写出来,更容易扩充其他逻辑,即使是用 `echo` 打印个简
|
||
|
单的日志。
|
||
|
|
||
|
### 间接调用含可变参数的函数
|
||
|
|
||
|
一般情况下,函数都不是独立完成工作的,往往还需要调用其他的函数。假如一个支持可
|
||
|
变参数的函数内,要调用另一个支持可变参数的函数,给后者传递的参数依赖于前者接收
|
||
|
的不确定的参数,这情况就似乎变得复杂了。
|
||
|
|
||
|
为说明这种应用场景,先参照上述 `Sum()` 函数再定义一个类似的连乘函数:
|
||
|
```vim
|
||
|
function! Prod(x, y, ...)
|
||
|
let l:prod = a:x * a:y
|
||
|
for l:arg in a:000
|
||
|
let l:prod = l:prod * l:arg
|
||
|
endfor
|
||
|
return l:prod
|
||
|
endfunction
|
||
|
```
|
||
|
注:VimL 支持 `+=` 操作符,却不支持 `*=` 操作符,请参阅 `:h +=` 。
|
||
|
|
||
|
然后再定义一个更上层的函数,根据一个参数分发调用连加 `Sum()` 或 连乘 `Prod()`
|
||
|
函数,传入剩余的不定参数:
|
||
|
```vim
|
||
|
function! Calculate(operator, ...)
|
||
|
echo Join(a:000, a:operator)
|
||
|
if a:operator ==# '+'
|
||
|
" let l:result = Sum(...)
|
||
|
" let l:result = Sum(a:000)
|
||
|
elseif a:operator ==# '*'
|
||
|
" let l:result = Prod(...)
|
||
|
" let l:result = Prod(a:000)
|
||
|
endif
|
||
|
return l:result
|
||
|
endfunction
|
||
|
|
||
|
echo Calculate('+', 1, 2, 3, 4)
|
||
|
echo Calculate('*', 1, 2, 3, 4)
|
||
|
```
|
||
|
|
||
|
在这个示例函数中,第一行的 `echo` 语句用于调试打印,不论是用刚才自定义的
|
||
|
`Join()` 或内置的 `join()` 函数都能正常工作。但是在随后的 `if` 分支中,
|
||
|
不论是 `Sum(...)` 还是 `Sum(a:000)` 都不能达到预期效果,虽然它作为“伪代码”
|
||
|
很好地表达了使用意途,所以先将其注释了。
|
||
|
|
||
|
先分析原因,`Sum(...)` 是语法错误。因为 `...` 只能用于函数头表示不定参数,却不
|
||
|
能在函数体中表示接收的所有不定参数。`a:000` 可以表示所有不定参数,但它只是一个
|
||
|
列表变量,调用 `Sum(a:0000)` 时只传了一个参数变量,而原来定义的 `Sum()` 函数要
|
||
|
求至少两个参数,所以也会出错误,因为相当于调用 `Sum([1,2,3,4])` 也是错误的。
|
||
|
|
||
|
解决办法是用 `call()` 函数间接调用,它的第一个参数是一个函数,第二个参数正是一
|
||
|
个列表,这个列表内的所有元素将传入第一个参数所代表的函数进行调用。例如,这语句
|
||
|
`:echo call('Sum', [1,2,3,4])` 能正常工作。于是可将 `Calculate()` 函数改写:
|
||
|
|
||
|
```vim
|
||
|
function! Calculate(operator, ...)
|
||
|
if a:0 < 2
|
||
|
echoerr 'expect at leat 2 operand'
|
||
|
return
|
||
|
endif
|
||
|
|
||
|
echo Join(a:000, a:operator)
|
||
|
if a:operator ==# '+'
|
||
|
let l:result = call('Sum', a:000)
|
||
|
elseif a:operator ==# '*'
|
||
|
let l:result = call('Prod', a:000)
|
||
|
endif
|
||
|
|
||
|
return l:result
|
||
|
endfunction
|
||
|
```
|
||
|
|
||
|
这里再作了另一个优化,先对不定参数个数作了判断,不足 2 个时返回错误。
|
||
|
|