文法
原型
在go/src/builtin/builtin.go中對append說明如下:
// The append built-in function appends elements to the end of a slice. If// it has sufficient capacity, the destination is resliced to accommodate the// new elements. If it does not, a new underlying array will be allocated.// Append returns the updated slice. It is therefore necessary to store the// result of append, often in the variable holding the slice itself:// slice = append(slice, elem1, elem2)// slice = append(slice, anotherSlice...)// As a special case, it is legal to append a string to a byte slice, like this:// slice = append([]byte("hello "), "world"...)func append(slice []Type, elems ...Type) []Type
要點 用append把一個或多個元素添加在一個slice的後面; append的slice有一個underlying array,此即slice和array的關係; 另外slice有一個length和capability的概念; 如果slice還有剩餘的空間,可以添加這些新元素,那麼append就將新的元素放在slice後面的空餘空間中; 如果slice的空間不足以放下新增的元素,那麼就需要重現建立一個數組;這時可能是alloc、也可能是realloc的方式分配這個新的數組; 也就是說,這個新的slice可能和之前的slice在同一個起始地址上,也可能不是一個新的地址。——通常而言,是一個新的地址。 分配了新的地址之後,再把原來slice中的元素逐個拷貝到新的slice中,並返回。
類比
這個slice可以和C++的vector作類比。為此,可以參考《C++源碼剖析》4.2.5 vector的構造與記憶體管理。 樣本 代碼
package mainimport ( "fmt")type Employee struct { name string city string}func (e *Employee)String() string { return fmt.Sprintf("Employee[address: %p, name: %s, city: %s]", e, e.name, e.city)}var g_employees = []Employee{}//var g_employees = make([]Employee, 0, 4)func add(employee *Employee) { g_employees = append(g_employees, *employee)}func debug() { count := len(g_employees) for i := 0; i < count; i++ { fmt.Printf("%d: %s\n", i, &(g_employees[i])) } fmt.Println()}func main() { one := Employee{name: "name1", city:"city1"} two := Employee{name: "name2", city:"city2"} three := Employee{name: "name3", city:"city3"} four := Employee{name: "name4", city:"city4"} add(&one) debug() add(&two) debug() add(&three) debug() add(&four) debug()}
運行結果
Case 1: var g_employees = []Employee{}0: Employee[address: 0xc42000e100, name: name1, city: city1]0: Employee[address: 0xc4200101c0, name: name1, city: city1]1: Employee[address: 0xc4200101e0, name: name2, city: city2]0: Employee[address: 0xc420086000, name: name1, city: city1]1: Employee[address: 0xc420086020, name: name2, city: city2]2: Employee[address: 0xc420086040, name: name3, city: city3]0: Employee[address: 0xc420086000, name: name1, city: city1]1: Employee[address: 0xc420086020, name: name2, city: city2]2: Employee[address: 0xc420086040, name: name3, city: city3]3: Employee[address: 0xc420086060, name: name4, city: city4]Case 2: var g_employees = make([]Employee, 0, 4)0: Employee[address: 0xc420074000, name: name1, city: city1]0: Employee[address: 0xc420074000, name: name1, city: city1]1: Employee[address: 0xc420074020, name: name2, city: city2]0: Employee[address: 0xc420074000, name: name1, city: city1]1: Employee[address: 0xc420074020, name: name2, city: city2]2: Employee[address: 0xc420074040, name: name3, city: city3]0: Employee[address: 0xc420074000, name: name1, city: city1]1: Employee[address: 0xc420074020, name: name2, city: city2]2: Employee[address: 0xc420074040, name: name3, city: city3]3: Employee[address: 0xc420074060, name: name4, city: city4]
分析
在調試列印中,特別把對象的地址列印處理。通過輸出,可以有如下結論: 在一個capability為0的空slice上面增加一個元素的時候,加1個、2個、3個的時候會重新分配儲存空間; 同時伴隨對象拷貝。 變化的對象與不變的指標
如果slice中存放的是對象,那麼在用指標處理這些對象的時候就要特別注意。比如現在全域變數採用第一種方式:
var g_employees = []Employee{}
main()代碼改為:
func main() { one := Employee{name: "name1", city:"city1"} two := Employee{name: "name2", city:"city2"} three := Employee{name: "name3", city:"city3"} four := Employee{name: "name4", city:"city4"} add(&one) debug() var p *Employee p = &(g_employees[0]) fmt.Printf("p: %s\n\n", p) add(&two) debug() fmt.Printf("p: %s\n\n", p) add(&three) debug() fmt.Printf("p: %s\n\n", p) add(&four) debug() fmt.Printf("p: %s\n\n", p)}
在增加了一個元素之後,就定義一個指標指向該元素。運行結果:
0: Employee[address: 0xc420074000, name: name1, city: city1]p: Employee[address: 0xc420074000, name: name1, city: city1]0: Employee[address: 0xc420076140, name: name1, city: city1]1: Employee[address: 0xc420076160, name: name2, city: city2]p: Employee[address: 0xc420074000, name: name1, city: city1]0: Employee[address: 0xc420082000, name: name1, city: city1]1: Employee[address: 0xc420082020, name: name2, city: city2]2: Employee[address: 0xc420082040, name: name3, city: city3]p: Employee[address: 0xc420074000, name: name1, city: city1]0: Employee[address: 0xc420082000, name: name1, city: city1]1: Employee[address: 0xc420082020, name: name2, city: city2]2: Employee[address: 0xc420082040, name: name3, city: city3]3: Employee[address: 0xc420082060, name: name4, city: city4]p: Employee[address: 0xc420074000, name: name1, city: city1]
可以看到指標指向的地址雖然一直不變,但已經不是我們所預期的對象了。 變通
現在讓slice中儲存物件地址,而不再是對象。 樣本一
package mainimport ( "fmt")type Employee struct { name string city string}func (e *Employee)String() string { return fmt.Sprintf("Employee[address: %p, name: %s, city: %s]", e, e.name, e.city)}var g_employees = []*Employee{}func add(employee *Employee) { g_employees = append(g_employees, employee)}func debug() { count := len(g_employees) for i := 0; i < count; i++ { fmt.Printf("%d: %s\n", i, g_employees[i]) } fmt.Println()}func main() { one := Employee{name: "name1", city:"city1"} two := Employee{name: "name2", city:"city2"} three := Employee{name: "name3", city:"city3"} four := Employee{name: "name4", city:"city4"} add(&one) debug() var p *Employee p = g_employees[0] fmt.Printf("p: %s\n\n", p) add(&two) debug() fmt.Printf("p: %s\n\n", p) add(&three) debug() fmt.Printf("p: %s\n\n", p) add(&four) debug() fmt.Printf("p: %s\n\n", p)}
運行結果:
0: Employee[address: 0xc42000e1a0, name: name1, city: city1]p: Employee[address: 0xc42000e1a0, name: name1, city: city1]0: Employee[address: 0xc42000e1a0, name: name1, city: city1]1: Employee[address: 0xc42000e1c0, name: name2, city: city2]p: Employee[address: 0xc42000e1a0, name: name1, city: city1]0: Employee[address: 0xc42000e1a0, name: name1, city: city1]1: Employee[address: 0xc42000e1c0, name: name2, city: city2]2: Employee[address: 0xc42000e1e0, name: name3, city: city3]p: Employee[address: 0xc42000e1a0, name: name1, city: city1]0: Employee[address: 0xc42000e1a0, name: name1, city: city1]1: Employee[address: 0xc42000e1c0, name: name2, city: city2]2: Employee[address: 0xc42000e1e0, name: name3, city: city3]3: Employee[address: 0xc42000e200, name: name4, city: city4]p: Employee[address: 0xc42000e1a0, name: name1, city: city1]
樣本二
package mainimport ( "fmt")type Employee struct { name string city string}func (e *Employee)String() string { return fmt.Sprintf("Employee[address: %p, name: %s, city: %s]", e, e.name, e.city)}var g_employees = []*Employee{}func add(index int) { g_employees = append(g_employees, &Employee{ fmt.Sprintf("name%d", index), fmt.Sprintf("city%d", index)})}func debug() { count := len(g_employees) for i := 0; i < count; i++ { fmt.Printf("%d: %s\n", i, g_employees[i]) } fmt.Println()}func main() { add(1) debug() var p *Employee p = g_employees[0] fmt.Printf("p: %s\n\n", p) add(2) debug() fmt.Printf("p: %s\n\n", p) add(3) debug() fmt.Printf("p: %s\n\n", p) add(4) debug() fmt.Printf("p: %s\n\n", p)}
運行結果不變。