slice 的問題

Codcodog發表於2020-11-27

場景

package main

import "fmt"

type cities []string

func main() {
    Nepal := cities{"Kathmandu", "Pokhara", "Lumbini"}
    Nepal.add()
    Nepal.print()
}

func (c cities) print() {
    for i, city := range c {
        fmt.Println(i, city)
    }
}

func (c cities) add() {
    c = append(c, "hello")
}

執行輸出如下:

0 Kathmandu
1 Pokhara
2 Lumbini

結果沒有 3 hello

原因

slicearray 很類似,但是它更靈活,可以自動擴容.

由原始碼可以看到它的本質:

// runtime/slice.go
type slice struct {
    array unsafe.Pointer // 元素指標
    len   int // 長度
    cap   int // 容量
}

slice 有三個屬性,分別是指向底層陣列的指標,切片可用的元素個數,底層陣列的元素個數.

底層的陣列可能被多個 slice 指向,所以修改其中一個 slice 的時候,會因此影響到其他的 slice.

上面的程式碼 c = append(c, "hello") 之後,遍歷 c 卻沒有 hello,這主要涉及到 slice 的擴容問題.

在新增元素的時候,如果 slice 的容量不夠的時候,是會先讀取原有元素,然後 cap 擴大一倍,再重新複製到新的陣列中去,slice 的陣列指標也更新為新的陣列指標.

把上面程式碼 c 的指標地址列印下,就清晰很多了.

package main

import "fmt"

type cities []string

func main() {
    Nepal := cities{"Kathmandu", "Pokhara", "Lumbini"}
    Nepal.add()
    Nepal.print()
}

func (c cities) print() {
    fmt.Printf("print(): %p \n", c)
    for i, city := range c {
        fmt.Println(i, city)
    }
}

func (c cities) add() {
    fmt.Printf("add() append before: %p \n", c)
    c = append(c, "hello")
    fmt.Printf("add() append after: %p \n", c)
}

輸出如下:

add() append before: 0xc000070150 # append 前後地址不一樣
add() append after: 0xc00007c120
print(): 0xc000070150
0 Kathmandu
1 Pokhara
2 Lumbini

解決

add() 方法的時候,使用指標賦值,直接修改原來的 slice.

func (c *cities) add() {
    *c = append(*c, "hello")
}

輸出如下:

0 Kathmandu
1 Pokhara
2 Lumbini
3 hello

參考

how-to-append-to-a-slice-pointer-receiver
深度解密Go語言之Slice

如果覺得不錯,可以給我一個 STAR

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章