1. 程式人生 > 其它 >Go語言基礎五:引用型別-切片和對映

Go語言基礎五:引用型別-切片和對映

切片

Go的陣列長度不可以改變,在某些特定的場景中就不太適用了。對於這種情況Go語言提供了一種由陣列建立的、更加靈活方便且功能強大的包裝(Wapper),也就是切片。與陣列相比切片的長度不是固定的,可以追加元素。

切片本身不擁有任何資料,它們只是對現有陣列的引用。

切片的定義

可以宣告一個未指定大小的陣列來定義切片:

var slice_name []type
//type用來指定切片內元素的資料型別

切片不需要宣告長度。還可以使用make()函式來建立切片:

var slice_name []type = make([]type, len)
//len是切片的長度
slice_name := make([]type,len)
//make()函式定義時,也可以指定切片的容量,其中capacity為可選引數
make([]type, length, capacity)

切片的初始化

直接初始化

直接初始化一個切片,[]表示是切片型別,{1,2,3}初始化值依次是1,2,3,對於下面這個切片,初始長度為3容量為3

slice_name := []int {1,2,3}
引用陣列或切片
//初始化切片slice,是陣列array的引用
slice := array[:]
//將array中從下標startIndex到下標endIndex-1下的元素建立為一個新的切片
slice := array[startIndex:endIndex]
//endIndex為空時,從索引為的startIndex元素到最後一個元素
slice := array[startIndex:]
//startIndex空時,從第一個元素(索引為0)到索引為endIndex-1的元素
slice := array[:endIndex]
//通過切片s初始化切片s1
s1 := s[startIndex:endIndex] 
make()函式

使用make()函式建立切片時,預設情況下切片的元素的值為0。

切片的修改

切片它自己不擁有任何資料,他只是對底層陣列的一種表示,對切片所做的任何修改都會反映在底層的陣列當中。

當多個切片共用相同的底層陣列時,每個切片的修改都將會反映在這個陣列中。

package main
import (
	"fmt"
)
func main() {
    arr := [3]int {1,2,3}
    slice_1 := arr[:]
    slice_2 := arr[:]
    fmt.Println("原始陣列",arr)
    slice_1[1] = 10
    fmt.Println("修改slice_1之後的陣列",arr)
    slice_2[2] = 20
    fmt.Println("修改slice_2之後的陣列",arr)
}

執行結果

原始陣列 [1 2 3]
修改slice_1之後的陣列 [1 10 3]
修改slice_2之後的陣列 [1 10 20]

從輸出中可以清晰的看出,對切片的修改會反映在底層陣列當中;當多個切片共享一個底層陣列時,每個切片所作出的修改都會反映在陣列中。

切片的長度和容量

切片的長度是切片中的元素數。切片的容量是從建立切片的索引開始的底層陣列中的元素數。

可以內建函式len()測量切片的長度,內建函式cap()獲取切片的容量。

package main
import (
	"fmt"
)
func main() {
    arr := [...]string{"A","B","C","D","E","F"}
    s := arr[1:3]
    fmt.Printf("切片的長度為%d,切片的容量為%d",len(s),cap(s))
    s = s[:1]
    fmt.Printf("切片的長度為%d,切片的容量為%d",len(s),cap(s))

}

輸出結果為:

切片的長度為2,切片的容量為5

在上述程式中,s是由從arr的索引1到2建立的一個切片。因此切片s的長度為2

切片s是從arr[1]開始,即索引從1開始建立的。根據切片容量的定義,可以得知,s的容量為從此索引開始的底層陣列arr中的元素數也就是5。

所以切片的長度為2,切片的容量為5。

追加和移除切片元素

追加元素

正如我們已經知道的,陣列的長度是固定的,它的長度是不能增加。切片是動態的,使用append()函式可以將新元素追加到切片上。append() 函式的定義是 func append(s[]T,x ... T)[]T。此函式只能將元素追加到切片末尾。

x ... T在函式的定義中表示該函式接受引數x的個數是可變的。這些型別的函式被稱為可變函式

append()函式的第二引數可以是一個數字,也可以是一組陣列。例

append(slice,1)或append(slice,1,2,3)或append(slice,slice2...)`

正如我們所瞭解的,切片的對陣列的一種抽象、一種包裝,但是陣列本身的長度是固定的,那麼切片是如何具有動態長度的?而且切片的容量是由他所引用的陣列的長度決定的,那麼當新的元素新增到切片當中,使得切片的長度變大以後超過了這個切片的容量,這個切片和它引用的陣列發生了什麼變化呢?

當新的元素新增到切片中時,系統會建立一個新的陣列,將現有的陣列元素複製到這個新的陣列當中,並且返回這個新陣列的新切片引用。並且新的切片容量為舊切片的兩倍。

package main
import(
	"fmt"
)
func main() {
    arr := [...]string{"A","B","C","D","E","F"}
    s1 := arr[:]
    fmt.Println("s1",s1,"長度為",len(s1),"容量為",cap(s1))
    s1 = append(s1,"G")
    fmt.Println("新增一個元素後s1",s1,"長度為",len(s1),"容量為",cap(s1))
	s1[0] = "1"
	fmt.Println(arr,s1)
}

輸出結果

s1 [A B C D E F] 長度為 6 容量為 6
新增一個元素後s1 [A B C D E F G] 長度為 7 容量為 12
[A B C D E F] [1 B C D E F G]

在上述程式當中,s1是由arr宣告的切片,其初始長度為6,初始容量為6。當向其中新增一個新的元素,這時系統會在建立一個新的陣列,並返回一個新的切片給s1,這時s1的長度為7,容量為12。可以看到容量翻了一倍。此時切片s1所表示的陣列已經不是arr,而是系統新建立的陣列。

追加元素到切片頭部

如果想把元素追加到切片的開頭,Go沒有提供原生的函式。可以通過append()函式變相實現,這種方法實際上是合併兩個切片。

nums := []int {1,2,3}
//... 三個點表示將切片元素展開傳遞給函式
nums = append([]int{4},nums...)
fmt.Println(nums)
//輸出
[4,1,2,3]
移除元素

使用切片子集和append()函式變相實現。

nums := []int {1,2,3,4,5}
nums = append(nums[:2],nums[3:]...)
fmt.Println(nums)
//輸出
[1,2,4,5]

切片的拷貝

從上面我們可以瞭解到切片是對一個數組的引用,因此不能向陣列那樣直接賦值給一個新的變數就會產生拷貝。

陣列是值型別,對陣列而言,將一個數組賦值給另一個新的陣列,那麼這裡會產生一次拷貝。直白地說,賦值之後兩個陣列是不一樣的他們指向地地址不同,一個數組發生變化,另一個數組不會發生變化。

切片是一個結構體,是對底層陣列地一個引用。將切片的值賦值給另一個新的切片,這兩個切片都是引用的同一個陣列。對切片地修改會對映引用的底層陣列上,那麼修個一個切片的值,另一個切片的值也會發生變化。

如果希望兩個切片引用的不是同一個陣列,就需要用到copy()函式完成對切片的拷貝。

copy(dst, src []Type)第一個引數為目標切片,第二個引數為源切片。

package main
import(
	"fmt"
)
func main() {
	arr := [...]int{1,2,3}
    s1 := arr[:]
    s2 := s1
    s1_copy := make([]int,3)
    copy(s1_copy,s1)
    s1[0] = 10
    fmt.Println("s1值為:",s1,",s2值為:",s2,",s1_copy值為:",s1_copy)
    
}

輸出結果

s1值為: [10 2 3] ,s2值為: [10 2 3] ,s1_copy值為: [1 2 3]

上述程式碼中s1是對arr引用過的一個切片;s2是由s1賦值而來的切片;s1_copy是由s1拷貝而來的切片。由輸出結果可知,賦值會將兩個切片指向同一個底層陣列,使用copy()函式會將源切片的值複製到新的切片當中。

切片在函式傳遞

我們可以認為,切片在內部可以由一個結構體型別表示。這是它的表現形式。

type slice struct {
	Length	int
	Capactiy	int
	ZerothElement *byte
}

切片包含長度、容量和指向陣列第零個元素的指標。當切片傳遞給函式時,即使他通過值傳遞,指標變數也將引用相同的底層陣列。因此,當切片作為引數傳遞給函式時,函式內做的更改,也會在函式外可見。

在Go語言中我們通常不會把陣列的指標作為引數在函式中傳遞,而是使用切片進行傳遞。切片就是一種對陣列的引用,這樣會使程式碼更加簡潔。

多維切片

多維切片類似與多維陣列,唯一的不同在於多維切片沒有指明長度。

package main
import(
    "fmt"
)
func main() {
    nums := [][]int{
        {1,2,3},
        {10,20,30},
        {100,200,300}
    }
    for _,v1 := range nums {
        for _,v2 := range v1 {
            fmt.Printf("%d,",v2)
        }
        fmt.Printf("\n")
    }
}

執行結果

1,2,3,
10,20,30,
100,200,300,

tips

  1. 切片是對底層陣列的引用。只要切片還在記憶體中,這個被引用的底層陣列就不能被垃圾回收。如果我們需要對很大的一個數組的一個小片段進行處理,那麼我們可以由這個陣列建立一個切片,並開始處理切片。但是同時這個很大的輸入仍然在記憶體中,一種解決方法就是使用copy()函式來生成一個切片副本,這樣我們就可以使用新的切片,原始的陣列就會被垃圾回收
  2. *切片的長度是當前切片可以訪問元素的數量,而切片的容量則是當前切片拓展後能訪問的元素的個數。當然這些可以訪問的元素都是底層陣列中的元素
  3. 切片是一個結構體,它由一個指向陣列中元素的指標、長度、容量來組成的。因此切片這個結構體型別同時具有引用型別的特徵和值型別的特徵
  4. 使用append()函式為切片新增元素時,如果使切片的長度超過了容量時,會返回一個新的切片,這個新切片的容量不一定為舊切片容量的2倍,具體參考這裡

對映

GO語言中的Map是一種鍵值對的無序列表。可以快速並高效地查詢唯一鍵的值。

Map是一種集合,所以可以像迭代陣列和切片那樣迭代它。不過,Map是無序的集合,因為它底層是一個hash表,因此無法決定它的返回順序。

Map的定義和初始化

使用make()函式

map_name := make(map[key_type]value_type)
map_name[key] = value

make(map[keyType]valueType, cap)cap表示容量,作為可選引數。

使用make()函式建立map建立時,可以指定一個合理的初始容量大小,這樣就會申請一塊合適的記憶體,避免在後續的使用中頻繁擴浪費效能。

使用make()函式建立map,其中沒有資料且不是零值。

map的零值為nil

使用字面值建立

var map_name = map[key_type]value_type{key1:v1,k2:v2}
或
map_name := map[key_type]value_type{key1:v1,k2:v2}

定義一個map後,如果不初始化map,那麼就會建立一個nil mapnil map不能用來存放鍵值對。

//定義一個map但是不初始化值
var student map[int]string 
//如果將值賦給這個map編譯就會報錯
student[1] = "bob"

執行結果

panic: assignment to entry in nil map

goroutine 1 [running]:
main.main()
	/box/main.go:9 +0x43

Exited with error status 2

Map的資料操作

key-value

map中新增資料的語法與陣列是類似的。

students[1] = "zs"

在Go語言中允許多返回值,在map中根據key獲取value,在返回value的同時會返回一個boolean型別的值,這個值用表示地是map中是否存在對應key。如果不存在對應keyvalue,則返回mapvalue資料型別對應的零值。當然也可以只返回一個value

//根據key獲取map中的資料
value := students[1]
//exists為true說明存在為false說明不存在,如過存在返回對應值,否則返回對應資料型別零值這裡string零值為""空字串
value,exists := students[1]

range()函式迭代Map

range()迭代map,返回key和value

package main
import "fmt"
func main() {	
	students := map[int]string{
        1:"zs",
        2:"ls",
        3:"ww",
	}
    //使用range()函式迭代Map 
    for key, value := range students {
        fmt.Printf("Key: %d Value: %s\n", key, value)
    }
}

delete()函式刪除Map中元素

delete(map,key)函式可以刪除map中指定key的鍵值對。接上面的程式碼。delete()函式沒有返回值。

//刪除鍵為1的鍵值對
delete(map,1)
//輸出students中所有鍵值對
for key, value := range students {
	fmt.Printf("Key: %d Value: %s\n", key, value)
}

len()函式獲取Map的長度

使用len()函式可以用來獲取map的長度

l := len(students)

map的函式傳遞

與切片一樣,對映也是一個引用型別,它們都都指向了底層的資料結構。切片指向的資料結構是陣列,而對映指向的資料結構是散列表。當對映作為引數在函式之間傳遞時,是進行map指標的拷貝,相對於指標來說是值拷貝,相對於底層來說是引用傳遞。直白的說就是進行引數傳遞時它們引用的底層資料結構都是一個,也就是對與一個發生變化另一個也會變化。

package main

import (  
    "fmt"
)

func main() {  
   	students := map[int]string{
        1:"zs",
        2:"ls",
        3:"ww",
    }
    fmt.Println("原始的students", students)
    modified := students
    modified[1] = "zss"
    fmt.Println("修改之後的students", students)

}

執行結果

原始的students map[1:zs 2:ls 3:ww]
修改之後的students map[1:zss 2:ls 3:ww]

Maps equality

對映之間不能使用==來進行比較操作。==只能用於檢查對映是否為nil

package main
func main() {  
    map1 := map[string]int{
        "one": 1,
        "two": 2,
    }
    map2 := map1
    if map1 == map2 {
    }
}

輸出結果:錯誤

./prog.go:11:10: invalid operation: map1 == map2 (map can only be compared to nil)

Go build failed.