數組
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 來實現面向對象。
- 結構體是值類型。結構體的所有字段在內存中是連續的。
結構體的聲明
type 結構體名稱 struct{
field1 type
field2 type
}
結構體的使用
type Person struct{
Name string
Age int
}
// 方式一:
p1 := Person{}
p1.Name = "Tom"
p1.Age = 10
// 方式二
p2 := Person{"Jerry", 5}
// 方式三
p3 := Person{Name: "張三", Age: 30}
// 或
p3 := Person{
Name: "張三",
Age: 30, // 注意這里要加逗號,否則會被默認加上分號
}
結構體指針
// 方式一:
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}
}
自定義數據類型
為了簡化數據類型定義,Golang 支持自定義數據類型。
基本語法:
type 自定義數據類型名 數據類型 // 相當於起了一個別名
示例:
type myint int //這時 myint 就等價於 int,但是 Go 會認為他們還是兩個類型
type mySum func(int, int) int //這時 mySum 就等價於一個函數類型
自定義數據類型跟原類型雖然在我們的理解上是一樣的,但是 Golang 會認為它們是兩種不同的數據類型。這導致這兩種類型是無法直接進行比較的,必須強轉。