Go:學習筆記兼吐槽(3)
陣列
Golang 中,陣列是值型別。
陣列的宣告
var arr [10]int
陣列的初始化
var arr1 [3]int = [3]int{1, 2, 3} var arr2 = [3]int{4, 5, 6} var arr3 = [...]int{7, 8, 9} var arr4 = [...]int{1: 100, 2: 200, 3: 300}
用 for-range 遍歷陣列
基本語法:
for index, value := range 陣列變數{ }
其中:index
為陣列下標,value
是該下標位置的值。
長度是陣列型別的一部分
長度是陣列型別的一部分,就是說陣列不可以脫離長度而存在。聽起來不太明白,我們來看下面的一個示例就明白了,這真的是一個大坑。
假設,我們現在要寫一個排序函式,C# 中,我們會這樣定義:
public void Sort(int[] array) { }
但是,在 Golang 中,這是不行的。
func main() { var arr [3]int = [3]int{1, 2, 3} Sort(arr) } func Sort(array []int){ }
Sort(arr)
這句編譯就會報錯:cannot use arr (type [3]int) as type []int in argument to Sort。因為Sort
函式的引數array []int
是一個切片,不是陣列,將陣列作為引數傳給Sort
就會報型別不匹配。
如果一定需要以陣列作為引數傳遞,Sort
的引數必須定義成陣列,就是帶上長度:
func Sort(array [3]int){ }
這麼定義這函式還有啥用?吐槽一萬字…
雖然有切片可以用來實現我們的功能,但是,陣列就變得有點雞肋了。
切片 slice
切片是引用型別,類似於 C# 中的list
。內部維護一個數組,當追加元素超出切片容量時,切片自動擴容。(跟list
是一樣的機制。)
切片的宣告
var arr []int
切片的使用
//方法一: var arr1 [5]int = [5]int{1, 2, 3, 4, 5} slice1 := arr1[1: 3]//這裡的使用跟 Python 很像 //方法二: var slice2 []int = make([]int, 5, 10) //方法三: var slice3 []int = []int{1, 2, 3, 4, 5}
使用make
初始化切片,make
的三個引數依次為:切片資料型別,切片長度,切片容量。
給切片追加元素
//方法一:追加一個或多個同類型 var slice1 []int = make([]int, 5, 10) slice1 = append(slice1, 100, 200) fmt.Printf("%v\n", slice1) //方法二:追加切片(只能是切片,不可以是陣列) var slice2 []int = []int{1, 2, 3, 4, 5} slice1 = append(slice1, slice2...)// 三個點不能少 fmt.Printf("%v", slice1)
append
函式也很搞笑,其返回值必須賦值給一個切片,否則編譯都過不了。如果一個切片呼叫append
追加元素後,又賦值給了自己(我們一般也是這麼用的),則切片的地址不會發生改變(除非發生了擴容)。如果切片 1
呼叫append
後賦值給了切片 2
,則切片 1
保持未追加前的原樣不變,另生成一個新的切片賦給切片 2
。
示例:
var slice1 []int = make([]int, 5, 10) fmt.Printf("%v %p\n", slice1, &slice1)// [0 0 0 0 0] 0xc000004460 slice1 = append(slice1, 100) fmt.Printf("%v %p\n", slice1, &slice1)// [0 0 0 0 0 100] 0xc000004460 slice2 := append(slice1, 200) fmt.Printf("%v %p\n", slice1, &slice1)// [0 0 0 0 0 100] 0xc000004460 fmt.Printf("%v %p\n", slice2, &slice2)// [0 0 0 0 0 100 200] 0xc0000044e0
對映 map
就是字典。
map 的宣告
var m map[int]string
map 的使用
// 方式一:使用 make 函式 m := make(map[int]string, 10) // 方式二:直接賦值 m := map[int]string{ 1: "張三", 2: "李四", }
make
方法的第一個引數是 map 的資料型別,第二個引數是初始容量。
注意,如果是方式二直接賦值,最後一個key-value 後面也要加逗號。
刪除元素
delete(map, key)
引數:
- map:要刪除元素的 map
- key:要刪除的 key,當 key 在 map 中不存在時,不進行任何操作,也不報錯。
Golang 中map
沒有類似其他語言中的clear
方法,如果要一次性刪除全部元素,可遍歷map
逐一刪除,或者重新make
一下使其指向一個新的記憶體空間。
查詢元素
val, finded := m[1] if finded{ fmt.Println(val) }
遍歷元素
只能用 for-range 遍歷
for k, v := range m{ fmt.Printf("%v: %v\n", k, v) }
結構體 struct
- Golang 中沒有類(class) ,Go 中的結構體(struct)和其他語言中的類有同等的地位。可以理解為 Golang 是基於 struct 來實現面向物件。
- Golang 中面向物件程式設計非常簡潔,去掉了傳統 OOP 語言中的繼承、方法過載、建構函式、解構函式、隱藏的 this 指標等等。
- Golang 仍然有面向物件程式設計的封裝、繼承、多型的特性,只是實現方式和其他 OOP 語言不一樣。
- 結構體是值型別。結構體的所有欄位在記憶體中是連續的。
結構體的宣告
type 結構體名稱 struct{ field1 type field2 type } p1 := Person{} p1.Name = "Tom" p1.Age = 10 // 或者 p2 := Person{"Jerry", 5}
結構體的使用
type Person struct{ Name string Age int }
結構體指標
// 方式一: var person1 *Person = new(Person) (*person1).Name = "Tom" (*person1).Age = 10 fmt.Println(*person1) // 方式二: person2 := new(Person) person2.Name = "Tom" person2.Age = 10 fmt.Println(*person2) // 方式三: var person3 *Person = &Person{"Jerry", 5} fmt.Println(*person3)
這三種方式定義的都是結構體指標,因為是指標,所以給欄位賦值的標準方式應該是方式一的寫法,但是 Go 的設計者為了程式設計師使用方便,給出了一個語法糖,使(*person1).Name = "Tom"
簡化為person1.Name = "Tom"
,即方式二的寫法,編譯時,會自動加上取值運算。而方式三的寫法可以直接賦值。
結構體標籤
struct 的每個欄位上可以定義一個標籤(tag),該標籤可以通過反射機制獲取,最常見的使用場景就是序列化和反序列化。
type Person struct{ Name string `json:"name"` Age int `json:"age"` } p := Person{"張三", 30} jsonStr, err := json.Marshal(p) if err == nil { fmt.Println(string(jsonStr)) // {"name":"張三","age":30} }