指針
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))
}