对于Swift的值类型而言,当程序进行变量赋值、参数传递时,函数内部对值类型变量进行了拷贝。也就是说在函数体内无论对参数进行哪些修改,参数的本身都不会产生变化。
所以,假如你需要通过函数直接交换两个变量的值,通常会这么做:
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let tempA = a
a = b
b = tempA
}
var a = 10, b = -10
swapTwoInts(&a, &b)
print(a,b) // a=-10 b=10
我们之所以需要添加inout关键字,就是因为传入函数的变量是值类型,那么假如我传入的变量是个引用类型不就好了吗?
class Data {
var a = 0
var b = 0
init(a: Int, b: Int) {
self.a = a
self.b = b
}
}
func swap(data: Data) {
swapTwoInts(&data.a, &data.b)
}
var d = Data(a: 6, b: 9)
swap(data: d)
上面的代码同样可以交换a,b的值,因为对于Swift的引用类型,程序传递的是引用(指针),因此在函数体中对参数的修改会同时影响参数本身。
也就是说,上面Data(a: 6, b: 9)
对象的实例d
和函数中形参data
引用的是同一个对象。因此当函数执行,交换形参中a,b两个成员变量的值时,主程序中d
对象对应的a,b也改变了。
继续思考下面这段代码:
func change(data: inout Data) {
data = Data(a: 0, b: 1)
}
假如在上述函数中给data添加inout关键字修饰,然后在函数体创建另一个Data对象,会发生什么?
答案显而易见,主程序中的d
实例被改变了,这一点可以通过print(Unmanaged.passUnretained(d).toOpaque())
打印变量的内存地址来验证。
那么,假如程序做如下修改,会发生什么:
func swap(data: inout Data!) {
data = nil
swapTwoInts(&data.a, &data.b)
}
var d: Data? = Data(a: 6, b: 9)
swap(data: &d)
答案是在swap(data: &d)
这一行报错。因为主程序中的对象也会被修改为nil,导致程序运行时崩溃。
最后,如果你用过早期的Swift版本,可能会见过变量形参这个东西,也就是在形参前使用var
关键字修饰。那么如果代码改成下面这样,又会是什么结果呢?
func swap(var data: Data) {
data = nil
swapTwoInts(&data.a, &data.b)
}
这段代码的运行是没有任何问题的,函数内部的data被赋值为nil,等于切断了data对堆中内容的指向,不形象外部变量。
但是这种写法很容易误解,所以在之后的Swift版本中,移除了变量形参这个概念,其实他实际只是在函数内部创建一个新的变量而已。
// 变量形参的实质
func swap(data: Data) {
var newData = data
data = nil
swapTwoInts(& newData.a, & newData.b)
}
你可能会疑问,引用类型传递到函数内,形参data和外部变量不是指向同一个对象吗?那么data=nil
时为什么外部变量不发生变化呢?
答:其实,方法内部data=nil
只是将data变量的指针又原来的内存地址指向了nil,跟外部变量根本没关系。如果想要影响,则要添加inout
关键字。
总结:
上面的内容其实没什么营养,我最早看到这个问题的时候觉得很有意思,因为涉及了值类型和引用类型的内存分布、In-Out参数等几个容易出错的知识点。但是写到最后好像并没有把我最初想表达的东西表现出来。
其实此类问题可以引申的方向有很多,OC中block和基本数据类型、对象类型的关系,还有Swift中block的捕获等。以后有时间再整理吧。嘻嘻
网友评论