亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

go slice 數(shù)組和切片使用區(qū)別示例解析

 更新時間:2023年01月03日 11:43:27   作者:eleven26  
這篇文章主要為大家介紹了go slice 數(shù)組和切片使用區(qū)別示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

slice(切片)是 go 里面非常常用的一種數(shù)據(jù)結(jié)構(gòu),它代表了一個變長的序列,序列中的每個元素都有相同的數(shù)據(jù)類型。 一個 slice 類型一般寫作 []T,其中 T 代表 slice 中元素的類型;slice 的語法和數(shù)組很像,但是 slice 沒有固定長度。

數(shù)組和切片的區(qū)別

數(shù)組有確定的長度,而切片的長度不固定,并且可以自動擴(kuò)容。

數(shù)組的定義

go 中定義數(shù)組的方式有如下兩種:

  • 指定長度:
arr := [3]int{1, 2, 3}
  • 不指定長度,由編譯器推導(dǎo)出數(shù)組的長度:
arr := [...]{1, 2, 3}

上面這兩種定義方式都定義了一個長度為 3 的數(shù)組。正如我們所見,長度是數(shù)組的一部分,定義數(shù)組的時候長度已經(jīng)確定下來了。

切片的定義

切片的定義方式跟數(shù)組很像,只不過定義切片的時候不用指定長度

s := []int{1, 2, 3}

在上面定義切片的代碼中,我們可以看到其實(shí)跟數(shù)組唯一的區(qū)別就是少了個長度。 那其實(shí)我們可以把切片看作是一個無限長度的數(shù)組。 當(dāng)然,實(shí)際上它并不是無限的,它只是在切片容納不下新的元素的時候,會自動進(jìn)行擴(kuò)容,從而可以容納更多的元素。

數(shù)組和切片的相似之處

正如我們上面看到的那樣,數(shù)組和切片兩者其實(shí)非常相似,在實(shí)際使用中,它們也是有些類似的。

比如,通過下標(biāo)來訪問元素:

arr := [3]int{1, 2, 3}
// 通過下標(biāo)訪問
fmt.Println(arr[1]) // 2
s := []int{1, 2, 3}
// 通過下標(biāo)訪問
fmt.Println(s[1]) // 2

數(shù)組的局限

我們知道了,數(shù)組的長度是固定的,這也就意味著如果我們想往數(shù)組里面增加一個元素會比較麻煩, 我們需要新建一個更大的數(shù)組,然后將舊的數(shù)據(jù)復(fù)制過去,然后將新的元素寫進(jìn)去,如:

// 往數(shù)組 arr 增加一個元素:4
arr := [3]int{1, 2, 3}
// 新建一個更大容量的數(shù)組
var arr1 [4]int
// 復(fù)制舊數(shù)組的數(shù)據(jù)
for i := 0; i < len(arr); i++ {
    arr1[i] = arr[i]
}
// 加入新的元素:4
arr1[3] = 4
fmt.Println(arr1)

這樣一來就非常的繁瑣,如果我們使用切片,就可以省去這些步驟:

// 定義一個長度為 3 的數(shù)組
arr := [3]int{1, 2, 3}
// 從數(shù)組創(chuàng)建一個切片
s := arr[:]
// 增加一個元素
s = append(s, 4)
fmt.Println(s)

因?yàn)閿?shù)組固定長度的缺點(diǎn),實(shí)際使用中切片會使用得更加普遍。

重新理解 slice

在開始之前,我們來看看 slice 這個單詞的意思:作為名詞,slice 的意思有 片;部分;(切下的食物)薄片;,作為動詞,slice 的意思有 切;把…切成(薄)片; 的意思。 從這個角度出發(fā),我們可以把 slice 理解為從某個數(shù)組上 切下來的一部分(從這個角度看,slice 這個命名非常的形象)。我們可以看看下圖:

在這個圖中,A 是一個保存了數(shù)字 1~7slice,B 是從 A切下來的一部分,而 B 只包含了 A 中的一部分?jǐn)?shù)據(jù)。 我們可以把 B 理解為 A 的一個 視圖,B 中的數(shù)據(jù)是 A 中的數(shù)據(jù)的一個 引用,而不是 A 中數(shù)據(jù)的一個 拷貝 (也就是說,我們修改 B 的時候,A 中的數(shù)據(jù)也會被修改,當(dāng)然會有例外,那就是 B 發(fā)生擴(kuò)容的時候,再去修改 B 的話就影響不了 A 了)。

slice 的內(nèi)存布局

現(xiàn)在假設(shè)我們有如下代碼:

// 創(chuàng)建一個切片,長度為 3,容量為 7
var s = make([]int, 3, 7)
s[0] = 1
s[1] = 2
s[2] = 3
fmt.Println(s)

對應(yīng)的內(nèi)存布局如下:

說明:

  • slice 底層其實(shí)也是數(shù)組,但是除了數(shù)組之外,還有兩個字段記錄切片的長度和容量,分別是 lencap。
  • 上圖中,slice 中的 array 就是切片的底層數(shù)組,因?yàn)樗拈L度不是固定的,所以使用了指針來保存,指向了另外一片內(nèi)存區(qū)域。
  • len 表明了切片的長度,切片的長度也就是我們可以操作的下標(biāo),上面的切片長度為 3,這也就意味著我們切片可以操作的下標(biāo)范圍是 0~2。超出這個范圍的下標(biāo)會報錯。
  • cap 表明了切片的容量,也就是切片擴(kuò)容之前可以容納的元素個數(shù)。

切片容量存在的意義

對于我們?nèi)粘i_發(fā)來說,slice 的容量其實(shí)大多數(shù)時候不是我們需要關(guān)注的點(diǎn),而且由于容量的存在,也給開發(fā)者帶來了一定的困惑。 那么容量存在的意義是什么呢?意義就在于避免內(nèi)存的頻繁分配帶來的性能下降(容量也就是提前分配的內(nèi)存大小)。

比如,假如我們有一個切片,然后我們知道需要往它里面存放 1w 個元素, 如果我們不指定容量的話,那么切片就會在它存放不下新的元素的時候進(jìn)行擴(kuò)容, 這樣一來,可能在我們存放這 1w 個元素的時候需要進(jìn)行多次擴(kuò)容, 這也就意味著需要進(jìn)行多次的內(nèi)存分配。這樣就會影響應(yīng)用的性能。

我們可以通過下面的例子來簡單了解一下:

// Benchmark1-20       100000000          11.68 ns/op
func Benchmark1(b *testing.B) {
   var s []int
   for i := 0; i < b.N; i++ {
      s = append(s, 1)
   }
}
// Benchmark2-20       134283985           7.482 ns/op
func Benchmark2(b *testing.B) {
   var s []int = make([]int, 10, 100000000)
   for i := 0; i < b.N; i++ {
      s = append(s, 1)
   }
}

在第一個例子中,沒有給 slice 設(shè)置容量,這樣它就只會在切片容納不下新元素的時候才會進(jìn)行擴(kuò)容,這樣就會需要進(jìn)行多次擴(kuò)容。 而第二個例子中,我們先給 slice 設(shè)置了一個足夠大的容量,那么它就不需要進(jìn)行頻繁擴(kuò)容了。

最終我們發(fā)現(xiàn),在給切片提前設(shè)置容量的情況下,會有一定的性能提升。

切片常用操作

創(chuàng)建切片

我們可以從數(shù)組或切片生成新的切片:

注意:生成的切片不包含 end。

target[start:end]

說明:

  • target 表示目標(biāo)數(shù)組或者切片
  • start 對應(yīng)目標(biāo)對象的起始索引(包含)
  • end 對應(yīng)目標(biāo)對象的結(jié)束索引(不包含)

如:

s := []int{1, 2, 3}
s1 := s[1:2]    // 包含下標(biāo) 1,不包含下標(biāo) 2
fmt.Println(s1) // [2]
arr := [3]int{1, 2, 3}
s2 := arr[1:2]
fmt.Println(s2) // [2]

在這種初始化方式中,我們可以省略 start

arr := [3]int{1, 2, 3}
fmt.Println(arr[:2]) // [1, 2]

省略 start 的情況下,就是從 target 的第一個元素開始。

我們也可以省略 end

arr := [3]int{1, 2, 3}
fmt.Println(arr[1:]) // [2, 3]

省略 end 的情況下,就是從 start 索引處的元素開始直到 target 的最后一個元素處。

除此之外,我們還可以指定新的切片的容量,通過如下這種方式:

target[start:end:cap]

例子:

arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
s := arr[1:4:5]
fmt.Println(s, len(s), cap(s)) // [2 3 4] 3 4

往切片中添加元素

我們前面說過了,如果我們想往數(shù)組里面增加元素,那么我們必須開辟新的內(nèi)存,將舊的數(shù)組復(fù)制過去,然后才能將新的元素加入進(jìn)去。

但是切片就相對簡單,我們可以使用 append 這個內(nèi)置函數(shù)來往切片中加入新的元素:

var a []int
a = append(a, 1) // 追加1個元素
a = append(a, 1, 2, 3) // 追加多個元素
a = append(a, []int{1,2,3}...) // 追加一個切片

切片復(fù)制

go 有一個內(nèi)置函數(shù) copy 可以將一個切片的內(nèi)容復(fù)制到另外一個切片中:

copy(dst, src []int)

第一個參數(shù) dst 是目標(biāo)切片,第二個參數(shù) src 是源切片,調(diào)用 copy 的時候會把 src 的內(nèi)容復(fù)制到 dst 中。

示例:

var a []int
var b []int = []int{1, 2, 3}
// a 的容量為 0,容納不下任何元素
copy(a, b)
fmt.Println(a) // []
a = make([]int, 3, 3) // 給 a 分配內(nèi)存
copy(a, b)
fmt.Println(a) // [1 2 3]

需要注意的是,如果 dst 的長度比 src 的長度小,那么只會截取 src 的前面一部分。

從切片刪除元素

雖然我們往切片追加元素的操作挺方便的,但是要從切片刪除元素就相對麻煩一些了。go 語言本身沒有提供從切片刪除元素的方法。 如果我們要刪除切片中的元素,只有構(gòu)建出一個新的切片:

對應(yīng)代碼:

var a = make([]int, 7, 7)
for i := 0; i < 7; i++ {
    a[i] = i + 1
}
fmt.Println(a) // [1 2 3 4 5 6 7]
var b []int
b = append(b, a[:2]...) // [1 2]
b = append(b, a[5:]...) // [1 2 6 7]
fmt.Println(b) // [1 2 6 7]

在這個例子中,我們想從 a 中刪除 3、4、5 這三個元素,也就是下標(biāo) 2~4 的元素, 我們的做法是,新建了一個新的切片,然后將 3 前面的元素加入到這個新的切片中, 再將 5 后面的元素加入到這個新切片中。

最終得到的切片就是刪除了 3、4、5 三個元素之后的切片了。

切片的容量到底是多少?

假設(shè)我們有如下代碼:

var a = make([]int, 7, 7)
for i := 0; i &lt; 7; i++ {
    a[i] = i + 1
}
// [1 2 3 4 5 6 7]
fmt.Println(a)
s1 := a[:3]
// [1 2 3] 3 7
fmt.Println(s1, len(s1), cap(s1))
s2 := a[4:6]
// [5 6] 2 3
fmt.Println(s2, len(s2), cap(s2))

s1s2 可以用下圖表示:

  • s1 只能訪問 array 的前三個元素,s2 只能訪問 56 這兩個元素。
  • s1 的容量是 7(底層數(shù)組的長度)
  • s2 的容量是 3,從 5 所在的索引處直到底層數(shù)組的末尾。

對于 s1s2,我們都沒有指定它的容量,但是我們打印發(fā)現(xiàn)它們都有容量, 其實(shí)在切片中,我們從切片中生成一個新的切片的時候,如果我們不指定容量, 那新切片的容量就是 s[start:end] 中的 start 直到底層數(shù)組的最后一個元素的長度。

切片可以共享底層數(shù)組

切片最需要注意的點(diǎn)是,當(dāng)我們從一個切片中創(chuàng)建新的切片的時候,兩者會共享同一個底層數(shù)組, 如上圖的那樣,s1s2 都引用了同一個底層的數(shù)組不同的索引, s1 引用了底層數(shù)組的 0~2 下標(biāo)范圍,s2 引用了底層數(shù)組 4~5 下標(biāo)范圍。

這意味著,當(dāng)我們修改 s1s2 的時候,原來的切片 a 也會發(fā)生改變:

var a = make([]int, 7, 7)
for i := 0; i < 7; i++ {
    a[i] = i + 1
}
// [1 2 3 4 5 6 7]
fmt.Println(a)
s1 := a[:3]
// [1 2 3]
fmt.Println(s1)
s1[1] = 100
// [1 100 3 4 5 6 7]
fmt.Println(a)
// [1 100 3]
fmt.Println(s1)

在上面的例子中,s1 這個切片引用了和 a 一樣的底層數(shù)組, 然后在我們修改 s1 的時候,a 也發(fā)生了改變。

切片擴(kuò)容不會影響原切片

上一小節(jié)我們說了,切片可以共享底層數(shù)組。但是如果切片擴(kuò)容的話,那就是一個全新的切片了

var a = []int{1, 2, 3}
// [1 2 3] 3 3
fmt.Println(a, len(a), cap(a))
// a 容納不下新的元素了,會進(jìn)行擴(kuò)容
b := append(a, 4)
// [1 2 3 4] 4 6
fmt.Println(b, len(b), cap(b))
b[1] = 100
// [1 2 3]
fmt.Println(a)
// [1 100 3 4]
fmt.Println(b)

在上面這個例子中,a 是一個長度和容量都是 3 的切片,這也就意味著,這個切片已經(jīng)滿了。 在這種情況下,我們再往其中追加元素的時候,就會進(jìn)行擴(kuò)容,生成一個新的切片。 因此,我們可以看到,我們修改了 b 的時候,并沒有影響到 a。

下面的例子就不一樣了:

// 長度為 2,容量為 3
var a = make([]int, 2, 3)
a[0] = 1
a[1] = 2
// [1 2] 2 3
fmt.Println(a, len(a), cap(a))
// a 還可以容納新的元素,不用擴(kuò)容
b := append(a, 4)
// [1 2 4] 3 3
fmt.Println(b, len(b), cap(b))
b[1] = 100
// [1 100]
fmt.Println(a)
// [1 100 4]
fmt.Println(b)

在后面這個例子中,我們只是簡單地改了一下 a 初始化的方式,改成了只放入兩個元素,但是容量還是 3, 在這種情況下,a 可以再容納一個元素,這樣在 b := append(a, 4) 的時候,創(chuàng)建的 b 底層的數(shù)組其實(shí)跟 a 的底層數(shù)組依然是一樣的。

所以,我們需要尤其注意代碼中作為切片的函數(shù)參數(shù),如果我們希望在被調(diào)函數(shù)中修改了切片之后,在 caller 里面也能看到效果的話,最好是傳遞指針。

func test1(s []int) {
   s = append(s, 4)
}
func test2(s *[]int) {
   *s = append(*s, 4)
}
func TestSlice(t *testing.T) {
   var a = []int{1, 2, 3}
   // [1 2 3] 3 3
   fmt.Println(a, len(a), cap(a))
   test1(a)
   // [1 2 3] 3 3
   fmt.Println(a, len(a), cap(a))
   var b = []int{1, 2, 3}
   // [1 2 3] 3 3
   fmt.Println(b, len(b), cap(b))
   test2(&amp;b)
   // [1 2 3 4] 4 6
   fmt.Println(b, len(b), cap(b))
}

在上面的例子中,test1 接收的是值參數(shù),所以在 test1 中切片發(fā)生擴(kuò)容的時候,TestSlice 里面的 a 還是沒有發(fā)生改變。 而 test2 接收的是指針參數(shù),所以在 test2 中發(fā)生切片擴(kuò)容的時候,TestSlice 里面的 b 也發(fā)生了改變。

總結(jié)

  • 數(shù)組跟切片的使用上有點(diǎn)類似,但是數(shù)組代表的是有固定長度的數(shù)據(jù)序列,而切片代表的是沒有固定長度的數(shù)據(jù)序列。
  • 數(shù)組的長度是類型的一部分,有兩種定義數(shù)組的方式:[2]int{1, 2}、[...]int{1, 2}
  • 數(shù)組跟切片都可以通過下標(biāo)來訪問其中的元素,可以訪問的下標(biāo)范圍都是 0 ~ len(x)-1,x 表示的是數(shù)組或者切片。
  • 數(shù)組無法追加新的元素,切片可以追加任意數(shù)量的元素。
  • slice 的數(shù)據(jù)結(jié)構(gòu)里面包含了:array 底層數(shù)組指針、len 切片長度、cap 切片容量。
  • 創(chuàng)建切片的時候,指定一個合適的容量可以減少內(nèi)存分配的次數(shù),從而在一定程度上提高程序性能。
  • 我們可以從數(shù)組或者切片創(chuàng)建一個新的切片:array[1:3] 或者 slice[1:3]
  • 使用 append 內(nèi)置函數(shù)可以往切片中添加新的元素。
  • 使用 copy 內(nèi)置函數(shù)可以將一個切片的內(nèi)容復(fù)制到另外一個切片中。
  • 切片刪除元素沒有好的辦法,只能截取被刪除元素前后的數(shù)據(jù),然后復(fù)制到一個新的切片中。
  • 假設(shè)我們通過 slice[start:end] 的方式從切片中創(chuàng)建一個新的切片,那么這個新的切片的容量是 cap(slice) - start,也就是,從 start 到底層數(shù)組最后一個元素的長度。
  • 使用切片的時候需要注意:切片之間會共享底層數(shù)組,其中一個切片修改了切片的元素的時候,也會反映到其他切片上。
  • 函數(shù)調(diào)用的時候,如果被調(diào)函數(shù)內(nèi)發(fā)生擴(kuò)容,調(diào)用者是無法知道的。如果我們不想錯過在被調(diào)函數(shù)內(nèi)切片的變化,我們可以傳遞指針作為參數(shù)。

以上就是go slice 數(shù)組和切片使用區(qū)別示例解析的詳細(xì)內(nèi)容,更多關(guān)于go slice 數(shù)組切片區(qū)別的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang依賴注入工具digo的使用詳解

    Golang依賴注入工具digo的使用詳解

    這篇文章主要為大家詳細(xì)介紹了Golang中依賴注入工具digo的使用,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-06-06
  • Golang源碼分析之golang/sync之singleflight

    Golang源碼分析之golang/sync之singleflight

    golang/sync庫拓展了官方自帶的sync庫,提供了errgroup、semaphore、singleflight及syncmap四個包,本次先分析第一個包errgroup的源代碼,下面這篇文章主要給大家介紹了關(guān)于Golang源碼分析之golang/sync之singleflight的相關(guān)資料,需要的朋友可以參考下
    2022-11-11
  • GoLang unsafe包詳細(xì)講解

    GoLang unsafe包詳細(xì)講解

    從golang的定義來看,unsafe 是類型安全的操作。顧名思義,它應(yīng)該非常謹(jǐn)慎地使用; unsafe可能很危險,但也可能非常有用。例如,當(dāng)使用系統(tǒng)調(diào)用和Go結(jié)構(gòu)必須具有與C結(jié)構(gòu)相同的內(nèi)存布局時,您可能別無選擇,只能使用unsafe
    2022-10-10
  • Go工具鏈之go tool fix用法詳解

    Go工具鏈之go tool fix用法詳解

    go tool fix 是 Go 工具鏈中的一個命令,作用是把指定 Go 程序代碼包中的的所有舊版本代碼修正為新版本的代碼,本文將簡單介紹一下go tool fix的使用方法,感興趣的小伙伴可以參考閱讀下
    2023-07-07
  • golang 整合antlr語法校驗(yàn)解析

    golang 整合antlr語法校驗(yàn)解析

    Antlr是一個語法分析器,本身是用java實(shí)現(xiàn)的,然是Runtime的庫也支持Golang、Java、Python等,本文給大家講解使用golang整合antlr進(jìn)行語法解析,感興趣的朋友一起看看吧
    2023-02-02
  • 全面解析Go語言中crypto/sha1庫

    全面解析Go語言中crypto/sha1庫

    crypto/sha1在Go語言標(biāo)準(zhǔn)庫中是一個強(qiáng)大且實(shí)用的工具,適用于多種應(yīng)用場景,本文就詳細(xì)的介紹了Go語言中crypto/sha1庫,具有一定的參考價值,感興趣的可以了解一下
    2024-02-02
  • Go語言學(xué)習(xí)筆記之文件讀寫操作詳解

    Go語言學(xué)習(xí)筆記之文件讀寫操作詳解

    這篇文章主要為大家詳細(xì)介紹了Go語言對文件進(jìn)行讀寫操作的方法,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Go語言有一定的幫助,需要的可以參考一下
    2022-05-05
  • Go語言基礎(chǔ)if條件語句用法及示例詳解

    Go語言基礎(chǔ)if條件語句用法及示例詳解

    這篇文章主要為大家介紹了Go語言基礎(chǔ)if條件語句的用法及示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2021-11-11
  • Golang字符串拼接的性能以及原理詳解

    Golang字符串拼接的性能以及原理詳解

    最近在做性能優(yōu)化,有個函數(shù)里面的耗時特別長,看里面的操作大多是一些字符串拼接的操作,而字符串拼接在golang里面其實(shí)有很多種實(shí)現(xiàn),下面這篇文章主要給大家介紹了關(guān)于Golang字符串拼接的性能以及原理的相關(guān)資料,需要的朋友可以參考下
    2023-06-06
  • Go語言CSP并發(fā)模型goroutine及channel底層實(shí)現(xiàn)原理

    Go語言CSP并發(fā)模型goroutine及channel底層實(shí)現(xiàn)原理

    這篇文章主要為大家介紹了Go語言CSP并發(fā)模型goroutine?channel底層實(shí)現(xiàn)原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05

最新評論