Go 語言中的 切片 --slice

牛马chen發表於2024-10-24

為了更好地理解 Go 語言中的 切片(slice),我們可以將它與 C++ 中的陣列或容器(如 std::vector)進行比較,但要注意的是,它們之間有一些關鍵的區別。讓我們逐步將 Go 的切片與 C++ 中的概念進行對應:

1. 陣列 vs 切片

在 C++ 中,陣列(array) 是一種固定大小的資料結構,大小必須在編譯時確定,並且不能動態調整。例如:

int arr[5] = {1, 2, 3, 4, 5};  // C++ 中的靜態陣列,大小為5

Go 語言中的 陣列 也是固定大小的,一旦定義,其長度是不可變的。例如:

var arr [5]int = [5]int{1, 2, 3, 4, 5}  // Go中的陣列,大小為5

但是 Go 中的 切片(slice) 是一種動態、可變長度的結構,相當於在 C++ 中使用 std::vector。Go 的切片是對底層陣列的引用,可以在執行時動態調整大小。

2. 切片 vs std::vector

C++ 中的 std::vector 與 Go 語言中的 切片(slice) 比較類似。兩者都提供了可變長度的陣列功能,並且它們都可以動態增加或縮小。

  • C++ 中的 std::vector
#include <vector>
std::vector<int> vec = {1, 2, 3, 4, 5};  // C++ 中的動態陣列
vec.push_back(6);  // 動態增加元素
  • Go 中的 切片
slice := []int{1, 2, 3, 4, 5}  // Go 中的切片
slice = append(slice, 6)  // 動態增加元素

在 C++ 中,std::vector 可以動態調整大小,並自動管理底層記憶體。同樣地,Go 的切片也可以透過 append 函式動態增加元素。就像 std::vector 可以在其容量不足時自動分配更多記憶體,Go 的切片也是如此。

3. 長度和容量

  • 在 C++ 的 std::vector 中,容量(capacity)指的是分配的記憶體空間大小,可以容納的元素數量,而長度(size)指的是實際儲存的元素個數。

    std::vector<int> vec = {1, 2, 3};
    int size = vec.size();      // 當前儲存的元素個數,輸出: 3
    int capacity = vec.capacity();  // 分配的記憶體容量
    
  • 在 Go 中,切片也有類似的概念:

    • 長度(len):切片中實際包含的元素個數。
    • 容量(cap):從切片的起始位置到底層陣列末尾的元素數量。
    slice := []int{1, 2, 3}
    fmt.Println(len(slice))  // 輸出:3
    fmt.Println(cap(slice))  // 輸出:3
    

當向 std::vector 或切片追加元素時,容量不足時會重新分配更大的記憶體空間。

4. 底層陣列和記憶體共享

在 C++ 中,當你操作 std::vector 時,通常是一個獨立的記憶體塊,每次操作都不會影響到其他 vector。然而,在 Go 中,切片是共享底層陣列的。如果從一個陣列或切片中建立另一個切片,多個切片可以共享同一底層陣列。這與 C++ 的陣列指標類似。

例如,假設我們有一個 C++ 的陣列指標:

int arr[] = {1, 2, 3, 4, 5};
int* ptr = arr + 1;  // 指向陣列的第二個元素
ptr[0] = 100;        // 修改陣列

這種情況下,修改 ptr 所指向的內容會影響到原始陣列。

在 Go 中,切片共享底層陣列的特性與此類似:

arr := [5]int{1, 2, 3, 4, 5}
slice1 := arr[1:4]  // 建立一個從索引1到3的切片
slice1[0] = 100     // 修改 slice1 的第一個元素,影響了底層陣列
fmt.Println(arr)    // 輸出: [1 100 3 4 5]

5. append 函式的行為

當我們在 Go 中向切片追加元素時,底層陣列的容量可能會重新分配。當容量不足時,Go 會建立一個新的底層陣列,並將現有的資料複製過去,這與 C++ 中 std::vector 的擴容機制類似。

  • 在 C++ 中,當 std::vector 容量不足時,會自動分配更多記憶體,現有元素會被複制到新的記憶體空間中。
  • 在 Go 中,append 可能會導致重新分配底層陣列,類似地也會將現有資料複製到新的陣列中。
std::vector<int> vec = {1, 2, 3};
vec.push_back(4);  // 如果容量不足,重新分配記憶體

Go 中的對應邏輯:

slice := []int{1, 2, 3}
slice = append(slice, 4)  // 如果容量不足,重新分配底層陣列

6. C++ 中的指標 vs Go 中的切片

在 C++ 中,如果你透過指標運算元組的部分元素,或者傳遞陣列指標到函式內,這種行為類似於 Go 中的切片。例如:

  • C++ 中透過指標運算元組:
void modifyArray(int* arr) {
    arr[0] = 100;
}

int main() {
    int arr[] = {1, 2, 3};
    modifyArray(arr);
    // arr[0] 現在是 100
}
  • 在 Go 中,切片類似於陣列的引用,因此可以在函式中直接修改底層陣列:
func modifySlice(s []int) {
    s[0] = 100
}

func main() {
    arr := []int{1, 2, 3}
    modifySlice(arr)
    fmt.Println(arr)  // 輸出: [100 2 3]
}

7. 總結:C++ vs Go 切片理解

  • 切片與 C++ 的 std::vector 相似,因為兩者都可以動態擴充套件,並且容量和長度是分開的概念。
  • 切片共享底層陣列,類似於在 C++ 中透過指標訪問陣列的某部分,多個切片可以引用同一塊記憶體。
  • Go 切片的追加操作append)與 C++ 中 std::vector 的擴容機制類似,當容量不足時,會自動分配新的更大的底層陣列。
  • 切片的指標行為 類似於 C++ 中透過指標運算元組元素的方式。

理解這些相似點和不同點,可以幫助你更快適應 Go 的切片操作,特別是在你熟悉 C++ 的陣列和 std::vector 時。

相關文章