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