Go语言学习查缺补漏ing Day7

本文探讨了Go语言中defer执行顺序的特殊情况,解释了为何在代码示例中defer函数打印的年龄值不同。此外,还讨论了切片声明的选择以及结构体成员的访问方式。最后,通过代码展示了map遍历的无序性,并分析了其内部的随机性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作者:ReganYue

来源:恒生LIGHT云社区

Go语言学习查缺补漏ing Day7

零、前言

因为笔者基础不牢,在使用Go语言的时候经常遇到很多摸不着头脑的问题,所以笔者下定决心好好对Go语言进行查漏补缺,本【Go语言查缺补漏ing】系列主要是帮助新手Gopher更好的了解Go语言的易错点、重难点。希望各位看官能够喜欢,点点赞、关注一下呗!

一、再谈defer的执行顺序

大家来看一看这段代码:

package main

import "fmt"

type Person struct {
    age int
}
func main() {
    person := &Person{28}

    //A
    defer func(p *Person) {
        fmt.Println(p.age)
    }(person)
    //B
    defer fmt.Println(person.age)
    //C
    defer func() {
        fmt.Println(person.age)
    }()
    person.age = 21
}

前面我们介绍过defer的执行顺序,但是我今天又遇到新问题,于是这里又补充介绍这个defer的顺序问题。

这个程序运行结果是:

21
28
21

我们都知道defer的执行顺序是先进后出,所以执行顺序是C、B、A。

B中 defer fmt.Println(person.age)输出28,为什么呢?

因为这里是将28作为defer()函数的参数,会把28推入栈中进行缓存,得到执行这条defer语句时就把它拿出来。所以输出28.

而A中:

defer func(p *Person) {
        fmt.Println(p.age)
    }(person)

defer()函数是将结构体Person的地址进行缓存,当后续改变这个地址的内值时,后续输出时这里就会输出那个地址内改变后的值。所以B defer语句执行时从地址中取出的值是29.

而C defer语句理由很简单:

defer func() {
        fmt.Println(person.age)
    }()

就是无参匿名函数的一种情形。闭包引用,person.age改变就会改变。

二、哪种切片的声明比较好?为什么?
var a []int
a := []int{}

这里第一种声明的是nil切片,而第二种声明是创建一个长度以及容量为零的空切片。

第一种切片声明方法比较好,因为它这种声明方式不占用空间,而第二种声明后会占用一部分空间。

三、取得结构体成员的几种方法
package main

import "fmt"

type S struct {
    m string
}

func f() *S {
    return &S{"ReganYue"}
}
func main() {
    p := f()
    p2 := *f()
    fmt.Println(p.m, p2.m)
}

我们运行能够发现:

p、p2都能获取结构体的成员变量。

为什么呢?f()函数的返回值是指针类型,所以p2获取*f()时,p2是S类型,p2.m可以获取其成员变量。

而f()的结果是指针,不过我们前面说过,一级指针能够自动进行解引用。所以也能够访问成员变量。

四、遍历map的存在顺序变化?为什么?

我们执行下面这段代码多次,看输出结果:

package main

import "fmt"

func main() {
    m := map[int]string{0: "zero", 1: "one", 3: "three", 4: "four", 5: "five"}
    for k, v := range m {
        fmt.Println(k, v)
    }
}

第一次执行结果如下:

5 five
0 zero
1 one
3 three
4 four

第二次执行结果如下:

0 zero
1 one
3 three
4 four
5 five

第三次执行结果如下:

4 four
5 five
0 zero
1 one
3 three

我们发现每一次执行的顺序都是变化的。这说明遍历map的顺序是无序的。为什么呢?

在runtime.mapiterinit中有这样一段代码:

// mapiterinit initializes the hiter struct used for ranging over maps.
// The hiter struct pointed to by 'it' is allocated on the stack
// by the compilers order pass or on the heap by reflect_mapiterinit.
// Both need to have zeroed hiter since the struct contains pointers.
func mapiterinit(t *maptype, h *hmap, it *hiter) {
	if raceenabled && h != nil {
		callerpc := getcallerpc()
		racereadpc(unsafe.Pointer(h), callerpc, funcPC(mapiterinit))
	}

	if h == nil || h.count == 0 {
		return
	}

	if unsafe.Sizeof(hiter{})/sys.PtrSize != 12 {
		throw("hash_iter size incorrect") // see cmd/compile/internal/gc/reflect.go
	}
	it.t = t
	it.h = h

	// grab snapshot of bucket state
	it.B = h.B
	it.buckets = h.buckets
	if t.bucket.ptrdata == 0 {
		// Allocate the current slice and remember pointers to both current and old.
		// This preserves all relevant overflow buckets alive even if
		// the table grows and/or overflow buckets are added to the table
		// while we are iterating.
		h.createOverflow()
		it.overflow = h.extra.overflow
		it.oldoverflow = h.extra.oldoverflow
	}

	// decide where to start
	r := uintptr(fastrand())
	if h.B > 31-bucketCntBits {
		r += uintptr(fastrand()) << 31
	}
	it.startBucket = r & bucketMask(h.B)
	it.offset = uint8(r >> h.B & (bucketCnt - 1))

	// iterator state
	it.bucket = it.startBucket

	// Remember we have an iterator.
	// Can run concurrently with another mapiterinit().
	if old := h.flags; old&(iterator|oldIterator) != iterator|oldIterator {
		atomic.Or8(&h.flags, iterator|oldIterator)
	}

	mapiternext(it)
}
	// decide where to start
	r := uintptr(fastrand())
	if h.B > 31-bucketCntBits {
		r += uintptr(fastrand()) << 31
	}
	it.startBucket = r & bucketMask(h.B)
	it.offset = uint8(r >> h.B & (bucketCnt - 1))

	// iterator state
	it.bucket = it.startBucket

我们可以看到,决定从哪开始是根据fastrand()取随机数决定的,所以每次运行,随机数都不一样,所以输出顺序也不一样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值