有解构赋值的语言都支持一种诡异的写法来交换两个变量的值:
a, b = b, a
其实, 这里有一个隐晦的小问题. 考虑下边这段 Swift 代码:
(a, b) = (c, d)
它的访问顺序是 c
→ d
→ a
→
b
. 在支持带副作用 (side effect) 的计算属性 (computed
property) 的 Swift 中, 保证这个顺序是非常重要的.
我不知道你有没有发现这个问题, 编译器不能直接把 c
赋给
a
, 再把 d
赋给 b
,
否则访问顺序就变成 c
→ a
→ d
→
b
了. 唯一可行的办法是创建两个临时变量 e
和
f
, 将 c
放进 e
, d
放进 f
, 再将 e
放进 a
,
f
放进 b
, 访问顺序是 c
→
e
→ d
→ f
→ e
→
a
→ f
→ b
. 删掉其中的
e
和 f
后, 才刚好符合要求.
不幸的是, 编译器常常没法优化掉这里的额外开销.
如果你肯用传统的办法交换对象, 或使用标准库内置的 swap
函数,
你可能只需要一个临时变量, 而不是两个:
let tmp = a
a = b
b = tmp
不要小看这个临时变量的开销喔, 如果你在写一个通用型数据结构, 不知道元素的大小, 还是考虑一下后者这种淳朴的写法叭! 当然, 如果只是普通场景, 怎么写都无所谓啦, 毕竟开心才最重要嘛.
这是 Swift 标准库中 MutableCollection
的
swapAt
函数的实现:
@inlinable
public mutating func swapAt(_ i: Index, _ j: Index) {
guard i != j else { return }
let tmp = self[i]
self[i] = self[j]
self[j] = tmp
}
思考题: 可以用
swap(&self[i], &self[j])
简化 swapAt
函数么? 为什么? 那 (self[i], self[j]) = (self[j], self[i])
呢? 上边提到的这几种写法之间有什么本质区别?