‹ 回到主页  |  查看 Markdown 源码  |  邮件勘误

解构赋值的陷阱

有解构赋值的语言都支持一种诡异的写法来交换两个变量的值:

a, b = b, a

其实, 这里有一个隐晦的小问题. 考虑下边这段 Swift 代码:

(a, b) = (c, d)

它的访问顺序是 cdab. 在支持带副作用 (side effect) 的计算属性 (computed property) 的 Swift 中, 保证这个顺序是非常重要的.

我不知道你有没有发现这个问题, 编译器不能直接把 c 赋给 a, 再把 d 赋给 b, 否则访问顺序就变成 cadb 了. 唯一可行的办法是创建两个临时变量 ef, 将 c 放进 e, d 放进 f, 再将 e 放进 a, f 放进 b, 访问顺序是 cedfeafb. 删掉其中的 ef 后, 才刚好符合要求.

不幸的是, 编译器常常没法优化掉这里的额外开销. 如果你肯用传统的办法交换对象, 或使用标准库内置的 swap 函数, 你可能只需要一个临时变量, 而不是两个:

let tmp = a
a = b
b = tmp

不要小看这个临时变量的开销喔, 如果你在写一个通用型数据结构, 不知道元素的大小, 还是考虑一下后者这种淳朴的写法叭! 当然, 如果只是普通场景, 怎么写都无所谓啦, 毕竟开心才最重要嘛.

这是 Swift 标准库中 MutableCollectionswapAt 函数的实现:

@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]) 呢? 上边提到的这几种写法之间有什么本质区别?