Go常用的資料結構

wangbjun發表於2019-01-23

閒著無事,隨便寫寫,初學Go,望各位大神輕噴!Go自帶的幾個複合資料型別,基本資料型別我們就不說了,大部分語言常見的幾種複合資料型別大概有陣列、字典、物件等,不同語言叫法不一樣,用法也有差異,比如說PHP裡面陣列其實嚴格來說不算陣列。

1.陣列

Go裡面的陣列和C類似,是由有序固定長度特定型別元素組成。畫重點,固定長度和特定型別。在很多弱型別的語言裡面,陣列非常隨意,PHP的陣列本質上是一個hash table,和C的陣列差異太大,所以寫慣了PHP再寫Go的話這點需要注意。

基礎用法1:

package main

import "fmt"

func main() {
	var a [5]int

	a[1] = 1
	a[2] = 3

	var b [10]string

	b[0] = "a1"
	b[1] = "b2"
	b[2] = "c5"

	fmt.Printf("%v\n", a)
	fmt.Printf("%v\n", b)
}
---結果---
[0 1 3 0 0]
[a1 b2 c5       ]
複製程式碼

從語法上看,Go定義陣列的型別放在後面,這點寫慣C系語言的估計蛋疼。陣列也是通過索引下標訪問,如果不初始化賦值的話,預設情況下,int型別的元素是0,string型別是空字串。

基礎用法2

我們也可以不先定義,直接使用字面量初始化陣列:

package main

import "fmt"

func main() {
	a := [...]int{1, 2, 3, 4, 5, 7}

	fmt.Printf("%v", a)
}
---結果---
[1 2 3 4 5 7]
複製程式碼

在這種情況下,我們可以省略長度,使用3個點代替,編譯器會自動判斷。

陣列遍歷

主要有兩種方式:

package main

import "fmt"

func main() {
	a := [...]int{1, 2, 3, 4, 5, 7}

	for i := 0; i < len(a); i++ {
		fmt.Print(a[i])
	}
	
	for k, v := range a {
		fmt.Print(k, "->", v)
	}
}
複製程式碼

如果知道長度的話可以使用for迴圈,否則可以使用for range 這種語法。

陣列函式

Go內建了一些函式可以運算元組,如果你使用了IDE的話,可以“點”出來:

Go常用的資料結構

然而,append並不是用來運算元組的,其實它是用來操作變長陣列的,即slice, 又稱切片。

2.Slice(切片)

傳統的陣列長度固定,所以實際用途並不多,除非你明確知道自己想要多長的陣列,很多時候我們需要的是一個可以改變長度大小的陣列,在Go裡面這型別被稱為切片。

slice其實是從陣列而來的,它和陣列非常像,區別就在於slice沒有固定長度,非常方便,所以平時一般都是用這個比較多。

基礎用法1:

package main

import "fmt"

func main() {
	var a []int

	a = append(a, 2)
	a = append(a, 1)
	a = append(a, 4)
	a = append(a, 5)

	fmt.Printf("%v", a)
}
複製程式碼

區別就在於slice在定義的時候不需要指定長度,也不用3個點,但是這就意味著你不能使用索引下標的方法去賦值了,可以使用append函式去追加元素。

而且在使用slice的也需要注意下標,如果大於slice的長度也會出現 panic: runtime error: index out of range

基礎用法2

package main

import "fmt"

func main() {
	a := [...]int{1,2,3,4,5,6,7,8}

	s1 := a[0:]

	s2 := a[1:5]

	s3 := a[4:6]

	fmt.Printf("%v\n", a)
	fmt.Printf("%v\n", s1)
	fmt.Printf("%v\n", s2)
	fmt.Printf("%v\n", s3)
}
複製程式碼

slice可以使用[start:end]這種語法從一個陣列裡面生成,比如a[1:5]意思是生成一個包含陣列索引1到5的之間元素的slice。

在Go裡面不同長度但是同一型別的陣列是不同型別的,比如你定義了2個int陣列,一個長度為5,一個長度為10,他們其實並不是同一個型別,雖然都是int型別。cannot use a (type [10]int) as type [5]int in argument

所以在大部分時候我們需要的是一個slice,並不是一個陣列。雖然這個2個用法基本上一毛一樣。。。

3.Map

在很多語言裡面,map被叫作字典,這個中文名稱很親切,字典就是一種key value結構,小時候大家都用過新華字典,字典的特徵就是每一個字都對應一個解釋。但是Go的map是無序的,這點大家需要注意。如果有童鞋寫過PHP,會發現這個資料型別類似PHP裡面的關聯陣列。

在Go裡面,它和slice的區別就是slice的索引是數值,map的索引型別就豐富了,基本上常用資料型別都支援,甚至包括結構體。

基礎用法

和其它陣列型別一樣,map也支援先定義後賦值,或者直接使用字面量建立。但是如果使用先定義後賦值這種方式,map需要使用make初始化。

package main

import "fmt"

func main() {
	var m1 map[string]string

	m1 = make(map[string]string)

	m1["name"] = "Golang"
	m1["address"] = "BeiJin"

	m2 := map[string]string{
		"name": "GoLand",
		"addr": "ShangHai",
	}

	fmt.Printf("%v\n", m1)
	fmt.Printf("%v", m2)
}
---結果---
map[name:Golang address:BeiJin]
map[name:GoLand addr:ShangHai]
複製程式碼

map可以使用for range 語法遍歷,但是需要注意的是每次遍歷的順序是無序的。

如何判斷一個key是否存在map裡面?在PHP裡面我們有一個array_key_exists函式,在Go裡面寫法略有不同:

age, ok := m1["age"]
if !ok {
    fmt.Println("age 不存在", age)
}
複製程式碼

其實如果你不判斷是否存在直接取也可以,並不會報錯,只不過獲取到的值是一個對應型別的零值。

4.結構體

Go的結構體也類似C,類似於現在很多物件導向的語言裡面的類,往往用來儲存一組相關聯的資料,Go雖然不是一個完全物件導向的語言,但是使用結構體可以實現類似效果。

基本用法

package main

import "fmt"

type Goods struct {
	name    string
	price   int
	pic     string
	address string
}

func main() {
	var goods Goods
	goods.name = "商品1"
	goods.price = 100
	goods.pic = "http://xxxx.jpg"
	goods.address = "中國"

	fmt.Printf("%v\n", goods)

	goods2 := Goods{
		name:    "商品2",
		price:   200,
		pic:     "http://xxxx.png",
		address: "日本",
	}

	fmt.Printf("%v", goods2)
}
---結果---
{商品1 100 http://xxxx.jpg 中國}
{商品2 200 http://xxxx.png 日本}
複製程式碼

先定義後賦值或者字面量賦值都可以,值得一提的是在Go裡面如果結構體或者其屬性的首字母大寫則表示該結構體或者屬性可以被匯出,也就是被其它包使用。結構體裡面的屬性成員的型別也可以是結構體,這就變相實現了類的繼承。

既然結構體和類差不多,那類的方法在哪裡定義呢?這點Go實現的就比較巧妙了!

func (g Goods) getName() string {
	return g.name
}
複製程式碼

我們只需要在函式的前面放一個變數,就變成了方法。在很多語言裡面,函式和方法區分不是很明顯,大部分時候我們都是混著叫,但是在Go裡面,方法指的是針對某一型別的函式。比如在上面的例子裡面,這個getName函式就是針對Goods結構體的,用物件導向的說法就是一個類方法。所以我們可以使用 goods.getName()的形式呼叫這個方法。

上面的程式碼裡那個附加的引數p,叫做方法的接收器(receiver),早期的面嚮物件語言留下的遺產將呼叫一個方法稱為“向一個物件傳送訊息”。 在Go語言中,我們並不會像其它語言那樣用this或者self作為接收器;我們可以任意的選擇接收器的名字。由於接收器的名字經常會被使用到,所以保持其在方法間傳遞時的一致性和簡短性是不錯的主意。這裡的建議是可以使用其型別的第一個字母。

在Go裡面我們可以為任何型別定義方法,無論是常見的int、string,還是map、struct都沒問題,下面的例子裡面就是為int型別擴充套件一個方法:

package main

import "fmt"

type MyInt int

func main() {
	myInt := MyInt(10)
	res := myInt.add(100)

	fmt.Printf("%d", res)
}

func (m MyInt) add(a int) int {
	return int(m) + a
}
---結果---
110
複製程式碼

我們無法直接使用基本資料型別,但是我們可以起一個別名,純屬娛樂!

5.JSON

嚴格來說,JSON並不是一種資料型別,但是json是現在最流行的資料交換格式,Go對json的支援也很好,在Go裡面主要通過結構體生成json,我們也可以把一個json轉換成結構體。

package main

import (
	"encoding/json"
	"fmt"
)

type Goods struct {
	Name    string
	Price   int
	Address string `json:"address2"`
	Tag     string
}

func main() {
	goods := Goods{
		"商品1", 100, "中國", "特價",
	}

	bytes, err := json.Marshal(goods)

	if err != nil {
		panic(err)
	}

	fmt.Printf("%s", bytes)
}
---結果---
{"Name":"商品1","Price":100,"address2":"中國","Tag":"特價"}
複製程式碼

把結構體轉換成json可以使用Marshal方法,有一點需要注意: 結構體的屬性成員首字母必須大寫,但是可以使用註解的Tag標註轉換成json之後的key名稱。

json字串轉換成結構體步驟差不多:

package main

import (
	"encoding/json"
	"fmt"
)

type Goods struct {
	Name    string
	Price   int
	Address string `json:"address2"`
	Tag     string
}

func main() {
	jsonStr := `{"Name":"商品1","Price":100,"address2":"中國","Tag":"特價"}`

	goods := Goods{}

	err := json.Unmarshal([]byte(jsonStr), &goods)
	if err != nil {
		panic(err)
	}

	fmt.Printf("%v", goods)
}
---結果---
{商品1 100  特價}
複製程式碼

這在我們平時寫介面或者請求介面的時候非常好使,簡單易用!

好了,今天就介紹這麼多了,謝謝大家檢視!

相關文章