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

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值