输入输出参数

输入输出参数

输入输出参数(In-Out Parameters)

函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。这意味着你不能错误地更改参数值。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数(In-Out Parameters)。

定义一个输入输出参数时,在参数定义前加 inout 关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。想获取更多的关于输入输出参数的细节和相关的编译器优化,请查看输入输出参数一节。

你只能传递变量给输入输出参数。你不能传入常量或者字面量(literal value),因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数名前加&符,表示这个值可以被函数修改。

注意
输入输出参数不能有默认值,而且可变参数不能用 inout 标记。
下面是例子,swapTwoInts(_:_:) 函数,有两个分别叫做 a 和 b 的输入输出参数:

1
2
3
4
5
func swapTwoInts(inout a: Int, inout _ b: Int) {
let temporaryA = a
a = b
b = temporaryA
}

这个 swapTwoInts(_:_:) 函数简单地交换 a 与 b 的值。该函数先将 a 的值存到一个临时常量 temporaryA 中,然后将 b 的值赋给 a,最后将 temporaryA 赋值给 b。

你可以用两个 Int 型的变量来调用 swapTwoInts(_:_:)。需要注意的是,someInt 和 anotherInt 在传入 swapTwoInts(_:_:) 函数前,都加了 & 的前缀:

1
2
3
4
5
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// prints "someInt is now 107, and anotherInt is now 3"

从上面这个例子中,我们可以看到 someInt 和 anotherInt 的原始值在 swapTwoInts(_:_:) 函数中被修改,尽管它们的定义在函数体外。

注意
输入输出参数和返回值是不一样的。上面的 swapTwoInts 函数并没有定义任何返回值,但仍然修改了 someInt 和 anotherInt 的值。输入输出参数是函数对函数体外产生影响的另一种方式。

应用场景, 两个变量交换值
1
2
3
4
5
func swapTwoInts(inout a: Int, inout _ b: Int) {
let temporaryA = a
a = b
b = temporaryA
}

泛型所解决的问题

下面是一个标准的非泛型函数 swapTwoInts(_:_:),用来交换两个 Int 值:

1
2
3
4
5
func swapTwoInts(inout a: Int, inout _ b: Int) {
let temporaryA = a
a = b
b = temporaryA
}

这个函数使用输入输出参数(inout)来交换 a 和 b 的值,请参考输入输出参数。

swapTwoInts(_:_:) 函数交换 b 的原始值到 a,并交换 a 的原始值到 b。你可以调用这个函数交换两个 Int 变量的值:

1
2
3
4
5
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// 打印 “someInt is now 107, and anotherInt is now 3”

诚然,swapTwoInts(_:_:) 函数挺有用,但是它只能交换 Int 值,如果你想要交换两个 String 值或者 Double值,就不得不写更多的函数,例如 swapTwoStrings(_:_:)swapTwoDoubles(_:_:),如下所示:

1
2
3
4
5
6
7
8
9
10
11
func swapTwoStrings(inout a: String, inout _ b: String) {
let temporaryA = a
a = b
b = temporaryA
}

func swapTwoDoubles(inout a: Double, inout _ b: Double) {
let temporaryA = a
a = b
b = temporaryA
}

你可能注意到 swapTwoInts(_:_:)swapTwoStrings(_:_:)swapTwoDoubles(_:_:) 的函数功能都是相同的,唯一不同之处就在于传入的变量类型不同,分别是 Int、String 和 Double。

在实际应用中,通常需要一个更实用更灵活的函数来交换两个任意类型的值,幸运的是,泛型代码帮你解决了这种问题。(这些函数的泛型版本已经在下面定义好了。)

注意
在上面三个函数中,a 和 b 类型相同。如果 a 和 b 类型不同,那它们俩就不能互换值。Swift 是类型安全的语言所以它不允许一个 String 类型的变量和一个 Double 类型的变量互换值。试图这样做将导致编译错误。


泛型函数

泛型函数可以适用于任何类型,下面的 swapTwoValues(_:_:) 函数是上面三个函数的泛型版本:

1
2
3
4
5
func swapTwoValues<T>(inout a: T, inout _ b: T) {
let temporaryA = a
a = b
b = temporaryA
}

swapTwoValues(_:_:) 的函数主体和 swapTwoInts(_:_:) 函数是一样的,它们只在第一行有点不同,如下所示:

1
2
func swapTwoInts(inout a: Int, inout _ b: Int)
func swapTwoValues<T>(inout a: T, inout _ b: T)

这个函数的泛型版本使用了占位类型名(在这里用字母 T 来表示)来代替实际类型名(例如 Int、String 或 Double)。占位类型名没有指明 T 必须是什么类型,但是它指明了 a 和 b 必须是同一类型 T,无论 T 代表什么类型。只有 swapTwoValues(_:_:) 函数在调用时,才能根据所传入的实际类型决定 T 所代表的类型。

另外一个不同之处在于这个泛型函数名(swapTwoValues(_:_:))后面跟着占位类型名(T),并用尖括号括起来(<T>)。这个尖括号告诉 Swift 那个 T 是 swapTwoValues(_:_:) 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 T 的实际类型。

swapTwoValues(_:_:) 函数现在可以像 swapTwoInts(_:_:) 那样调用,不同的是它能接受两个任意类型的值,条件是这两个值有着相同的类型。swapTwoValues(_:_:) 函数被调用时,T 所代表的类型都会由传入的值的类型推断出来。

在下面的两个例子中,T 分别代表 Int 和 String:

1
2
3
4
5
6
7
8
9
var someInt = 3
var anotherInt = 107
swapTwoValues(&someInt, &anotherInt)
// someInt is now 107, and anotherInt is now 3

var someString = "hello"
var anotherString = "world"
swapTwoValues(&someString, &anotherString)
// someString is now "world", and anotherString is now "hello"

注意
上面定义的 swapTwoValues(_:_:) 函数是受 swap(_:_:) 函数启发而实现的。后者存在于 Swift 标准库,你可以在你的应用程序中使用它。如果你在代码中需要类似 swapTwoValues(_:_:) 函数的功能,你可以使用已存在的 swap(_:_:) 函数。