为什么这个 goroutine 的行为就像是通过引用调用?
Why does this goroutine behave like it's call by reference?
我正在尝试学习 Go 的基础知识,但在我测试的代码片段中,我对按值调用和按引用调用之间的区别有点困惑。
我试图解决一个coding game puzzle,其中要计算一个井字游戏字段的解决方案。
我正在使用的代码
因为在学围棋,所以想用一个goroutine来测试井字棋的每一个字段,看看这个字段是不是解,然后把指向这个字段的指针放到一个channel里面获得结果的主要方法。我使用的代码如下所示:
package main
import "fmt"
import "os"
var player int = int('O')
var opponent int = int('X')
var empty int = int('.')
type board struct {
fields [][]int
}
func main() {
lines := [3]string {"OO.", "...", "..."}
var b board
b.fillBoard(lines)
fmt.Fprintln(os.Stderr, "input board:")
b.printBoard(true)
resultChannel := make(chan *board)
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
go tryField(b, [2]int{i, j}, resultChannel) // goroutine call that isn't working as expected
}
}
fmt.Fprintln(os.Stderr, "\nresult:")
for i := 0; i < 9; i++ {
resultBoard := <- resultChannel
if (resultBoard != nil) {
resultBoard.printBoard(false)
return
}
}
// fmt.Fprintln(os.Stderr, "Debug messages...")
fmt.Println("false")// Write answer to stdout
}
func tryField(b board, field [2]int, result chan *board) {
b.printBoard(true)
fmt.Fprintln(os.Stderr, "add O to field: ", field)
fmt.Fprint(os.Stderr, "\n")
if (b.fields[field[0]][field[1]] != empty) {
result <- nil
}
b.fields[field[0]][field[1]] = player
if (b.isWon()) {
result <- &b
} else {
result <- nil
}
}
func (b *board) fillBoard(lines [3]string) {
b.fields = make([][]int, 3)
for i := 0; i < 3; i++ {
b.fields[i] = make([]int, 3)
}
for i, line := range lines {
for j, char := range line {
b.fields[i][j] = int(char)
}
}
}
func (b *board) printBoard(debug bool) {
var stream *os.File
if (debug) {
stream = os.Stderr
} else {
stream = os.Stdout
}
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
fmt.Fprint(stream, string(b.fields[i][j]))
}
fmt.Fprint(stream, "\n")
}
}
func (b *board) isWon() bool {
for i := 0; i < 3; i++ {
rowFull := true
colFull := true
for j := 0; j < 3; j++ {
rowFull = rowFull && b.fields[i][j] == player
colFull = rowFull && b.fields[j][i] == player
}
if (rowFull || colFull) {
return true
}
}
diagonal1Full := true
diagonal2Full := true
for i := 0; i < 3; i++ {
diagonal1Full = diagonal1Full && b.fields[i][i] == player
diagonal2Full = diagonal2Full && b.fields[i][2-i] == player
}
if (diagonal1Full ||diagonal2Full) {
return true
}
return false
}
您可以 运行 它在 go playground。
问题
由于代码段中的最后一个函数被声明为 func tryField(b board, field [2]int, result chan *board)
我假设板 b
是一个独立的副本,每次我调用该方法时,因为它是按值调用的。所以改变这个板不应该影响其他 goroutines 中的其他板。但不幸的是,改变一个 goroutine 中的板确实会影响其他 goroutine 中的板,因为程序的输出如下:
input board:
OO.
...
...
result:
OO.
...
...
add O to field: [1 0]
OO.
O..
...
add O to field: [2 1]
OO.
O..
.O.
如您所见,初始字段在第一行的第一列和第二列有两个 O。向 [1 0] 位置添加一个 O 就像预期的那样工作,但是当向字段 [2 1] 添加一个 O 时,在 [1 0] 处也有一个 O,这是在之前的 goroutine 中添加的,不应该因为它是按值调用的。
问题
为什么我的代码片段中的代码表现得像通过引用调用,尽管该函数不使用指针?
提前致谢!
切片是对数组的引用。在不复制切片的情况下修改切片时,将修改底层数组。因此,指向同一底层数组的所有切片都将看到此更改。
我正在尝试学习 Go 的基础知识,但在我测试的代码片段中,我对按值调用和按引用调用之间的区别有点困惑。
我试图解决一个coding game puzzle,其中要计算一个井字游戏字段的解决方案。
我正在使用的代码
因为在学围棋,所以想用一个goroutine来测试井字棋的每一个字段,看看这个字段是不是解,然后把指向这个字段的指针放到一个channel里面获得结果的主要方法。我使用的代码如下所示:
package main
import "fmt"
import "os"
var player int = int('O')
var opponent int = int('X')
var empty int = int('.')
type board struct {
fields [][]int
}
func main() {
lines := [3]string {"OO.", "...", "..."}
var b board
b.fillBoard(lines)
fmt.Fprintln(os.Stderr, "input board:")
b.printBoard(true)
resultChannel := make(chan *board)
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
go tryField(b, [2]int{i, j}, resultChannel) // goroutine call that isn't working as expected
}
}
fmt.Fprintln(os.Stderr, "\nresult:")
for i := 0; i < 9; i++ {
resultBoard := <- resultChannel
if (resultBoard != nil) {
resultBoard.printBoard(false)
return
}
}
// fmt.Fprintln(os.Stderr, "Debug messages...")
fmt.Println("false")// Write answer to stdout
}
func tryField(b board, field [2]int, result chan *board) {
b.printBoard(true)
fmt.Fprintln(os.Stderr, "add O to field: ", field)
fmt.Fprint(os.Stderr, "\n")
if (b.fields[field[0]][field[1]] != empty) {
result <- nil
}
b.fields[field[0]][field[1]] = player
if (b.isWon()) {
result <- &b
} else {
result <- nil
}
}
func (b *board) fillBoard(lines [3]string) {
b.fields = make([][]int, 3)
for i := 0; i < 3; i++ {
b.fields[i] = make([]int, 3)
}
for i, line := range lines {
for j, char := range line {
b.fields[i][j] = int(char)
}
}
}
func (b *board) printBoard(debug bool) {
var stream *os.File
if (debug) {
stream = os.Stderr
} else {
stream = os.Stdout
}
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
fmt.Fprint(stream, string(b.fields[i][j]))
}
fmt.Fprint(stream, "\n")
}
}
func (b *board) isWon() bool {
for i := 0; i < 3; i++ {
rowFull := true
colFull := true
for j := 0; j < 3; j++ {
rowFull = rowFull && b.fields[i][j] == player
colFull = rowFull && b.fields[j][i] == player
}
if (rowFull || colFull) {
return true
}
}
diagonal1Full := true
diagonal2Full := true
for i := 0; i < 3; i++ {
diagonal1Full = diagonal1Full && b.fields[i][i] == player
diagonal2Full = diagonal2Full && b.fields[i][2-i] == player
}
if (diagonal1Full ||diagonal2Full) {
return true
}
return false
}
您可以 运行 它在 go playground。
问题
由于代码段中的最后一个函数被声明为 func tryField(b board, field [2]int, result chan *board)
我假设板 b
是一个独立的副本,每次我调用该方法时,因为它是按值调用的。所以改变这个板不应该影响其他 goroutines 中的其他板。但不幸的是,改变一个 goroutine 中的板确实会影响其他 goroutine 中的板,因为程序的输出如下:
input board:
OO.
...
...result:
OO.
...
...
add O to field: [1 0]OO.
O..
...
add O to field: [2 1]OO.
O..
.O.
如您所见,初始字段在第一行的第一列和第二列有两个 O。向 [1 0] 位置添加一个 O 就像预期的那样工作,但是当向字段 [2 1] 添加一个 O 时,在 [1 0] 处也有一个 O,这是在之前的 goroutine 中添加的,不应该因为它是按值调用的。
问题
为什么我的代码片段中的代码表现得像通过引用调用,尽管该函数不使用指针?
提前致谢!
切片是对数组的引用。在不复制切片的情况下修改切片时,将修改底层数组。因此,指向同一底层数组的所有切片都将看到此更改。