V 的函数默认是不可变的,即使传递的是引用,要传递可变参数必须标记。
由于 V 没有全局变量,这意味着函数的返回值仅是其参数的函数,并且它们的执行没有影响(除非函数使用 I/O)。
注意:V 并不是一种纯粹的函数式语言。
编译器有一项叫 -enable-globals
的 flag 来允许全局变量,但这只是为底层的内核、驱动开发准备的。
可以用关键字 mut
修饰函数参数:
struct User {
name string
mut:
is_registered bool
}
fn (mut u User) register() {
u.is_registered = true
}
mut user := User{}
println(user.is_registered) // "false"
user.register()
println(user.is_registered) // "true"
上面的例子里,接收者被明确标记为了可变的,所以 register()
可以改变 user 对象。这样的操作对非接收者参数也起作用:
fn multiply_by_2(mut arr []int) {
for i in 0 .. arr.len {
arr[i] *= 2
}
}
mut nums := [1, 2, 3]
multiply_by_2(mut nums)
println(nums)
// "[2, 4, 6]"
注意:你调用时也必须在 nums
前面加上 mut
标记,这会很清晰地展示该函数会改变值。
与直接修改值相比,更推荐返回值,比如更推荐写 user = register(user)
或 user.register()
而不是 register(mut user)
。修改参数只能在程序的关键性能部分进行,从而减少内存的分配与复制。
也是出于这个原因,V 不允许修改默认类型的参数(比如整型)。只有诸如数组或哈希表这样的复杂类型才能被修改。
V 可以轻松返回一个修改后的对象:
struct User {
name string
age int
is_registered bool
}
fn register(u User) User {
return User{
...u
is_registered: true
}
}
mut user := User{
name: 'abc'
age: 23
}
user = register(user)
println(user)
fn sum(a ...int) int {
mut total := 0
for x in a {
total += x
}
return total
}
println(sum()) // 0
println(sum(1)) // 1
println(sum(2, 3)) // 5
// using array decomposition
a := [2, 3, 4]
println(sum(...a)) // <-- using prefix ... here. output: 9
b := [5, 6, 7]
println(sum(...b)) // output: 18
fn sqr(n int) int {
return n * n
}
fn cube(n int) int {
return n * n * n
}
fn run(value int, op fn (int) int) int {
return op(value)
}
fn main() {
// Functions can be passed to other functions
println(run(5, sqr)) // "25"
// Anonymous functions can be declared inside other functions:
double_fn := fn (n int) int {
return n + n
}
println(run(5, double_fn)) // "10"
// Functions can be passed around without assigning them to variables:
res := run(5, fn (n int) int {
return n + n
})
println(res) // "10"
// You can even have an array/map of functions:
fns := [sqr, cube]
println(fns[0](10)) // "100"
fns_map := {
'sqr': sqr
'cube': cube
}
println(fns_map['cube'](2)) // "8"
}
- 函数可以传给其他函数
- 匿名函数可以在其他函数中声明
- 函数可以在不分配入参变量的情况下传递
- 你甚至可以构造函数数组/函数哈希表
V 也支持闭包。 匿名函数可以从创建它们的域内继承变量。不过它们必须通过明确地列出所有继承的变量来这样做。
my_int := 1
my_closure := fn [my_int] () {
println(my_int)
}
my_closure() // prints 1
继承的变量会在匿名函数创建的时候复制一份。也就是说,如果原变量在匿名函数创建之后被修改了,这样的修改不会反应到匿名函数中,匿名函数执行时仍然拿着创建时的值执行。
mut i := 1
func := fn [i] () int {
return i
}
println(func() == 1) // true
i = 123
println(func() == 1) // still true
但是,变量可以在匿名函数里修改。尽管修改不会反映在原变量上,但会在后面继续调用时保存下来。
fn new_counter() fn () int {
mut i := 0
return fn [mut i] () int {
i++
return i
}
}
c := new_counter()
println(c()) // 1
println(c()) // 2
println(c()) // 3
如果你需要修改原变量,使用引用。
**警告!**你需要保证引用总是有效,否则会导致未定义的行为。
mut i := 0
mut ref := &i
print_counter := fn [ref] () {
println(*ref)
}
print_counter() // 0
i = 10
print_counter() // 10
struct Foo {}
fn (foo Foo) bar_method() {
// ...
}
fn bar_function(foo Foo) {
// ...
}
如果一个函数的参数时不可变的(比如上面例子里的 foo
),传递值或者引用进去都可以。编译器会自己判断,开发者不需要想。
你不再需要去记住你在传递结构体时应该传值还是传引用。
当然你也可以通过加 &
保证总是以引用的方式传入结构体:
struct Foo {
abc int
}
fn (foo &Foo) bar() {
println(foo.abc)
}
上面代码中 foo
仍然不可变,无法被修改。想要修改必须使用 (mut foo Foo)
。
通常来说,V 的引用与 Go 指针还有 C++ 引用类似。比如通用树结构按如下定义:
struct Node<T> {
val T
left &Node<T>
right &Node<T>
}
想要取消引用直接用值,则像 C 一样用 *
符号即可。