欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go語(yǔ)言Slice切片底層的實(shí)現(xiàn)

 更新時(shí)間:2025年04月15日 09:25:43   作者:明天不下雨(??屯? 
本文主要介紹了Go語(yǔ)言Slice切片底層的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

Go語(yǔ)言(Golang)中切片(slice)的相關(guān)知識(shí)、包括切片與數(shù)組的關(guān)系、底層結(jié)構(gòu)、擴(kuò)容機(jī)制、以及切片在函數(shù)傳遞、截取、增刪元素、拷貝等操作中的特性。并給出了相關(guān)代碼示例和一道面試題。關(guān)鍵要點(diǎn)包括:

數(shù)組特性:Go語(yǔ)言中數(shù)組是一個(gè)值、數(shù)組變量表示整個(gè)數(shù)組、不同于C語(yǔ)言中指向第一個(gè)元素的指針。傳遞數(shù)組到函數(shù)或拷貝數(shù)組時(shí)、會(huì)有不同的內(nèi)存地址和數(shù)據(jù)獨(dú)立性表現(xiàn)。

切片定義:切片是建立在Go數(shù)組之上的抽象類型、其底層結(jié)構(gòu)包含指向底層數(shù)組的指針、長(zhǎng)度和容量。

切片擴(kuò)容

  • 新切片長(zhǎng)度大于舊切片容量?jī)杀稌r(shí)、新容量為新長(zhǎng)度
  • 舊容量小于256時(shí)、新容量為舊容量?jī)杀?/li>
  • 否則按1.25倍增速擴(kuò)容、還會(huì)進(jìn)行內(nèi)存對(duì)齊。

函數(shù)傳遞:切片通過(guò)函數(shù)傳遞時(shí)、傳的是切片結(jié)構(gòu)、在函數(shù)內(nèi)改變切片可能影響函數(shù)外的切片、取決于底層數(shù)組是否變化。

切片操作

  • 通過(guò) “:” 作截取切片、新切片與原切片共享底層數(shù)組
  • 刪除元素可通過(guò)拼接切片實(shí)現(xiàn)
  • 新增元素使用append操作
  • 深度拷貝可使用copy函數(shù)。

1.切片是什么

在Go語(yǔ)言中 切片(slice)是建立在數(shù)組之上的一種抽象類型。切片提供了一種更靈活的方式來(lái)處理數(shù)組、它允許動(dòng)態(tài)地改變數(shù)組的大小、并且可以方便地進(jìn)行切片操作。理解切片之前、我們需要先了解數(shù)組。

Go的數(shù)組 在Go語(yǔ)言中、數(shù)組的長(zhǎng)度是類型的一部分、這意味著數(shù)組的長(zhǎng)度是固定的、不能改變。

數(shù)組的傳遞和拷貝行為與C語(yǔ)言不同、Go語(yǔ)言中的數(shù)組是值類型、傳遞數(shù)組時(shí)會(huì)進(jìn)行值拷貝。

1.1 示例一:

將數(shù)組傳遞到函數(shù)中 數(shù)組的地址不一樣

package main

import "fmt"

func main() {
    array := [3]int{1, 2, 3}
    // 數(shù)組傳遞到函數(shù)中
    test(array)
    fmt.Printf("array 外: %p\n", &array)
}

func test(array [3]int) {
    fmt.Printf("array 內(nèi): %p\n", &array)
    }

由于數(shù)組是值類型、傳遞數(shù)組時(shí)會(huì)進(jìn)行值拷貝、因此在test函數(shù)中打印的地址與main函數(shù)中打印的地址不同。

1.2 值拷貝

值拷貝意味著拷貝的是變量的內(nèi)容、而不是內(nèi)存地址。因此拷貝出來(lái)的變量有自己的獨(dú)立副本、內(nèi)容相同、但它們存儲(chǔ)在不同的內(nèi)存地址中。

Go 語(yǔ)言中的切片(slice)是動(dòng)態(tài)擴(kuò)容的。當(dāng)你向切片中添加元素時(shí)、Go 會(huì)自動(dòng)管理切片的大小、并在需要時(shí)進(jìn)行擴(kuò)容。

具體行為:

  • 初始容量:當(dāng)你創(chuàng)建一個(gè)切片時(shí)、Go 會(huì)為切片分配一個(gè)初始容量。如果你添加的元素超過(guò)了切片當(dāng)前的容量Go 會(huì)自動(dòng)擴(kuò)容。
  • 擴(kuò)容規(guī)則:Go 會(huì)根據(jù)當(dāng)前切片的容量自動(dòng)擴(kuò)展切片的大小、通常是原來(lái)容量的2倍。擴(kuò)容后、切片的長(zhǎng)度和容量都會(huì)增加。
  • 內(nèi)部機(jī)制:當(dāng)切片擴(kuò)容時(shí)、Go 會(huì)為新切片分配新的底層數(shù)組、并將原數(shù)組的元素拷貝到新數(shù)組中。這是一個(gè)代價(jià)比較高的操作、尤其是在需要多次擴(kuò)容的情況下

2.底層結(jié)構(gòu)

type slice struct {
    // 底層數(shù)組指針(或者說(shuō)是指向一塊連續(xù)內(nèi)存空間的起點(diǎn))
    array unsafe.Pointer
    // 長(zhǎng)度
    len  int
    // 容量
    cap  int
}

在這個(gè)結(jié)構(gòu)中:

  • array:指向底層數(shù)組的指針、或者說(shuō)是指向一塊連續(xù)內(nèi)存空間的起點(diǎn)。
  • len:切片的長(zhǎng)度、即切片中實(shí)際包含的元素?cái)?shù)量。
  • cap:切片的容量、即切片可以包含的元素的最大數(shù)量,不包括可能的擴(kuò)展空間。

切片擴(kuò)容

計(jì)算目標(biāo)容量

case1: 如果新切片的長(zhǎng)度大于舊切片容量的兩倍、則新切片容量就為新切片的長(zhǎng)度。

case2:

  • 如果舊切片的容量小于256、那么新切片的容量就是舊切片的容量的兩倍。
  • 反之需要用舊切片容量按照1.25倍的增速、直到大于新切片長(zhǎng)度。

為了更平滑的過(guò)渡、每次擴(kuò)大1.25倍、還會(huì)加上3/4 * 256

進(jìn)行內(nèi)存對(duì)齊、需要按照Go內(nèi)存管理的級(jí)別去對(duì)齊內(nèi)存、最終容量以這個(gè)為準(zhǔn)。

3.切片問(wèn)題

3.1 切片通過(guò)函數(shù)傳的是什么

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    s := make([]int, 5, 10)
    PrintSliceStruct(&s)
    test(s)
}

func test(s []int) {
    PrintSliceStruct(&s)
}

func PrintSliceStruct(s *[]int) {
    // 代碼 將slice 轉(zhuǎn)換成 reflect.SliceHeader
    ss := (*reflect.SliceHeader)(unsafe.Pointer(s))

    // 查看slice的結(jié)構(gòu)
    fmt.Printf("slice struct: %+v, slice is %v\n", ss, s)
}

控制臺(tái)輸出:

slice struct: &{Data:1374389649568 Len:5 Cap:10}, slice is &[0 0 0 0 0]
slice struct: &{Data:1374389649568 Len:5 Cap:10}, slice is &[0 0 0 0 0]

切片的定義:你創(chuàng)建了一個(gè)切片 s、通過(guò) make([]int, 5, 10) 創(chuàng)建了一個(gè)長(zhǎng)度為 5、容量為 10 的切片。

也就是說(shuō)它初始化了一個(gè)包含 5 個(gè)元素且最大容量為 10 的底層數(shù)組

總結(jié):

切片傳遞:當(dāng)切片通過(guò)參數(shù)傳遞到函數(shù)時(shí)、傳遞的是切片的值、但切片內(nèi)部的底層數(shù)組地址(指針)并沒(méi)有被復(fù)制。

PrintSliceStruct 打印的結(jié)構(gòu):無(wú)論是在 main 函數(shù)還是 test 函數(shù)中、切片的底層數(shù)組地址、長(zhǎng)度和容量都是相同的、因?yàn)榈讓訑?shù)組是共享的。

為什么輸出相同:

輸出顯示的 Data 地址、Len 和 Cap 是一致的、因?yàn)?test(s) 傳遞的是切片的值(即切片的結(jié)構(gòu)),但切片中的指針指向相同的底層數(shù)組。所以無(wú)論是傳遞給 PrintSliceStruct 函數(shù)的 s、還是 test 函數(shù)中的 s、它們指向的是同一個(gè)底層數(shù)組、并且它們的長(zhǎng)度和容量保持一致

3.2 在函數(shù)里面改變切片 函數(shù)外的切片會(huì)被影響嗎

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    s := make([]int, 5) // 創(chuàng)建一個(gè)長(zhǎng)度為 5 的切片
    case1(s)             // 調(diào)用 case1 函數(shù)
    case2(s)             // 調(diào)用 case2 函數(shù)
    PrintSliceStruct(&s) // 打印切片結(jié)構(gòu)
}

// 底層數(shù)組不變
func case1(s []int) {
    s[1] = 1 // 修改切片中的元素
    PrintSliceStruct(&s) // 打印切片結(jié)構(gòu)
}

// 底層數(shù)組變化
func case2(s []int) {
    s = append(s, 0) // 擴(kuò)容切片
    s[1] = 1         // 修改切片中的元素
    PrintSliceStruct(&s) // 打印切片結(jié)構(gòu)
}

func PrintSliceStruct(s *[]int) {
    // 將切片轉(zhuǎn)換成 reflect.SliceHeader
    ss := (*reflect.SliceHeader)(unsafe.Pointer(s))

    // 打印切片的底層結(jié)構(gòu)
    fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

關(guān)鍵點(diǎn):

case1 函數(shù):

在 case1 中、你傳入一個(gè)長(zhǎng)度為 5 的切片 s、并修改切片中的元素。

切片在函數(shù)內(nèi)的操作是對(duì)原切片的修改、因此底層數(shù)組沒(méi)有發(fā)生變化、切片的容量、長(zhǎng)度仍然相同。

打印的 slice struct 的 Data、Len 和 Cap 字段顯示的是切片的原始底層數(shù)據(jù)結(jié)構(gòu)。

case2 函數(shù):

在 case2 中、你向切片添加一個(gè)元素(通過(guò) append 操作)、這將可能導(dǎo)致切片的底層數(shù)組擴(kuò)容。

因?yàn)?nbsp;append 操作在超出當(dāng)前容量時(shí)會(huì)觸發(fā)擴(kuò)容、所以 s 的底層數(shù)組會(huì)發(fā)生變化、容量也可能增加。

 case2 中、s 被賦值為 append(s, 0)、這將導(dǎo)致原有切片 s 的底層數(shù)組被擴(kuò)展、并且一個(gè)新的數(shù)組被分配給 s(s 指向的是新的底層數(shù)組)

打印時(shí)會(huì)看到 slice struct 中的 Data 指向一個(gè)新的地址、表示底層數(shù)組已經(jīng)發(fā)生了變化。

append(s, 0) 函數(shù)會(huì)檢查切片 s 是否有足夠的容量來(lái)存儲(chǔ)新的元素。如果切片的容量不足、append 函數(shù)會(huì)分配一個(gè)新的更大的數(shù)組、并復(fù)制舊數(shù)組的內(nèi)容到新數(shù)組中、然后將新元素添加到新數(shù)組的末尾、并更新切片的指針以指向包含新元素的新底層數(shù)組。

3.3 截取切片

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    s := make([]int, 5)  // 創(chuàng)建一個(gè)長(zhǎng)度為 5 的切片,默認(rèn)初始化為 [0 0 0 0 0]
    
    case1(s)  // 調(diào)用 case1,修改切片內(nèi)容
    case2(s)  // 調(diào)用 case2,修改切片并改變底層數(shù)組
    case3(s)  // 調(diào)用 case3,截取切片并改變其長(zhǎng)度
    case4(s)  // 調(diào)用 case4,截取切片的部分元素
    PrintSliceStruct(&s)  // 最后打印切片的底層結(jié)構(gòu)
}

// case1:修改切片元素、底層數(shù)組不變
func case1(s []int) {
    s[1] = 1  // 修改切片中的第二個(gè)元素,s[1] = 1
    PrintSliceStruct(&s)  // 打印修改后的切片底層結(jié)構(gòu)
}

// case2:重新賦值為新的切片
func case2(s []int) {
    s = s[:]  // 這里實(shí)際上并沒(méi)有改變切片的內(nèi)容、它只是重新賦值為原切片的一個(gè)新引用。
    PrintSliceStruct(&s)  // 打印新的切片底層結(jié)構(gòu)
}

// case3:截取切片、底層數(shù)組不變
func case3(s []int) {
    s = s[:len(s)-1]  // 截取切片、去掉最后一個(gè)元素、新的切片長(zhǎng)度為 4
    PrintSliceStruct(&s)  // 打印截取后的切片底層結(jié)構(gòu)
}

// case4:截取切片的部分元素、底層數(shù)組不變
func case4(s []int) {
    sl := s[1:2]  // 截取 s[1:2],即取出切片中索引為 1 的元素
    PrintSliceStruct(&sl)  // 打印截取后的新切片底層結(jié)構(gòu)
}

// PrintSliceStruct 打印切片的底層結(jié)構(gòu)
func PrintSliceStruct(s *[]int) {
    // 將切片的指針轉(zhuǎn)換為 reflect.SliceHeader 結(jié)構(gòu)體,通過(guò) unsafe.Pointer 獲取底層數(shù)據(jù)
    ss := (*reflect.SliceHeader)(unsafe.Pointer(s))
    
    // 打印切片的底層數(shù)據(jù)結(jié)構(gòu)、包括:指向底層數(shù)組的內(nèi)存地址、切片的長(zhǎng)度和容量
    fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

總結(jié):

切片操作的影響

修改切片元素不會(huì)改變底層數(shù)組的地址。

重新賦值切片并沒(méi)有改變底層數(shù)組、除非涉及擴(kuò)容(例如 append)。

截取切片時(shí)、底層數(shù)組不變、切片的長(zhǎng)度和容量可能會(huì)變化。

3.4 刪除元素

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    // 創(chuàng)建一個(gè)包含5個(gè)整數(shù)的切片
    s := []int{0, 1, 2, 3, 4}

    // 打印切片的底層結(jié)構(gòu)
    PrintSliceStruct(&s)

    // 刪除切片中的最后一個(gè)元素,正確的做法是通過(guò)切片截取
    _ = s[4]                      // 訪問(wèn)并丟棄切片中的最后一個(gè)元素
    s1 := append(s[:1], s[2:]...) // 刪除元素 s[1],
 //s[:1](即切片 [0])和 s[2:](即切片 [2, 3, 4])拼接在一起。


    // 打印修改后的切片
    fmt.Println(s)  // [0 2 3 4 4]
    fmt.Println(s1) // [0, 2, 3, 4]

    // 打印切片底層結(jié)構(gòu)
    PrintSliceStruct(&s)
    PrintSliceStruct(&s1)

    // 訪問(wèn)切片的元素
    s = s[:4] // 截取切片、刪除最后一個(gè)元素
    _ = s[3]  // 訪問(wèn)切片中的最后一個(gè)元素(索引為3的元素)
}

// 打印切片的底層結(jié)構(gòu)
func PrintSliceStruct(s *[]int) {
    // 將切片轉(zhuǎn)換為 reflect.SliceHeader
    ss := (*reflect.SliceHeader)(unsafe.Pointer(s))

    // 打印切片的底層結(jié)構(gòu)
    fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

在 Go 中、切片操作需要特別注意切片的索引和截取。訪問(wèn)切片中的元素時(shí)要小心類型不匹配(例如不能將一個(gè)切片元素賦值給切片)。

控制臺(tái)輸出:

slice struct: &{Data:1374390755328 Len:5 Cap:5}, slice is [0 1 2 3 4]
[0 2 3 4 4]
[0 2 3 4]
slice struct: &{Data:1374390755328 Len:5 Cap:5}, slice is [0 2 3 4 4]
slice struct: &{Data:1374390755328 Len:4 Cap:5}, slice is [0 2 3 4]

打印原切片 s 時(shí)、它仍然指向原底層數(shù)組(長(zhǎng)度為 5、容量為 5)、而且由于 s[4] 在內(nèi)存中并沒(méi)有被移除、原底層數(shù)組中的最后一個(gè)元素 4 被保留、因此 s 顯示為 [0 2 3 4 4]。

簡(jiǎn)而言之s 顯示為 [0 2 3 4, 4] 是因?yàn)樵记衅牡讓訑?shù)組并沒(méi)有被修改、而 append 操作生成了一個(gè)新的切片(s1)并分配了新的底層數(shù)組。所以s 中仍然包含原數(shù)組中的所有元素、最后一個(gè) 4 仍然存在。

為什么 s變成了 [0, 2, 3, 4, 4]

append 會(huì)根據(jù)切片的容量決定是否會(huì)使用原來(lái)的底層數(shù)組。如果原切片的容量足夠大、append 就會(huì)直接修改原切片。

在這段代碼中、由于原始切片 s 的容量足夠大(原始切片 s 的容量為 5)、append 仍然修改了原始切片 s 的內(nèi)容。切片的 s 和 s1 都指向相同的底層數(shù)組。

重點(diǎn):

原切片 s 的容量沒(méi)有改變:s 底層的數(shù)組仍然包含原來(lái) s 的所有元素。

append 沒(méi)有重新分配新的底層數(shù)組:由于原切片的容量足夠、所以 append 在修改原底層數(shù)組時(shí)、并沒(méi)有創(chuàng)建新的底層數(shù)組。因此原始切片 s 中的 4 仍然存在。

修改后 s 中的元素為 [0, 2, 3, 4, 4]:雖然你刪除了 s[1] 這個(gè)元素、但 append 使得 s 的底層數(shù)組沒(méi)有發(fā)生變化,因此原始的 4 元素仍然保留在切片中。

結(jié)論:

append 操作有時(shí)會(huì)創(chuàng)建新的底層數(shù)組(如果容量不足)、但如果原切片的容量足夠、append 直接修改原切片的底層數(shù)組。在這種情況下原切片 s 會(huì)保持原來(lái)的容量和數(shù)據(jù)、導(dǎo)致 s 顯示為 [0, 2, 3, 4, 4],即最后一個(gè) 4 保留下來(lái)了。

3.5 新增元素

package main

import (
    "fmt"
    "reflect"
    "unsafe"
)

func main() {
    case1()
    case2()
    case3()
}

// case1 函數(shù)展示了使用 append 在切片末尾添加元素的行為
func case1() {
    // 創(chuàng)建一個(gè)長(zhǎng)度為 3,容量為 3 的切片
    s1 := make([]int, 3, 3)
    // 向切片添加一個(gè)元素 1,append 返回一個(gè)新的切片
    s1 = append(s1, 1)
    // 打印切片的底層結(jié)構(gòu)
    PrintSliceStruct(&s1) //1
}

// case2 函數(shù)展示了在原切片上使用 append 并打印切片結(jié)構(gòu)的變化
func case2() {
    // 創(chuàng)建一個(gè)長(zhǎng)度為 3,容量為 4 的切片
    s1 := make([]int, 3, 4)
    // 向切片添加一個(gè)元素 1,append 會(huì)擴(kuò)展切片的長(zhǎng)度
    s2 := append(s1, 1)
    // 打印原切片 s1 和新切片 s2 的底層結(jié)構(gòu)
    PrintSliceStruct(&s1)//2
    PrintSliceStruct(&s2)//3
}

// case3 函數(shù)與 case2 類似,展示了切片長(zhǎng)度、容量變化的行為
func case3() {
    // 創(chuàng)建一個(gè)長(zhǎng)度為 3,容量為 3 的切片
    s1 := make([]int, 3, 3)
    // 向切片添加一個(gè)元素 1,append 返回一個(gè)新的切片
    s2 := append(s1, 1)
    // 打印原切片 s1 和新切片 s2 的底層結(jié)構(gòu)
    PrintSliceStruct(&s1)//4
    PrintSliceStruct(&s2)//5
}

// PrintSliceStruct 打印切片的底層結(jié)構(gòu)
func PrintSliceStruct(s *[]int) {
    // 使用 reflect 和 unsafe 包將切片轉(zhuǎn)換成 reflect.SliceHeader 結(jié)構(gòu)體
    ss := (*reflect.SliceHeader)(unsafe.Pointer(s))

    // 打印切片的底層結(jié)構(gòu)
    fmt.Printf("slice struct: %+v, slice is %v\n", ss, *s)
}

控制臺(tái)輸出

slice struct: &{Data:1374390755328 Len:4 Cap:6}, slice is [0 0 0 1]
slice struct: &{Data:1374390779936 Len:3 Cap:4}, slice is [0 0 0]
slice struct: &{Data:1374390779936 Len:4 Cap:4}, slice is [0 0 0 1]
slice struct: &{Data:1374390673552 Len:3 Cap:3}, slice is [0 0 0]
slice struct: &{Data:1374390755376 Len:4 Cap:6}, slice is [0 0 0 1]

case1:

使用 make([]int, 3, 3) 創(chuàng)建了一個(gè)長(zhǎng)度為 3,容量為 3 的切片 s1,初始內(nèi)容為 [0, 0, 0]。

然后append(s1, 1) 會(huì)將元素 1 添加到切片的末尾、生成一個(gè)新的切片并返回。由于容量是 3、append 會(huì)自動(dòng)擴(kuò)容新的切片長(zhǎng)度是 4

最后調(diào)用 PrintSliceStruct 打印 s1 切片的底層結(jié)構(gòu)。

case2:

make([]int, 3, 4) 創(chuàng)建了一個(gè)長(zhǎng)度為 3、容量為 4 的切片 s1

使用 append(s1, 1) 向切片添加元素 1、生成一個(gè)新切片 s2。由于 s1 的容量已足夠、不會(huì)觸發(fā)擴(kuò)容。

通過(guò) PrintSliceStruct 打印切片 s1 和 s2 的底層結(jié)構(gòu)。

s3 和 s4 參考上面

3.6 操作原來(lái)切片會(huì)影響新的切片嗎

在 Go 中、切片是引用類型,這意味著當(dāng)你創(chuàng)建一個(gè)新切片時(shí),它實(shí)際上可能會(huì)指向同一個(gè)底層數(shù)組。因此,如果你修改了原切片(比如通過(guò) append 或其他操作),它可能會(huì)影響到新切片,特別是在底層數(shù)組沒(méi)有被重新分配的情況下。

切片和底層數(shù)組

切片(slice)是一個(gè)非常輕量級(jí)的抽象,它包含了三個(gè)部分:指向底層數(shù)組的指針、切片的長(zhǎng)度和切片的容量。

當(dāng)你對(duì)切片進(jìn)行操作時(shí)(例如使用 append、copy 或直接修改),這些操作通常會(huì)影響到底層數(shù)組。

如果多個(gè)切片引用同一個(gè)底層數(shù)組,改變其中一個(gè)切片的內(nèi)容可能會(huì)影響到其他切片,尤其是在沒(méi)有擴(kuò)容時(shí)。

append操作

當(dāng)使用 append 函數(shù)時(shí)、如果切片的容量足夠、append 會(huì)直接在原底層數(shù)組上操作、不會(huì)創(chuàng)建新的底層數(shù)組。在這種情況下、修改原切片的內(nèi)容會(huì)影響到新切片、因?yàn)樗鼈冎赶蛳嗤牡讓訑?shù)組。

如果容量不足、append 會(huì)創(chuàng)建一個(gè)新的底層數(shù)組、并將原切片的數(shù)據(jù)復(fù)制到新數(shù)組中、這時(shí)原切片和新切片就指向不同的底層數(shù)組了、它們互不影響。

例子:

沒(méi)有擴(kuò)容:

s1 := []int{1, 2, 3}
s2 := s1      // s2 指向與 s1 相同的底層數(shù)組
s1[0] = 100   // 修改 s1 中的第一個(gè)元素
fmt.Println(s1) // 輸出 [100, 2, 3]
fmt.Println(s2) // 輸出 [100, 2, 3]

這里s1 和 s2 指向相同的底層數(shù)組,因此修改 s1 會(huì)影響到 s2。

擴(kuò)容時(shí):

s1 := []int{1, 2, 3}
s2 := append(s1, 4)  // s2 創(chuàng)建了新的底層數(shù)組
s1[0] = 100          // 修改 s1 中的第一個(gè)元素
fmt.Println(s1) // 輸出 [100, 2, 3]
fmt.Println(s2) // 輸出 [1、2、3、 4]

這里s2 創(chuàng)建了一個(gè)新的底層數(shù)組,因此修改 s1 不會(huì)影響 s2。

結(jié)論:

修改原切片會(huì)影響新切片:如果新切片是通過(guò)引用原切片的底層數(shù)組創(chuàng)建的(沒(méi)有觸發(fā)擴(kuò)容)、修改原切片的內(nèi)容會(huì)影響到新切片。

擴(kuò)容時(shí)不影響:如果 append 或其他操作導(dǎo)致了擴(kuò)容、原切片和新切片就會(huì)指向不同的底層數(shù)組、互不影響。

4.字節(jié)面試題

下面這道題的輸出是什么

package main

import "fmt"

func main() {

    // 定義一個(gè)匿名函數(shù) doAppend,用來(lái)執(zhí)行 append 操作并打印切片的長(zhǎng)度和容量
        doAppend := func(s []int) {
                s = append(s, 1)         // 向切片中添加元素 1
                printLengthAndCapacity(s) // 打印切片的長(zhǎng)度和容量
        }

    // 創(chuàng)建一個(gè)長(zhǎng)度為 8,容量為 8 的切片 s
        s := make([]int, 8, 8)
    
    // 傳遞 s 的前 4 個(gè)元素(即 s[:4])到 doAppend
        doAppend(s[:4])  // 只傳遞前4個(gè)元素的切片

    // 打印原始切片 s 的長(zhǎng)度和容量
        printLengthAndCapacity(s)

    // 傳遞整個(gè)切片 s 到 doAppend
        doAppend(s)
    
    // 打印原始切片 s 的長(zhǎng)度和容量
       printLengthAndCapacity(s)
}

func printLengthAndCapacity(s []int) {
    fmt.Println() 
    fmt.Printf("len=%d cap=%d \n", len(s), cap(s))  // 打印切片的長(zhǎng)度和容量
}

len(s) 是切片的長(zhǎng)度。

cap(s) 是切片的容量、表示切片底層數(shù)組的大小。

len=5 cap=8 len=8 cap=8 len=9 cap=16 len=8 cap=8

調(diào)用 doAppend(s[:4]):

s[:4] 是 s 切片的前 4 個(gè)元素、創(chuàng)建一個(gè)新的切片 [0, 0, 0, 0],長(zhǎng)度為 4、容量為 8(因?yàn)樗玫氖窃衅牡讓訑?shù)組)。

在 doAppend 中、執(zhí)行 append(s, 1)、這會(huì)向切片添加一個(gè)元素 1、導(dǎo)致切片的長(zhǎng)度變?yōu)?5、容量保持為 8(因?yàn)樗鼪](méi)有觸發(fā)擴(kuò)容)。

打印結(jié)果為:len=5 cap=8。

調(diào)用 doAppend(s)

這次傳遞整個(gè) s 切片、長(zhǎng)度為 8、容量為 8。

執(zhí)行 append(s 1)、這會(huì)向切片 s 添加一個(gè)元素 1。因?yàn)?s 的容量是 8、不能再容納更多元素、因此會(huì)觸發(fā)擴(kuò)容、新的底層數(shù)組的容量將是原來(lái)的 2 倍、即 16、長(zhǎng)度變?yōu)?9。

打印結(jié)果為:len=9 cap=16。

注意:

append 創(chuàng)建了一個(gè)新的底層數(shù)組、并返回了一個(gè)新的切片。如果你不把返回的新切片賦值回 s、原始切片 s 不會(huì)改變、仍然指向舊的底層數(shù)組。

由于 append(s 1) 返回的是一個(gè)新的切片、但并沒(méi)有將它賦值回 s、所以原始切片 s 的長(zhǎng)度和容量沒(méi)有變化、仍然是 len=8 和 cap=8

如果改成將新切片賦值回s

package main

import "fmt"

func main() {
    // 定義一個(gè)匿名函數(shù) doAppend 用于向切片添加元素
    doAppend := func(s []int) {
       s = append(s, 1)          // 向切片中添加元素 1
       printLengthAndCapacity(s) // 打印切片的長(zhǎng)度和容量
    }

    // 定義一個(gè)匿名函數(shù) doAppend 用于向切片添加元素
    doAppends := func(s []int) []int {
       s = append(s, 1) // 使用 append 向切片添加一個(gè)元素 1
       printLengthAndCapacity(s)
       return s
    }

    // 創(chuàng)建一個(gè)長(zhǎng)度為 8,容量為 8 的切片 s
    s := make([]int, 8, 8)

    // 傳遞前 4 個(gè)元素的切片
    doAppend(s[:4]) // 只傳遞前4個(gè)元素的切片
    printLengthAndCapacity(s)

    // 傳遞整個(gè)切片 s
    s = doAppends(s) // 將返回的新切片賦值回 s
    printLengthAndCapacity(s)
}

func printLengthAndCapacity(s []int) {
    fmt.Println()
    fmt.Printf("len=%d cap=%d \n", len(s), cap(s))
}

len=5 cap=8 
len=8 cap=8 
len=9 cap=16 
len=9 cap=16 

到此這篇關(guān)于Go語(yǔ)言Slice切片底層的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)Go語(yǔ)言Slice切片內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Go語(yǔ)言題解LeetCode599兩個(gè)列表的最小索引總和

    Go語(yǔ)言題解LeetCode599兩個(gè)列表的最小索引總和

    這篇文章主要為大家介紹了Go語(yǔ)言題解LeetCode599兩個(gè)列表的最小索引總和示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-12-12
  • Golang?strings包常用字符串操作函數(shù)

    Golang?strings包常用字符串操作函數(shù)

    Golang?中的字符串統(tǒng)一使用?UTF-8?(屬于Unicode編碼的一種實(shí)現(xiàn)方式)進(jìn)行編碼,本篇文章將結(jié)合具體實(shí)例對(duì)常用的字符串操作函數(shù)進(jìn)行介紹,感興趣的可以了解一下
    2021-12-12
  • go程序員日常開(kāi)發(fā)效率神器匯總

    go程序員日常開(kāi)發(fā)效率神器匯總

    這篇文章主要介紹了go程序員開(kāi)發(fā)效率神器包含了go常用開(kāi)發(fā)工具,go調(diào)試工具,go常用網(wǎng)站,golang常用庫(kù),需要的朋友可以參考下
    2022-11-11
  • go結(jié)構(gòu)體嵌套的切片數(shù)組操作

    go結(jié)構(gòu)體嵌套的切片數(shù)組操作

    這篇文章主要介紹了go結(jié)構(gòu)體嵌套的切片數(shù)組操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • 詳解Go語(yǔ)言實(shí)現(xiàn)線性查找算法和二分查找算法

    詳解Go語(yǔ)言實(shí)現(xiàn)線性查找算法和二分查找算法

    線性查找又稱順序查找,它是查找算法中最簡(jiǎn)單的一種。二分查找,也稱折半查找,相比于線性查找,它是一種效率較高的算法。本文將用Go語(yǔ)言實(shí)現(xiàn)這兩個(gè)查找算法,需要的可以了解一下
    2022-12-12
  • Go channel實(shí)現(xiàn)原理分析

    Go channel實(shí)現(xiàn)原理分析

    Channel是go語(yǔ)言內(nèi)置的一個(gè)非常重要的特性,也是go并發(fā)編程的兩大基石之一,下面這篇文章主要給大家介紹了關(guān)于Go中channel的相關(guān)資料,需要的朋友可以參考下
    2023-04-04
  • Golang實(shí)現(xiàn)SSH、SFTP操作小結(jié)

    Golang實(shí)現(xiàn)SSH、SFTP操作小結(jié)

    在日常的一些開(kāi)發(fā)場(chǎng)景中,我們需要去和遠(yuǎn)程服務(wù)器進(jìn)行一些通信,本文主要介紹了Golang實(shí)現(xiàn)SSH、SFTP操作小結(jié),具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-04-04
  • Golang多線程刷票的實(shí)現(xiàn)代碼

    Golang多線程刷票的實(shí)現(xiàn)代碼

    這篇文章主要介紹了Golang多線程刷票的相關(guān)資料,這里實(shí)現(xiàn)刷票的功能,對(duì)于投票,刷票的很方便,并附實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2017-07-07
  • Go語(yǔ)言通過(guò)Luhn算法驗(yàn)證信用卡卡號(hào)是否有效的方法

    Go語(yǔ)言通過(guò)Luhn算法驗(yàn)證信用卡卡號(hào)是否有效的方法

    這篇文章主要介紹了Go語(yǔ)言通過(guò)Luhn算法驗(yàn)證信用卡卡號(hào)是否有效的方法,實(shí)例分析了Luhn算法的原理與驗(yàn)證卡號(hào)的使用技巧,需要的朋友可以參考下
    2015-03-03
  • GoLang 逃逸分析的機(jī)制詳解

    GoLang 逃逸分析的機(jī)制詳解

    這篇文章主要介紹了GoLang-逃逸分析的機(jī)制詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02

最新評(píng)論