在Swift中,初次接触inout
关键字以及它的用法,可能会让我们想起C/C++中的指针,但实际上Swift中inout
只不过是按值传递,然后再写回原变量,而不是按引用传递:
An in-out parameter has a value that is passed in to the function, is modified by the function, and is passed back out of the function to replace the original value.
这样的好处在于它远比使用引用安全。首先举个最简单的例子看一看inout
关键字怎么用:
func inc(inout i: Int) {
++i
}
var x = 0
inc(&x)
print(x) // 输出结果:“1”
参数x
传入到inc
函数中后,在函数内被修改为1,函数返回时这个值(1)覆盖了原来的x
的值(0),所以x
变成了1。
对比一下另一种同样能在函数内部改变变量值的实现方式——闭包:
func inc() -> () -> Int {
var i = 0 //在inc函数内定义变量i
return { ++i } // 闭包中截获变量i
}
let f = inc()
print(f()) // 输出结果:“1”
print(f()) // 输出结果:“2”
闭包是通过截获外部变量的引用从而实现对变量的修改的,我们通过闭包来证明,inout
参数是按值传递的:
func inc(inout i: Int) -> () -> Int {
return { ++i } // 闭包中截获inout参数i
}
var x = 0
let f = inc(&x)
print(f()) // 输出结果:“1”
print(x) // 输出结果:“0”
如果inout
参数是按引用传递,因为我们知道闭包会按引用截获变量,所以闭包内的++i
语句实际上会影响到我们定义的变量x
,因此最后一个输出的结果应该是1,但实际上运行结果是0。
这说明inout
参数是按值传递的,我们梳理一下整个过程:
- 首先变量
x
的值是0,它作为inout
参数传入inc
方法中,inc
方法内有一个x
的副本,闭包截获了这个副本的引用。 - 随后
inc
方法方法返回,此时的副本值还是0,所以外部的变量x
的值为0。 - 接下来我们调用闭包,副本值被改为1,但是外部的变量
x
的值不会受到任何影响,所以它依然为0。
如果在inc
方法中返回闭包之前就调用这个闭包,那么外部的变量x
的值就会被修改为1,这是因为在函数返回前,副本的值变成了1:
func inc(inout i: Int) -> () -> Int {
let f = { ++i } // 闭包中截获inout参数i
f()
return f
}
var x = 0
let f = inc(&x)
print(x) // 输出结果:“1”
&并不总表示inout
如果在函数声明中,参数是一个UnsafeMutablePointer
的指针,那么传递参数的时候也要加上&
,这和inout
参数看上去用法类似,但实际上这里是按引用传递而不是按值传递。我们可以改写一下之前的inc
方法:
func inc(i: UnsafeMutablePointer<Int>) -> () -> Int {
//函数内存储指针i的副本,闭包截获这个副本
return {
i.memory++
return i.memory
}
}
这个方法的使用与之前类似。有兴趣的读者可以自行尝试。这里我们换一种调用方式,传入inc
方法的参数不是整数地址,而是数组的地址:
let f: () -> Int
do {
var x = [0]
f = inc(&x)
}
print(f())