Go語言中數組、字符串和切片三者是密切相關的數據結構。這3種數據類型,在底層原始數據有着相同的內存結構,在上層,由於語法的限制而有着不一樣的行爲表現。數組
Go語言的數據是一種值類型,雖然數組的元素能夠被修改,可是數組自己的賦值和函數傳參都是以總體複製的方式處理的。數據結構
Go語言字符串底層數據也是對應的字節數組,可是字符串的只讀屬性禁止了在程序中對底層字節數組的元素的修改。字符串賦值只是複製了數據地址和對應的長度,而不會導師底層數據的複製。app
切片的底層數據雖然也是對應數據類型的數組,可是每一個切片還有獨立的長度和容量信息,切片賦值和函數的傳參時也是將切片頭信息部分按傳值方式處理。由於切片頭含有底層數據的指針,因此它的賦值也不會致使底層數據的複製。ide
1、數組函數
數組是一個由固定長度的特定類型元素組成的序列,一個數組能夠由零個或多個元素組成。數組的長度是數組類型的組成部分。由於數組長度是數組的一部分,不一樣長度或不一樣類型的數據組成的都是不一樣的類型,因此在Go語言中不多直接使用數組。和數組對應的類型是切片,切片是能夠動態增加和收縮的序列,切片的功能也更靈活。性能
數組定義方式:編碼
var a [3]int //定義長度爲3的int型數組,元素所有爲0 var b = [...]int{1, 2, 3} //定義長度爲3的int型數組,元素爲1,2,3 var c = [...]int{2: 3, 1: 2} //定義長度爲3的int型數組,元素爲0,2,3 var d = [...]int{1,2,4:5,6} //定義長度爲6的int型數組,元素爲1,2,0,0,5,6
第一種方式是定義一個數組變量的最基本的方式,數組長度明確指定,數組中的每一個元素都以0值初始化spa
第二種方式是定義數組,能夠在定義的時候順序指定所有元素的初始化值,數組的長度根據初始化元素的數目自動計算指針
第三種方式是以索引的方式來初始化數組的元素,所以元素的初始化值出現順序比較隨意。這種初始化方式和map[int]Type類型的初始化語法相似。數組的長度以出現的最大索引爲準,沒有明確初始化的元素依然用0值初始化code
第四種方式是混合了第二種和第三種的初始化方式,前面兩個元素採用順序初始化,第三個和第四個元素以0值初始化,第五個元素經過索引初始化,最後一個元素跟在前面的第五個元素以後採用順序初始化。
Go語言數組是值定義。一個數組變量即表示整個數組,它並不隱式的指向第一個元素的指針,而是一個完整的值。
var a = [...]int{1, 2, 3} //a是一個數組 var b = &a //b是指向數組的指針 fmt.Println(a[0], a[1]) //打印數組的前兩個元素 fmt.Println(b[0], b[1]) //打印數組指針訪問數組元素的方式和經過數組相似 for i, v := range b { //經過數組指針迭代數組的元素 fmt.Println(i, v) }
其中b是指向數組a的指針,可是經過b訪問數組中元素的寫法和a是相似的。還能夠經過for range 來迭代數組指針指向的數組元素。
能夠將數組看作一個特殊的機構體,結構的字段名對應數組的索引,同時結構體成員的數目是固定的。內置函數len()能夠用於計算數組的長度,cap()函數用於計算數組的容量。
數組不只能夠定義數值數組,還能夠定義字符串數組、結構體數組、函數數組、接口數組、通道數組等
2、字符串
一個字符串是一個不可改變的字節序列,和數組不一樣的是,字符串的元素不可修改,是一個只讀的字節數組。字符串的結構有兩個信息組成:第一個是字符串指向的底層字節數組;第二個是字符串的字節長度。如下是go語言源碼中對string類型的說明:
// string is the set of all strings of 8-bit bytes, conventionally but not // necessarily representing UTF-8-encoded text. A string may be empty, but // not nil. Values of string type are immutable. type string string
字符串是8位字節字符的集合,默認以utf-8編碼,能夠是空的可是不能是nil,字符串是隻讀的。
字符串雖然不是切片,可是支持切片操做,不一樣位置的切片底層訪問的是同一塊內存數據
s := "hello, world" hello := s[:5] world := s[7:]
字符串和數組相似,內置的len()函數返回字符串的長度
3、切片
切片(slice)是一種簡化版的動態數組。
切片的定義方式:
var (
a [] int //nil切片,和nil相等,通常用來表示不存在的切片
b = []int{} //空切片,和nil不相等,通常用來表示一個空的集合
c = []int{1, 2, 3} //有3個元素的切片,len和cap都爲3
d = c[:2] //有2個元素的切片,len爲2,cap爲3
e = c[0:2:cap(c)] //有2個元素的切片,len爲2,cap爲3
f = make([]int,3) //有3個元素的切片,len和cap都爲3
)
和數組同樣,內置的len()函數返回切片中有效元素的長度,內置的cap()函數返回切片容量的大小,容量必須大於或等於切片的長度。
添加切片元素
內置的泛型函數append()能夠在切片的尾部追加N個元素:
var a []int a = append(a, 1) //追加一個元素 a = append(a, 1, 2, 3) //追加多個元素,手寫解包方式 a = append(a, []int{1, 2, 3}...) //追加一個切片,切片須要解包
注意,在容量不足的狀況下,append()操做會致使從新分配內幕才能,可能致使巨大的內存分配和複製數據的代價。及時容量足夠,依然須要用append()函數的返回值來更新切片自己,由於新切片的長度已經發生了變化。
除了在切片尾部追加,還能夠在切片的開頭添加元素:
var a = []int{1, 2, 3} a = append([]int{0}, a...) a = append([]int{-3, -2, -1}, a...)
在開頭通常會致使內存的從新分配,並且會致使已有的元素所有複製一次,所以,從切片開頭添加元素的性能通常要比從尾部追加元素的性能差不少。
因爲append()函數返回新的切片,也就是它支持鏈式操做,所以咱們能夠將多個append()操做組合起來,實如今切片中間插入元素:
var a []int a = append(a[:i], append([]int{x}, a[i:]...)...) //在第i個位置插入x a = append(a[:i], append([]int{1, 2, 3}, a[i:]...)...) //在第一個位置插入切片
用copy()和append()組合能夠避免建立中間的臨時切片,一樣完成添加元素的操做:
a = append(a, 0) //切片擴展一個空間 copy(a[i+1], a[i:]) //a[i:]向後移動一個位置 a[i] = x //設置新添加的元素
操做語句雖然冗餘了一些,可是相比以前的方法能夠減小中間建立的臨時切片。
2.刪除切片元素
根據要刪除的元素的位置,有從開頭位置刪除、從中間位置刪除和從尾部刪除3種狀況,其中刪除切片尾部的元素最快:
a = []int{1, 2, 3} a = a[:len(a) - 1] //刪除尾部1個元素 a = a[:len(a) - n] //刪除尾部n個元素
刪除開頭的元素能夠直接移動數據指針:
a = []int{1, 2, 3} a = a[1:] //刪除開頭1個元素 a = a[N:] //刪除開頭N個元素
刪除中間的元素,須要對剩餘的元素進行一次總體挪動,一樣能夠用append()或copy()原地完成
a = []int{1, 2, 3, ...} a = append(a[:i], a[i+1:]...) //刪除中間1個元素 a = append(a[:i], a[i+N:]...) //刪除中間N個元素 a = a[:i+copy(a[i:], a[i+1:])] //刪除中間1個元素 a = a[:i+copy(a[i:], a[i+N:])] //刪除中間N個元素