一文詳解Golang中的切片數(shù)據(jù)類型
含義
切片是一個種特殊的數(shù)組。是對數(shù)組的一個連續(xù)片段的引用,所以切片是一個引用類型。切片可以是數(shù)組中的一部分,也可以是由起始和終止索引標(biāo)識的一些項(xiàng)的子集。切片有點(diǎn)像C語言里的指針,指針可以做運(yùn)算,但代價是內(nèi)存操作越界,切片在指針的基礎(chǔ)上增加了大小,約束了切片對應(yīng)的內(nèi)存區(qū)域,切片使用中無法對切片內(nèi)部的地址和大小進(jìn)行手動調(diào)整,因此切片比指針更安全、強(qiáng)大。
定義
切片定義分為三中形式。依次從數(shù)組中生成、從切片中生成和全新定義一個切片。
三個要素
1.起始位置:切片引用數(shù)組的開始位置。
2.大?。?/strong>切片中的元素個數(shù)。切片中的大小不能超過容量數(shù)量??梢允褂胠en()函數(shù)對切片統(tǒng)計(jì)大小。
3.容量:切片最大可存的元素個數(shù)。如果空間不足以容納足夠多的元素,切片就會進(jìn)行動態(tài)“擴(kuò)容”,此時新切片的長度會發(fā)生改變。一般切片的擴(kuò)容是按照擴(kuò)容前容量的2倍??梢允褂胏ap()函數(shù)對切片容量進(jìn)行統(tǒng)計(jì)。
切片與數(shù)組的區(qū)別
- 切片是對數(shù)組中的連續(xù)引用。切片的初始位置指向數(shù)組的內(nèi)存地址,如果切片的值改變,數(shù)組對應(yīng)的值也會對應(yīng)改變。
- 切片的長度是動態(tài)的,本質(zhì)上是一個可變的動態(tài)數(shù)組。數(shù)組的長度在定義的時候就決定好了,后期是無法修改數(shù)組的長度的。
- 切片的長度是可以動態(tài)擴(kuò)容的[如上面容量一次提到的]。
- 切片本身是不保存數(shù)據(jù),它只是底層數(shù)組的表示。對切片所做的任何修改都將反應(yīng)到底層數(shù)組中。
package main import ( "fmt" ) func main() { numa := [3]int{78, 79, 80} nums1 := numa[:] nums2 := numa[:] fmt.Println("array before change 1", numa) nums1[0] = 100 fmt.Println("array after modification to slice nums1", numa) nums2[1] = 101 fmt.Println("array after modification to slice nums2", numa) }
// output array before change 1 [78 79 80] array after modification to slice nums1 [100 79 80] array after modification to slice nums2 [100 101 80]
當(dāng)多個切片共享一個底層數(shù)組時,每個切片的修改都將反映在底層數(shù)組中。
示例代碼
// 通過數(shù)組定義切片 var array1 = [3]int{1, 1, 3} fmt.Println("數(shù)組的元素分別是:", array1) slice1 := array1[0:2] slice1[1] = 11111 fmt.Println("數(shù)組的元素分別是:", array1)
輸出結(jié)果;
數(shù)組的元素分別是: [1 1 3]
數(shù)組的元素分別是: [1 11111 3]
切片內(nèi)存分布
切片定義分類
數(shù)組生成切片
定義語法:
slice[起始位置:結(jié)束位置]
- 1.slice:表示切片的對象。例如從一個數(shù)組中生成切片則slice就是定義的數(shù)組名稱。
- 2.起始位置:從數(shù)組中的某個元素的下標(biāo)開始切,默認(rèn)中0開始。
- 3.結(jié)束位置:切片的結(jié)束位置。也就是數(shù)組的某個元素下標(biāo)位置。<font color=“red”>需要注意的是這里取的是開區(qū)間</font>。如果需要取到數(shù)組的最后一個元素,結(jié)束位置這是數(shù)組的長度+1。
- 4.切片的長度:(切片的結(jié)束位置-切片的起始位置)。
示例代碼
// 通過數(shù)組定義切片 array := []string{"A","B","C","D","E","F","G","H","I","G","K","L"} slice := array(0:5) // 打印結(jié)果為 [A B C D E] // 使用make方式創(chuàng)建切片 slice1 := make([]string, 2, 3) slice1[0] = "1" fmt.Println(slice1) slice2 := make([]string, 2, 3) fmt.Println(slice2) fmt.Println("切片的長度為", len(slice1)) fmt.Println("切片的容量為", cap(slice1)) // output [1 ] [ ] 切片的長度為 2 切片的容量為 3
切片索引
1.切片的起始位置省略,結(jié)束位置省略。則默認(rèn)從數(shù)組的開始位置截取到數(shù)組的結(jié)束位置+1。得到的是和數(shù)組內(nèi)容一樣的切片,表示原切片。
// 切片定義省略開始和結(jié)束位置 array := []string{"A","B","C","D","E","F","G","H","I","G","K","L"} fmt.Println("開始位置和結(jié)束位置都缺省",array[:]) // output 開始位置和結(jié)束位置都缺省 [A B C D E F G H I G K L]
2.切片的起始位置不省略,結(jié)束位置不省略。則根據(jù)起始位置和結(jié)束位置進(jìn)行切取。
// 起始位置和結(jié)束位置都不省略 array := []string{"A","B","C","D","E","F","G","H","I","G","K","L"} slice := array(0:5) // output [A B C D E]
3.起始位置省略,結(jié)束位置不省略。則默認(rèn)從數(shù)組的最開始位置切取,直到結(jié)束位置為止。
// 起始位置省略,結(jié)束位置都不省略 array := []string{"A","B","C","D","E","F","G","H","I","G","K","L"} slice := array(:5) // output [A B C D E]
4.起始位置不省略,結(jié)束位置省略。則默認(rèn)從數(shù)組的指定起始位置竊取到數(shù)組的最后以為(位置為數(shù)組長度+1)。
// 起始位置不省略,結(jié)束位置省略 array := []string{"A","B","C","D","E","F","G","H","I","G","K","L"} fmt.Println("缺省結(jié)束位置:",array[2:]) // 打印結(jié)果為 缺省結(jié)束位置: [C D E F G H I G K L]
5.切片的起始位置和結(jié)束位置,不能超出數(shù)組的范圍。
array := []string{"A","B","C","D","E","F","G","H","I","G","K","L"} fmt.Println("切片",array[-1:100]) // 打印結(jié)果為 invalid slice index -1 (index must be non-negative)
6.切片的起始位置和結(jié)束位置都為0,得到一個空的切片,表示清空切片。一般用于切片的復(fù)位。
// 起始位置和結(jié)束位置都為0 array := []string{"A","B","C","D","E","F","G","H","I","G","K","L"} fmt.Println("切片",array[0:0]) // 打印結(jié)果為 切片: []
直接聲明切片
除了可以從原有的數(shù)組或者切片中生成切片外,也可以聲明一個新的切片,每一種類型都可以擁有其切片類型,表示多個相同類型元素的連續(xù)集合,因此切片類型也可以被聲明。
定義語法
// 也可以通過一個空的數(shù)組形式 var slice []type
1.slice是切片的名稱。
2.type是切片的數(shù)據(jù)類型。
代碼示例
// 聲明一個整型切片 var slice1 []int // 初始化一個切片 var slice2 []int = []int{}
使用make定義切片
除了上面的幾種方式外,如果需要動態(tài)地創(chuàng)建一個切片,可以使用 make() 內(nèi)建函數(shù)。
定義語法:
make([]type, size, cap)
- 1.type為切片的數(shù)據(jù)類型。
- 2.size為切片的大小。
- 3.cap為切片的容量。
切片的大小不能超過容量,容量表示該切片最大的元素個數(shù),切片的大小表示實(shí)際的元素個數(shù)。例如,一個教室里面可以坐到30個人,現(xiàn)目前坐了10個人。這里的10就表示size,30就表示cap。
代碼示例:
// 定義切片 slice1 := make([]string, 2, 3) slice1[0] = "1" fmt.Println(slice1) // 打印如下結(jié)果 [1 ]
如果切片在復(fù)制的過程中,對應(yīng)的下標(biāo)未分配值,則根據(jù)數(shù)據(jù)類型默認(rèn)分配一個值。例如上面的slince1定義的時2個長度,但是只給下標(biāo)為0的分配了值,因此下標(biāo)為1的根據(jù)數(shù)據(jù)類型時string類型,默認(rèn)分配一個" "值。
常用操作
長度計(jì)算
切片長度使用len()計(jì)算。
// 計(jì)算切片長度 slice1 := make([]string, 2, 3) fmt.Println("切片的長度為", len(slice1)) // 打印結(jié)果為 切片的長度為 2
容量計(jì)算
切片容量使用cap()計(jì)算。
// 計(jì)算切片長度 slice1 := make([]string, 2, 3) fmt.Println("切片的長度為", cap(slice1)) // 打印結(jié)果為 切片的容量為 3
判斷是否為空
在創(chuàng)建變量章節(jié)提到, 變量如果創(chuàng)建時未給一個初始化值,編譯時會默認(rèn)分配一個nil的值。因此判斷一個切片為空,直接與nil比較。
// 判斷空切片 slice3 := make([]string, 2, 3) if slice3 == nil { fmt.Println("slice3是空切片") } else { fmt.Println("slice3不是空切片") } var slice4 []string if slice4 == nil { fmt.Println("slice4是空切片") } else { fmt.Println("slice4不是空切片") }
// output slice3不是空切片 slice4是空切片
// 錯誤演示 slice1 := make([]int, 2, 5) slice2 := make([]int, 2, 5) if slice1 == slice2 { fmt.Println("相等") } else { fmt.Println("不想等") }
// output slice1 == slice2 (slice can only be compared to nil)
使用make創(chuàng)建切片時,因?yàn)槎x了一個長度為2,容量為3的切片。雖然切片內(nèi)容是[ ],但是實(shí)際是有值的,只不過是一個空值。切片是動態(tài)結(jié)構(gòu),只能與 nil 判定相等,不能互相判定相等。聲明新的切片后,可以使用 append() 函數(shù)向切片中添加元素。
切片追加
追加的定義:
使用append()可以動態(tài)的給切片的開始位置,結(jié)束位置或者中間位置添加元素。
語法格式
append(slice, element)
1.slice,要追加的切片,必須是一個切片。
2.element,向切片中添加的元素,可以是單個元素、多個元素或著切片。
尾部追加
// 切片的開始位置追加元素 var slice []int = []int {1,2,3} // 打印原始切片的長度和容量 fmt.Println("原始切片長度和容量分別是", len(slice), cap(slice)) // 向切片后面追加一個元素 slice = append(slice, 1) fmt.Println(slice) // 向切片后面追加多個元素 slice = append(slice, 6,7,8,9) fmt.Println(slice) // 打印新的切片的長度和容量 fmt.Println("新切片長度和容量分別是", len(slice), cap(slice))
// 打印的內(nèi)容分別如下 原始切片長度和容量分別是 3 3 [1 2 3 1] [1 2 3 1 6 7 8 9] 新切片長度和容量分別是 8 12
注意事項(xiàng):
- 1.在切片的尾部添加元素,只能是單個元素或者是多個","隔開的元素,而不能是其他的數(shù)據(jù)類型。
- 2.如果切片追加元素時,容量不夠,切片會自動的擴(kuò)容。自動擴(kuò)容的規(guī)律是2的倍數(shù)。如下代碼:
// 驗(yàn)證切片追加元素自動擴(kuò)容 var numbers []int for i := 0; i < 10; i++ { numbers = append(numbers, i) fmt.Printf("len: %d cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers) }
// output
len: 1 cap: 1 pointer: 0xc0420080e8
len: 2 cap: 2 pointer: 0xc042008150
len: 3 cap: 4 pointer: 0xc04200e320
len: 4 cap: 4 pointer: 0xc04200e320
len: 5 cap: 8 pointer: 0xc04200c200
len: 6 cap: 8 pointer: 0xc04200c200
len: 7 cap: 8 pointer: 0xc04200c200
len: 8 cap: 8 pointer: 0xc04200c200
len: 9 cap: 16 pointer: 0xc042074000
len: 10 cap: 16 pointer: 0xc042074000
開始位置追加
// 向切片的開始位置追加元素 var slice = []int {1,2,3} slice = append([]int {0}, slice...) // 在開頭添加只有1個元素的切片 slice = append([]int {-3,-2,-1}, slice...) // 在開頭添加擁有多個元素的切片 fmt.Println(slice)
// output
[-3 -2 -1 0 1 2 3]
- 1.在切片的開始位置添加元素,將添加的元素作為append()的第一個參數(shù),第二個參數(shù)為原始的切片,需要在原始切片后加"…"。
- 2.append()的第一個參數(shù)必須是切片。
- 3.在切片開頭添加元素一般都會導(dǎo)致內(nèi)存的重新分配,而且會導(dǎo)致已有元素全部被復(fù)制 1 次,因此,從切片的開頭添加元素的性能要比從尾部追加元素的性能差很多。
中間位置追加
// 向切片的中間追加元素 var slice2 = []int {1,2,3,7,8,9} slice2 = append(slice2[0:3], append([]int {4,5,6},slice2[3:]...)...) fmt.Println(slice2)
// output
[1 2 3 4 5 6 7 8 9]
- 1.向切片的中間追加元素基本格式為<kbd>append(a[:i], append([]int{x}, a[i:]…)…) // 在第i個位置插入x</kbd>
- 2.每個添加操作中的第二個 append 調(diào)用都會創(chuàng)建一個臨時切片,并將 a[i:] 的內(nèi)容復(fù)制到新創(chuàng)建的切片中,然后將臨時創(chuàng)建的切片再追加到 a[:i] 中。
復(fù)制
復(fù)制的定義:
語言的內(nèi)置函數(shù), copy()可以將一個數(shù)組切片復(fù)制到另一個數(shù)組切片中,如果加入的兩個數(shù)組切片不一樣大,就會按照其中較小的那個數(shù)組切片的元素個數(shù)進(jìn)行復(fù)制。
copy( destSlice, srcSlice []T) int
其中 srcSlice 為數(shù)據(jù)來源切片,destSlice 為復(fù)制的目標(biāo)(也就是將 srcSlice 復(fù)制到 destSlice),目標(biāo)切片必須分配過空間且足夠承載復(fù)制的元素個數(shù),并且<font color=“red”>來源和目標(biāo)的數(shù)據(jù)類型必須一致</font>,copy() 函數(shù)的返回值表示實(shí)際發(fā)生復(fù)制的元素個數(shù)。
示例代碼:
slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} copy(slice2, slice1) // 只會復(fù)制slice1的前3個元素到slice2中 fmt.Println(slice2) // output [1 2 3]
slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3} copy(slice1, slice2) // 只會復(fù)制slice2的3個元素到slice1的前3個位置 fmt.Println(slice1) // output [5 4 3 4 5]
雖然通過循環(huán)復(fù)制切片元素更直接,不過內(nèi)置的 copy() 函數(shù)使用起來更加方便,copy() 函數(shù)的第一個參數(shù)是要復(fù)制的目標(biāo) slice,第二個參數(shù)是源 slice,兩個 slice 可以共享同一個底層數(shù)組,甚至有重疊也沒有問題。示例代碼如下:
// 通過循環(huán)的方式演示切片 slice1 := make([]int, 2, 110) slice2 := make([]int, 2, 110) slice1[0] = 1 slice1[1] = 2 slice2[0] = 5 fmt.Println(slice1)// [1 2] fmt.Println(slice2)// [5 0] // 將切片1復(fù)制到切片2 for i := 0; i < 2; i++ { slice2[i] = slice1[i] } fmt.Println(slice2)// [1 2] // 將切片2復(fù)制到切片1 for i := 0; i < 2; i++ { slice1[i] = slice2[i] } fmt.Println(slice1)// [5 0]
引用和復(fù)制
package main import "fmt" func main() { // 設(shè)置元素?cái)?shù)量為1000 const elementCount = 1000 // 預(yù)分配足夠多的元素切片 srcData := make([]int, elementCount) // 將切片賦值 for i := 0; i < elementCount; i++ { srcData[i] = i } // 引用切片數(shù)據(jù) refData := srcData // 預(yù)分配足夠多的元素切片 copyData := make([]int, elementCount) // 將數(shù)據(jù)復(fù)制到新的切片空間中 copy(copyData, srcData) // 修改原始數(shù)據(jù)的第一個元素 srcData[0] = 999 // 打印引用切片的第一個元素 fmt.Println(refData[0]) // 打印復(fù)制切片的第一個和最后一個元素 fmt.Println(copyData[0], copyData[elementCount-1]) // 復(fù)制原始數(shù)據(jù)從4到6(不包含) copy(copyData, srcData[4:6]) for i := 0; i < 5; i++ { fmt.Printf("%d ", copyData[i]) } }
執(zhí)行邏輯:
第 8 行,定義元素總量為 1000。
第 11 行,預(yù)分配擁有 1000 個元素的整型切片,這個切片將作為原始數(shù)據(jù)。
第 14~16 行,將 srcData 填充 0~999 的整型值。
第 19 行,將 refData 引用 srcData,切片不會因?yàn)榈忍柌僮鬟M(jìn)行元素的復(fù)制。
第 22 行,預(yù)分配與 srcData 等大(大小相等)、同類型的切片 copyData。
第 24 行,使用 copy() 函數(shù)將原始數(shù)據(jù)復(fù)制到 copyData 切片空間中。
第 27 行,修改原始數(shù)據(jù)的第一個元素為 999。
第 30 行,引用數(shù)據(jù)的第一個元素將會發(fā)生變化。
第 33 行,打印復(fù)制數(shù)據(jù)的首位數(shù)據(jù),由于數(shù)據(jù)是復(fù)制的,因此不會發(fā)生變化。
第 36 行,將 srcData 的局部數(shù)據(jù)復(fù)制到 copyData 中。
第 38~40 行,打印復(fù)制局部數(shù)據(jù)后的 copyData 元素。
切片的復(fù)制,是在內(nèi)存另外的分配,將被分配的空間分配到目標(biāo)空間。原空間發(fā)生變化,新分配的空間則不會受影響。切片的引用則會收到影響。
切片的刪除
切片本身不帶刪除的函數(shù)操作。只能使用切片自身的特性來進(jìn)行操作。刪除切片有如下三中情況,刪除開頭,刪除結(jié)尾,刪除中間。
刪除開頭
// 刪除切片開頭元素 // 1.使用切片的截取方法 slice = []int{1, 2, 3} slice = slice[1:] // 刪除開頭1個元素 slice = slice[N:] // 刪除開頭N個元素 // 2.使用切片中的append()函數(shù) slice = []int{1, 2, 3} slice = append(slice[:0], slice[1:]...) // 刪除開頭1個元素 slice = append(slice[:0], slice[N:]...) // 刪除開頭N個元素 // 3.使用切片的copy()函數(shù) slice = []int{1, 2, 3} slice = slice[:copy(slice, slice[1:])] // 刪除開頭1個元素 slice = slice[:copy(slice, slice[N:])] // 刪除開頭N個元素
使用append()函數(shù),不移動數(shù)據(jù)指針,但是將后面的數(shù)據(jù)向開頭移動,可以用 append 原地完成(所謂原地完成是指在原有的切片數(shù)據(jù)對應(yīng)的內(nèi)存區(qū)間內(nèi)完成,不會導(dǎo)致內(nèi)存空間結(jié)構(gòu)的變化)。
刪除中間
// 刪除中間 slice = []int{1, 2, 3, ...} slice = append(slice[:i], slice[i+1:]...) // 刪除中間1個元素 slice = append(slice[:i], slice[i+N:]...) // 刪除中間N個元素 slice = slice[:i+copy(slice[i:], slice[i+1:])] // 刪除中間1個元素 slice = slice[:i+copy(slice[i:], slice[i+N:])] // 刪除中間N個元素
刪除結(jié)尾
// 刪除結(jié)尾 slice = []int{1, 2, 3} slice = slice[:len(slice)-1] // 刪除尾部1個元素 slice = slice[:len(slice)-N] // 刪除尾部N個元素
指定位置
// 刪除切片的指定位置 seq := []string{"a", "b", "c", "d", "e"} // 指定刪除位置 index := 2 // 查看刪除位置之前的元素和之后的元素 fmt.Println(seq[:index], seq[index+1:]) // 將刪除點(diǎn)前后的元素連接起來 seq = append(seq[:index], seq[index+1:]...) fmt.Println(seq)
排序
// 整型排序 sli := []int{1, 5, 3, 4} sort.Ints(sli) for index, value := range sli { fmt.Println(index, value) } fmt.Println("---------------") // 字符串排序 sliStr :=[]string{"lisi", "zhangsan", "bruce"} sort.Strings(sliStr) fmt.Println(sliStr) fmt.Println("---------------") // 浮點(diǎn)型排序 sliFloat := []float64{12.56, 12.12} sort.Float64s(sliFloat) fmt.Println(sliFloat)
// output
0 1
1 3
2 4
3 5
---------------
[bruce lisi zhangsan]
---------------
[12.12 12.56]
迭代器
Go語言有個特殊的關(guān)鍵字 range,它可以配合關(guān)鍵字 for 來迭代切片里的每一個元素,如下所示:
// 創(chuàng)建一個整型切片,并賦值 slice := []int{10, 20, 30, 40} // 迭代每一個元素,并顯示其值 for index, value := range slice { fmt.Printf("Index: %d Value: %d\n", index, value) } // output Index: 0 Value: 10 Index: 1 Value: 20 Index: 2 Value: 30 Index: 3 Value: 40
到此這篇關(guān)于一文詳解Golang中的切片數(shù)據(jù)類型的文章就介紹到這了,更多相關(guān)Golang切片內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang迭代如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解
這篇文章主要為大家介紹了Golang迭代之如何在Go中循環(huán)數(shù)據(jù)結(jié)構(gòu)使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Go調(diào)度器學(xué)習(xí)之goroutine調(diào)度詳解
這篇文章主要為大家詳細(xì)介紹了Go調(diào)度器中g(shù)oroutine調(diào)度的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-03-03golang?sync.Cond同步機(jī)制運(yùn)用及實(shí)現(xiàn)
在?Go?里有專門為同步通信而生的?channel,所以較少看到?sync.Cond?的使用,不過它也是并發(fā)控制手段里的一種,今天我們就來認(rèn)識下它的相關(guān)實(shí)現(xiàn),加深對同步機(jī)制的運(yùn)用2023-09-09