14-Reference & Value Semantics、 Pointers vs Values、 Loop Gotcha ...
課程地址 go-class-slides/xmas-2020 at trunk · matt4biz/go-class-slides (github.com)
主講老師 Matt Holiday
14-Reference & Value Semantics
Pointers vs Values
如果要共用變數並修改,建議統一用指針傳遞的方式,否則 \(f3\) 返回的是原來的副本,\(f4\) 作出的修改將無法反映到 \(f1\)、\(f2\) 修改的對象上。即針對一個對象的修改卻產生了兩個對象。
Loop Gotcha
迴圈內第二個參數拿到的是副本,要在迴圈內修改原切片欄位的值不能直接修改副本,需要通過索引進行修改。
在函數內修改切片最好將切片返回,因為修改切片很可能會導致切片描述符指向的底層數組地址發生改變,比如 \(grow\) 擴容。
將指針指向切片內的元素是極其危險的。當切片描述符指向的底層數組擴容時,會導致指針指向已經過時的底層數組。再通過指針修改元素會導致修改無效。
package main
import "fmt"
func main() {
items := [][2]byte{{1, 2}, {3, 4}, {5, 6}}
a := [][]byte{}
for _, item := range items {
a = append(a, item[:])
}
fmt.Println(items)
fmt.Println(a)
}
[[1 2] [3 4] [5 6]]
[[5 6] [5 6] [5 6]]
因為 \(item\) 是切片元素的副本,所以是兩位元組數組,在記憶體中有特定位置,每次迴圈獲得到的副本都在記憶體的同一個地方。當迴圈結束後,最後兩個位元組數組是 \(5、6\) ,而向 \(a\) 添加的是三個相同的 \(item\) 引用,所以都將引用 \(item\) 的最終值。修複這種方法的方法是在每次迴圈內部聲明一個新變數。
func main() {
items := [][2]byte{{1, 2}, {3, 4}, {5, 6}}
a := [][]byte{}
for _, item := range items {
i := make([]byte, len(item))
copy(i, item[:])
a = append(a, i)
}
fmt.Println(items)
fmt.Println(a)
}
[[1 2] [3 4] [5 6]]
[[1 2] [3 4] [5 6]]
如果給定 \(i\) 的 \(length\) 為 \(0\),會導致 \(copy\) 無法工作。所以必須給定長度。
不要引用用於迴圈的變數。在迴圈內部聲明新變數將其特殊化。