在 golang 中用 'a, b = b, a' 交换两个整数是否安全?

Is it safe to swap two integers by `a, b = b, a` in golang?

提问人:xmllmx 提问时间:6/4/2023 更新时间:6/6/2023 访问量:119

问:

package main

import "fmt"

func main() {
    a := 1
    b := 2

    fmt.Printf("Before Swap: %v %v\n", a, b)
    a, b = b, a
    fmt.Printf(" After Swap: %v %v\n", a, b)
}

输出为:

Before Swap: 1 2  
 After Swap: 2 1

看起来不错。我只是想知道:

在 golang 中交换两个整数是否安全a, b = b, a

GO 标准 语义 习语 正确性

评论

11赞 Paul Hankin 6/4/2023
是的,语言规范可用且易于阅读:go.dev/ref/spec#Assignment_statements
1赞 JimB 6/5/2023
为什么不安全?在使用编译器允许的合法赋值时,您担心什么样的情况?

答:

0赞 Bhojendra Rauniyar 6/4/2023 #1

是的,它是。当您具有相同的变量类型时,您可以滑动它们。

i := []int{1, 2, 3, 4}
i[0], i[1], i[2], i[3] = i[3], i[2], i[1], i[0]
// now, i is: []int{4, 3, 2, 1}
// btw, you could also range over slice and swipe them

文档

在赋值中,每个值必须可赋值给分配给它的操作数类型,但存在以下特殊情况:

  1. 任何类型化值都可以分配给空白标识符。
  2. 如果将非类型化常量分配给接口类型的变量或空白标识符,则该常量将首先隐式转换为默认类型
  3. 如果将非类型化的布尔值分配给接口类型的变量或空白标识符,则首先将其隐式转换为类型 bool。

评论

0赞 cuonglm 6/6/2023
我不确定你的引述是否解释了为什么在赋值中交换两个变量是安全的。正确的引文应该是描述评估和分配顺序的段落。
2赞 cuonglm 6/6/2023 #2

是的,使用以下命令交换两个变量是安全的:

a, b = b, a

分配分两个步骤进行:

  • 首先评估作业的右侧。
  • 然后,评估值将按从左到右的顺序分配给左侧。

换句话说,将大致重写为:a, b = b, a

tmpB := b
tmpA := a
a = tmpB
b = tmpA

这就是为什么作业是安全的。


事实上,编译器足够聪明,可以避免第二个临时变量,因此将被重写为:a, b = b, a

tmpA := a
a = b
b = tmpA
$ cat x.go 
package p

func f(a, b int) {
    a, b = b, a
}
$ go tool compile -W x.go

before walk f
.   AS2 tc(1) # x.go:4:7
.   AS2-Lhs
.   .   NAME-p.a esc(no) Class:PPARAM Offset:0 OnStack Used int tc(1) # x.go:3:8
.   .   NAME-p.b esc(no) Class:PPARAM Offset:0 OnStack Used int tc(1) # x.go:3:11
.   AS2-Rhs
.   .   NAME-p.b esc(no) Class:PPARAM Offset:0 OnStack Used int tc(1) # x.go:3:11
.   .   NAME-p.a esc(no) Class:PPARAM Offset:0 OnStack Used int tc(1) # x.go:3:8
after walk f
.   BLOCK # x.go:4:7
.   BLOCK-List
.   .   AS tc(1) # x.go:4:7
.   .   .   NAME-p..autotmp_2 esc(N) Class:PAUTO Offset:0 AutoTemp OnStack Used int tc(1) # x.go:4:7
.   .   .   NAME-p.a esc(no) Class:PPARAM Offset:0 OnStack Used int tc(1) # x.go:3:8
.   .   AS tc(1) # x.go:4:7
.   .   .   NAME-p.a esc(no) Class:PPARAM Offset:0 OnStack Used int tc(1) # x.go:3:8
.   .   .   NAME-p.b esc(no) Class:PPARAM Offset:0 OnStack Used int tc(1) # x.go:3:11
.   .   AS tc(1) # x.go:4:7
.   .   .   NAME-p.b esc(no) Class:PPARAM Offset:0 OnStack Used int tc(1) # x.go:3:11
.   .   .   NAME-p..autotmp_2 esc(N) Class:PAUTO Offset:0 AutoTemp OnStack Used int tc(1) # x.go:4:7