文章由作者馬志國在博客園的原創,若轉載請於明顯處標記出處:http://www.cnblogs.com/mazg/
數組是由同構的元素組成。結構體是由異構的元素組成。數據和結構體都是有固定內存大小的數據結構。相比之下,切片和映射則是動態的數據結構,它們根據需要動態增長。
4.1 數組
數組是一系列同一類型數據的集合,數組中包含的每個數據被稱為數組元素。一個數組包含的元素個數稱為數組的長度,數組長度是固定的。一個數組可以由零個或多個元素組成。
1 數組聲明
數組聲明的一般形式:
var 數組名 [長度]類型 |
示例如下:
var arr [10]int //10個元素的整型數組 var ptrs [5]*float64 //5個元素的指針數組,每個指針都指向float64類型 var points [8]struct{ x, y int } //8個元素的結構體類型 var arry [2][3]int //2*3的二維整型數組 |
2 簡短聲明
與變量的簡短聲明一樣,數組也可以簡短聲明。如果在數組的長度位置出現的是“...”省略號,則表示數據的長度是根據初始化值得個數來計算。
a := [3]int{1, 2, 3} // 長度為3的數組 b := [5]int{1, 2, 3} //長度為10,前三個元素為1、2、3,其它默認為0 c := [...]int{4, 5, 6} //長度3的方式,Go自動計算長度 r := [...]int{9: 6} //長度為10,最后一個元素的值為6,其它默認為0 arr2 := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}//二維數組 |
3 元素訪問
數組的每個元素通過索引下標來訪問,內置的len函數將返回數組中元素的個數。
arr := [...]int{1, 2, 3, 4, 5} len := len(arr) //len獲取數組長度 fmt.Println("修改前:", arr) arr[0] = 100 //下標訪問數組元素 fmt.Println("修改后:", arr) fmt.Println("數組長度:", len) 打印結果: 修改前: [1 2 3 4 5] 修改后: [100 2 3 4 5] 數組長度: 5 |
4 數組遍歷
兩種遍歷方式,其中range表達式遍歷有兩個返回值,第一個是索引,第二個是元素的值。示例如下:
arr := [...]int{1, 2, 3, 4, 5} len := len(arr) //len獲取數組長度 for i := 0; i < len; i++ { fmt.Println(i, arr[i]) } for i, v := range arr { fmt.Println(i, v) } |
5 作為函數參數
在Go語言中,數組作為函數的參數仍然是值傳遞,雖然可以使用數組的指針來代替,但是改變不了數組長度。這時,我們通常使用切片slice來替代數組。
6 數組比較
如果數組元素的類型是可比較的,那么這個數組也是可的比較。只有數組的所有元素都相等數組才是相等的。由於長度也是數組類型的一部分,所以長度不同的數組是不等的。
數組可遍歷、可修改,是否可比較,由數組元素決定。%T用於顯示一個值對應的數據類型。
4.2 數組切片(slice)
數組的長度在定義之后無法修改。數組是值類型,每次傳遞都將產生一份副本。顯然這無法滿足開發者的某些需求。Go語言提供了數組切片(slice)來彌補數組的不足。數組和slice之間有着緊密的聯系,一個slice是一個輕量級的數據結構,提供了訪問數組子序列元素的功能,而且slice的底層確實引用一個數組對象。
1 聲明數組切片(slice)
數組切片與數組聲明非常相似,唯一區別是無需指定長度。
var 數組切片 []類型 var slice []int |
2 基於數組以及基於切片創建切片
arr := [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} //數組 slice1 := arr[0:5] //基於數組切片1,左閉右開 slice2 := slice1[1:3] //基於切片1創建切片2 fmt.Println(slice1) //[1 2 3 4 5] fmt.Println(slice2) //[2 3] |
3 直接創建切片
Go語言提供的內置函數make()可以用於靈活的創建數組切片。make()函數創建一個指定元素類型、長度和容量的slice。容量部分可以省略,在這種情況下,容量將等於長度。在底層,make創建了一個匿名的數組變量,然后返回一個slice。只有通過返回的slice才能引用匿名的數組變量。
make([]T,len) make([]T,len,cap) |
內置的len()函數獲取長度,內置的cap()函數獲取容量。
slice1 := make([]int, 5) slice2 := make([]int, 5, 10) slice3 := []int{1, 2, 3, 4, 5} fmt.Println(slice1, len(slice1), cap(slice1)) fmt.Println(slice2, len(slice2), cap(slice2)) fmt.Println(slice3, len(slice3), cap(slice3)) 打印結果: [0 0 0 0 0] 5 5 [0 0 0 0 0] 5 10 [1 2 3 4 5] 5 5 |
與數組相同,slice操作不能超出len指定的范圍。
slice := make([]int, 5, 10) slice[3] = 10 //正確 slice[8] = 8 //錯誤 ,索引超出范圍 |
4 切片的遍歷
切片的遍歷與數組的遍歷方式相同。
5 切片不能比較
與數組不同的是slice之間不能比較,因此我們不能使用==操作符來判斷兩個slice是否含有全部相等的元素。不過標准庫提供了高度優化的bytes.Equal函數兩個字節型slice是否相等,但是對於其它類型的slice,我們必須自己展開每個元素進行比較。
切片可遍歷,可修改,不可比較
6 判斷切片是否為空
使用len(s)==0來判斷一個slice是否為空。
7 追加元素
內置的append函數用於向slice追加元素。可以直接追加元素,也可以追加一個slice。注意參數slice后有...。否則有語法錯誤。因為append()函數的語義是從第二個參數開始都應該是待附加的元素。slice后加...意味將slice的元素全部打散后傳入。數組切片會自動處理存儲空間不足的問題。如果追加的內容長度超過當前已分配的存儲空間(即cap()調用返回的信息),數組切片會自動分配一塊足夠大的內存。
slice := make([]int, 5, 10) slice = append(slice, 1, 2, 3) fmt.Println(slice) slice2 := []int{4, 5, 6} slice = append(slice, slice2...) fmt.Println(slice) 打印結果: [0 0 0 0 0 1 2 3] [0 0 0 0 0 1 2 3 4 5 6] |
8 切片復制
內置的copy函數用於數組切片的復制。復制時無需考慮目標數組和源數組的長度。
slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{7, 8, 9} copy(slice2, slice1) //只會將slice1的前3個元素賦值給slice2 fmt.Println(slice2) slice3 := []int{1, 2, 3, 4, 5} slice4 := []int{7, 8, 9} copy(slice3, slice4) //將slice4的元素賦值slice3的前3個元素 fmt.Println(slice3) 打印結果: [1 2 3] [7 8 9 4 5] |
9 作為函數參數時切片與數組的區別
func SetValueByArray(arr [5]int) { arr[0] = 100 }
func SetValueBySlice(slice []int) { slice[0] = 100 } func main() { arr := [5]int{1, 2, 3, 4, 5} SetValueByArray(arr) fmt.Println(arr) slice := arr[:] SetValueBySlice(slice) fmt.Println(arr) } //打印結果: [1 2 3 4 5] [100 2 3 4 5] |
10字符串和byte切片
標准庫中提供了4個與字符串操作相關的包:
包 |
功能 |
strings |
提供了字符串查詢、替換、比較、截斷、拆分和合並等功能。 |
bytes |
提供了很多與strings包類似的功能。因為字符串是只讀的,逐步構建字符串會導致很多分配和復制,這種情況下,使用bytes.Buffer類型將會更有效。 |
strconv |
提供了布爾類型、整數、浮點數和對應字符串的相互轉換,還提供了雙引號轉義相關的轉換。 |
unicode |
提供了IsDigit、IsLetter、IsUpper和IsLower等功能,用於給字符分類。 |
strings包常用的6個函數,功能參考Go中文幫助文檔
byte包常用的的6個函數,功能與strings類似。
它們之間的唯一區別就是字符串類型的參數替換成了字節slice類型的參數。
4.3 映射
映射是一個無序的鍵值對集合,其中所有的鍵都是不同的,然后通過給定的鍵可以在常數時間復雜度內檢索、更新或刪除對應的值。在Go語言中,一個map就是一個哈希表的引用,映射中所有的鍵都有相同的類型,所有的值也有着相同的類型,但是鍵和值之間可以是不同的數據類型。
1 聲明映射
聲明的一般格式為:
var 映射名稱 map[鍵]值 |
只是聲明一個map,它的初始值是nil,也就是沒有引用任何哈希表。所以不能向一個nil值的map存入元素。
var ages map[string]int fmt.Println(age == nil)//”true” fmt.Println(len(ages)== 0)//”true” |
2 創建映射
內置的make函數可以創建一個map,創建后可以存入元素。
myMap1 := make(map[string]int) //創建一個map myMap2 := make(map[string]int, 100) //創建一個map,初始儲存能力為100 myMap3 := map[string]int{ "str1": 10, "str2": 20, "str3": 30} //直接創建,並初始化 fmt.Println(myMap1, len(myMap1)) fmt.Println(myMap2, len(myMap2)) fmt.Println(myMap3, len(myMap3)) 打印結果: map[] 0 map[] 0 map[str3:30 str1:10 str2:20] 3 |
3 元素的賦值和刪除
元素可以直接賦值,內置的delete函數用於刪除元素,刪除一個不存在的元素,不會造成錯誤。
myMap3 := map[string]int{"str1": 10, "str2": 20, "str3": 30} fmt.Println(myMap3, len(myMap3)) myMap3["str5"] = 50 fmt.Println(myMap3, len(myMap3)) delete(myMap3, "str") delete(myMap3, "str3") fmt.Println(myMap3, len(myMap3)) 打印結果: map[str3:30 str1:10 str2:20] 3 map[str5:50 str1:10 str2:20 str3:30] 4 map[str1:10 str2:20 str5:50] 3 |
4 查找元素
有時候可能需要知道對應的元素是否在map中,例如,如果元素類型是一個布爾類型,你可能需要區分一個零值的元素,和因為元素不存在而返回的零值,可以像下面這樣使用:
v,ok:=map[key] if !ok{/*在map中不存在key為鍵的元素*/}
//結合起來使用 if v,ok:=map[key];!ok{/* */ } |
在這種場景下,map下標語法將產生兩個值;第二個值是一個布爾類型,用於表示元素是否存在。布爾變量一般命名為ok。示例如下:
myMap3 := map[string]int{"str1": 10, "str2": 20, "str3": 30} if v, ok := myMap3["str2"]; ok { fmt.Println(v) } |
5 遍歷映射
遍歷map使用range風格的for循環實現。由於map是無序的集合,所以每次遍歷的順序可能不一樣。
myMap3 := map[string]int{"str1": 10, "str2": 20, "str3": 30} for k, v := range myMap3 { fmt.Println(k, v) } |
6 映射不可比較
和slice一樣,map之前也不能進行相等比較;唯一的例外是和nil進行比較。要判斷兩個map是否包含相同的key和value,我們必須通過一個循環實現。有時候,我們需要一個map的key是slice類型,但是map的key必須是可比較的類型,但是slice並不滿足這個條件。我們可以通過兩個步驟繞過這個限制。第一步,定義一個輔助函數k,將slice轉為map對應的string類型的key,確保只有x和y相等時,k(x)==k(y)才成立。然后創建一個key為string類型的map,在每次對map操作時,先用k輔助函數將slice轉化為string類型。
7 不能對映射元素取址操作
map中的元素並不是一個變量,不能對map元素進行取址操作。禁止對map元素取址的原因是map可能隨着元素數量的增長而重新分配更大的內存空間,從而可能導致之前的地址無效。slice元素可以取址操作。
fmt.Println(&myMap3["str1"])//錯誤,不能取址操作 |
8 nil值映射
map上的大部分操作,包括查找、刪除、len和range循環都可以安全工作在nil值的map上,它們的行為和一個空map類似。但是向一個nil值的map存入元素將導致一個panic異常。