GO語言學習筆記(二)指針、切片、字典、結構體

指針

golang中指針變量的值爲被指向變量的地址,指針變量可以通過地址去訪問並修改被指向對象的值。需要注意的是golang禁止對指針變量的值做修改。此外如果局部變量的地址被指針變量引用,具備變量的生命週期大於等於該指針的生命週期。

package main

import "fmt"

func point() *int {
	var x int = 2
	fmt.Println(&x)
	return &x
}

func main() {
	var px *int
	px = point()
	*px++ //px指向的變量值+1
	// px++  禁止對地址進行運算
	fmt.Println(*px, px)
}

數組

golang中數組是一串固定長度的的特定類型元素序列,由於長度固定,因此一般使用Slice(切片)的較多,切片長度可收縮更爲方便。

當二個數組長度和類型一致時,可以進行相互賦值,此外數組也支持索引賦值。

package main

import "fmt"

func main() {
	var d1 [3]int = [3]int{1, 2, 3}
	var d2 [3]int = [3]int{4, 5, 6}
	var d3 [4]int
	d4 := [4]int{7, 8}      //未初始化的成員賦值爲0
	d5 := [...]int{1, 2, 3} //d5數組長度爲3
	d6 := [...]int{7: 3}    //數組支持索引賦值x:y d6數組第x位爲數值3

	d1 = d2
	//d3 = d2  // d2的數據類型爲[3]int d3的數據類型爲[4]int因此不能直接賦值

	d7 := [3][2]int{ // 二維數組定義方式
		{1, 2},
		{3, 4},
		{5, 6},
	}

	d8 := [...][3][2]int{ //...只能使用在數組高維的數值上
		{
			{1, 2},
			{3, 4},
			{5, 6},
		},
		{
			{1, 2},
			{3, 4},
			{5, 6},
		},
	}

	fmt.Println(d1, d2, d3, d4, d5, d6, d7, d8)
}

切片

golang切片(slice)本身並非動態數組或者數組指針而是結構體對象,slice是通過指針去引用底層的數組,以開始和結束的索引位置確定引用數組片段。slice定義爲

type slice struct{
    array unsafe.Pointer   //數組指針
    len int      // 限定可讀寫元素數量
    cap int      // 切片所引用數組真實長度
}
package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	var a []int  // 定義了[]int切片類型,但未初始化切片對象,因此切片內部指針值爲0
	b := []int{} // 定義了[]int切片類型,切片對象爲一個0成員數組
    // 這裏新手需要注意的是切片類型不要和數組類型搞混 b := [4]int{}這種格式爲數組類型
	c := [6]int{1, 2, 3, 4, 5, 6}
	fmt.Println(a == nil, a, b == nil, b)
	fmt.Printf("a: %#v\n", (*reflect.SliceHeader)(unsafe.Pointer(&a)))
	fmt.Printf("b: %#v\n", (*reflect.SliceHeader)(unsafe.Pointer(&b)))
	a = c[1:4] // 切片指針指向c[1]開始的地址
	b = c[1:5] // 切片指針指向c[1]開始的地址
	fmt.Println(a == nil, a, b == nil, b)
	fmt.Printf("a: %#v\n", (*reflect.SliceHeader)(unsafe.Pointer(&a)))
	fmt.Printf("b: %#v\n", (*reflect.SliceHeader)(unsafe.Pointer(&b)))
}

slice可以通過索引方式訪問數組內容,實際範圍是一個右半開區間

package main

import "fmt"

func main() {
	a := [6]int{1, 2, 3, 4, 5, 6}
	fmt.Println(a, len(a), cap(a)) // 數組acap和len屬性一致
	b := a[2:4]
	c := a[:4]
	d := a[2:]
	e := a[:]
	fmt.Println(b, len(b), cap(b)) // slice b 的len爲[2:4] cap爲[2,len(a)]
	fmt.Println(c, len(c), cap(c)) // slice c 的len爲[0:4] cap爲[0,len(a)]
	fmt.Println(d, len(d), cap(d)) // slice d 的len爲[2:len(a)] cap爲[2,len(a)]
	fmt.Println(e, len(e), cap(e)) // slice e 的len爲[0:len(a)] cap爲[0,len(a)]
}

slice作爲引用類型,無需預先定義數組,可以直接使用make創建切片對象。

package main

import "fmt"

func main() {
	a := make([]int, 3, 6) // make(type len cap) 切片內默認值爲0
	b := make([]int, 5)    // len=cap 切片內默認值爲0
	fmt.Println(a, len(a), cap(a))
	fmt.Println(b, len(b), cap(b))
}

我們可以通過append對slice進行追加值得操作,當追加值超過原有切片對象時,golang一般將會爲該切片對象重新分配一個內存空間擴大二倍的數組。這裏需要注意的是切片如果引用的是一個已定義的數組變量,當追加值超過cap時,切片對象和數組將不再共享一片空間,即切片上修改的內容不再影響數組。

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
	var a []int // 定義了[]int切片類型,但未初始化切片對象,因此切片內部指針值爲0
	fmt.Printf("a: %#v\n", (*reflect.SliceHeader)(unsafe.Pointer(&a)))
	a = append(a, 1, 2) // 對nil切片對象,追加數據時, 會爲其底層分配空間
	fmt.Printf("a: %#v\n", (*reflect.SliceHeader)(unsafe.Pointer(&a)))
	a = append(a, 3) // 當追加的數據超過切片cap值,一般會爲切片對象重新分配二倍空間的數據
	fmt.Printf("a: %#v\n", (*reflect.SliceHeader)(unsafe.Pointer(&a)))

	b := [5]int{1, 2, 3, 4, 5}
	c := b[:4]
	fmt.Printf("c: %#v\n", (*reflect.SliceHeader)(unsafe.Pointer(&c)))
	fmt.Println(b, c)

	c = append(c, 11)  // 爲切片對象追加一個值,切片對象len+1,原數組對象b[4]的值被修改爲11
	fmt.Printf("c: %#v\n", (*reflect.SliceHeader)(unsafe.Pointer(&c)))
	fmt.Println(b, c)

	c = append(c, 12)  // 爲切片對象重新申請空間,切片對象的起始指針發生變化
	c[0] = 1024
	fmt.Printf("c: %#v\n", (*reflect.SliceHeader)(unsafe.Pointer(&c)))
	fmt.Println(b, c)
}

我們可以通過copy函數,對切片進行拷貝操作,拷貝的長度爲較短的對象的長度爲準。此外byte類型切片還支持與string類型發生拷貝關係

package main

import "fmt"

func main() {
	a := []int{1, 2, 3, 4}
	b := []int{5, 6}
	c := []byte{1, 2, 3}
	d := "hello world"
	copy(a, b)
	copy(c, d)
	fmt.Print(a, b, c)
}

字典

字典博主習慣稱之爲哈希表,這是一個使用頻率很高的數據結構。golang中對字典的定義map[key]value中,key的類型必須支持==、!=運算符。map作爲引用類型,我們使用make或者初始化語句進行創建:

package main

import "fmt"

func main() {
	a := make(map[int]string) // make創建map對象
	a[0] = "this is 0"
	a[1] = "this is 1"
	fmt.Println(a)

	b := map[int]string{ //初始化語句創建map對象
		0: "this is 0",
		1: "this is 1",
	}
	fmt.Println(b)
}

map作爲內置類型,其增刪改該查的方式也簡單

package main

import "fmt"

func main() {
	a := map[string]int{
		"cny": 100,
		"usd": 14,
		"eur": 13,
	}

	a["jpy"] = 1535            //增加
	a["eur"] = 12              //修改
	delete(a, "usd")           //刪除,當key不存在時,不會報錯
	if v, ok := a["cny"]; ok { // 查找,使用 ok-idiom判斷,當ok變量值爲true時,map存在該鍵值,當ok爲false時,v的值默認爲0
		fmt.Println("map have cny key and value is", v)
	}

	fmt.Println(a)
}

golang對map類型的設計是not addressable,因此當value的類型是結構體時,我們不能直接修改其成員的值。

package main

import "fmt"

type user struct {
	num int
	buf string
}

func main() {
	a := map[string]user{
		"cny": {
			100,
			"china",
		},
		"usd": {
			14,
			"America",
		},
	}
	b := map[string]int{
		"hello": 1,
	}

	//a["cny"].num += 1  cannot assign to struct field a["cny"].num in map
	b["hello"] += 1
	c := a["cny"]
	c.num += 1
	a["cny"] = c

	fmt.Println(a, b)
}

使用迭代器訪問map類型,每次返回鍵值的時序是不相同的,因爲Golang底層並沒有保證map時序固定,設計者爲了避免開發人員依賴map遍歷時序,而進行打亂。

package main

func main() {
	a := map[string]int{
		"cny": 100,
		"usd": 14,
		"eur": 13,
		"jpy": 1535,
	}

	for i := 0; i < 3; i++ {
		for b, c := range a {
			print(b, c, " ")
		}
		println()
	}

}

此外在迭代期間對map新增或者刪除鍵值是安全的,程序運行時會對併發任務做檢測,如果某一個任務正在對map做寫操作,那麼其他讓我不能對map進行讀寫操作,否則會造成進程崩潰。因此Map的併發安全性需要靠開發人員進行維護。開發人員可以通過數據競爭來檢查此類問題,並且使用讀寫鎖的機制來解決該類問題。

package main

import "time"

func main() {
	a := map[string]int{
		"cny": 100,
		"usd": 14,
		"eur": 13,
		"jpy": 1535,
	}

	go func() {
		for {
			a["usd"] += 1 // 對map進行寫操作
			time.Sleep(time.Microsecond)
		}
	}()

	go func() {
		_ = a["usd"] // 對map進行讀操作
		time.Sleep(time.Microsecond)
	}()
	select {}
}

package main

import (
	"sync"
	"time"
)

func main() {
	a := map[string]int{
		"cny": 100,
		"usd": 14,
		"eur": 13,
		"jpy": 1535,
	}
	var lock sync.RWMutex

	go func() {
		for {
			lock.Lock()
			a["usd"] += 1 // 對map進行寫操作
			lock.Unlock()
			time.Sleep(time.Microsecond)
		}
	}()

	go func() {
		lock.RLock()
		_ = a["usd"] // 對map進行讀操作
		lock.RUnlock()
		time.Sleep(time.Microsecond)
	}()
	select {}
}

結構體

結構體是將多種類型的字段打包成一個複合集合,字段名必須唯一,但是可以使用_進行補位。可順序初始化所有字段或者以命名方式初始化指定字段。爲防止程序後期改變結構體,推薦使用後者。

package main

import "fmt"

type node struct {
	_    int
	_    int
	len  int
	name string
}

func main() {
	var a node
	b := node{1, 2, 3, "nodeB"}   // 順序初始化
	c := node{len: 3, name: "nodeD"} // 指定字段初始化
	//d := node{_: 1} invalid field name _ in struct initializer
	//d := node{1, 2, 3} too few values in node literal
	fmt.Println(a, b, c)
}

golang支持匿名結構體,可作爲變量類型或者結構體成員類型。需要注意的是當匿名結構體作爲結構體成員時,該結構體成員由於缺少類型標識,無法直接進行初始化。

package main

import "fmt"

type addrx struct {
	email   string
	country string
}

type node struct {
	len  int
	name string
	addr addrx
	coo  struct {
		x int
		y int
	}
}

func main() {
	a := addrx{
		email:   "xx.com",
		country: "cn",
	}
	b := node{
		len:  12,
		name: "x",
		addr: a,  // 正常結構體成員可以直接進行初始化
		// 無法對匿名結構體coo進行直接初始化
	}
	b.coo.x = 1
	b.coo.y = 2
	fmt.Println(a, b)
}

golang支持匿名字段,結構體成員可以只定義數據類型,不定義數據名稱,因此也被稱之爲嵌入字段。匿名字段隱式的將數據類型當做數據名稱來使用,因此結構體不能包含二個及以上的相同數據類型的匿名字段。

package main

import "fmt"

type node struct {
	x      int
	int    // 匿名字段
	string // 匿名字段
}

func main() {
	a := node{
		x:      1,
		int:    2,
		string: "a",
	}
	b := node{
		x:   1,
		int: 2,
	}

	b.string = "b"
	fmt.Println(a, b)
}

Golang中字段標籤並不是註釋,而是對字段描述的元數據,他不屬於類型成員,是類型組成的一部分。在程序運行期間可以通過反射獲取標籤信息,常被用來做格式校驗或者數據庫關係映射。

package main

import (
	"fmt"
	"reflect"
)

type node struct {
	x int `X座標`
	y int `Y座標`
	z int
}

func main() {
	a := node{
		x: 1,
		y: 2,
		z: 3,
	}
	b := reflect.ValueOf(a)
	c := b.Type()

	fmt.Println(c.Field(0).Tag, b.Field(0))
	fmt.Println(c.Field(1).Tag, b.Field(1))
	fmt.Println(c.Field(2).Tag, b.Field(2))
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章