• go经典知识及总结


    1.无论sync.Mutex还是其衍生品都会提示不能复制,但是能够编译运行

    加锁后复制变量,会将锁的状态也复制,所以 mu1 其实是已经加锁状态,再加锁会死锁.
    所以此题的答案是 fatal error;

    type MyMutex struct {
        count int
        sync.Mutex
    }
    
    func main() {
        var mu MyMutex
        mu.Lock()
        var mu1 = mu
        mu.count++
        mu.Unlock()
        mu1.Lock()
        mu1.count++
        mu1.Unlock()
        fmt.Println(mu.count, mu1.count)
    }
    

    2.defer对于有返回变量的函数,它的生命其可以保留到return之后.

    func main() {
    	fmt.Println(doubleScore(0)) //0
    	fmt.Println(doubleScore(20.0)) //40
    	fmt.Println(doubleScore(50.0)) //100
    }
    func doubleScore(source float32) (score float32) {
    	defer func() {
    		if score < 1 || score >= 100 {
    			score = source //最终还能修改返回值
    		}
    	}()
    	return source * 2
    }
    

    3.sync.Mutex不能重复加锁

    var mu sync.Mutex
    var chain string
    
    func main() {
        chain = "main"
        A()
        fmt.Println(chain)
    }
    func A() {
        mu.Lock()
        defer mu.Unlock()
        chain = chain + " --> A"
        B()
    }
    
    func B() {
        chain = chain + " --> B"
        C()
    }
    
    func C() {
        mu.Lock() //A处还没有UnLock,此处又加锁,所以出现deadlock
        defer mu.Unlock()
        chain = chain + " --> C"
    }
    

    3.go的for语句几大特别之处

    a.break到指定的层级
    
    func main() {
    OuterLoop:
    	for i := 0; i < 2; i++ {
    	InLoop:
    		for j := 0; j < 5; j++ {
    			switch j {
    			case 2:
    				fmt.Println(i, j)
    				break InLoop
    			case 3:
    				fmt.Println(i, j)
    				break OuterLoop
    			}
    		}
    	}
    }
    
    b.类似于while
    for true{
    	//do sth.
    }
    for{
    	//do sth.
    }
    
    c.三分号的用法和c++类似
    for ; step > 0; step-- {
        fmt.Println(step)
    }
    
    for ; ; i++ {
        if i > 10 {
            break
        }
    }
    
    
    func main() {
        v := []int{1, 2, 3}
        for i, n := 0, len(v); i < n; i++ {
            v = append(v, i)
        }
        fmt.Println(v)
    }
    

    4.go的一些预定义字符

    这些不是关键字,但最好不要拿去用,面试出这样的题的话,意义不大,只能拿来搞怪.

    true /false

    append 函数
    append 函数用于向切片中添加元素,并返回新的切片。

    make 函数
    make 函数用于创建切片、映射和通道。

    new 函数
    new 函数用于分配内存并返回指向新分配的零值对象的指针。

    len 函数
    len 函数用于返回字符串、切片、映射、通道、数组等的长度。

    cap 函数
    cap 函数用于返回切片、数组、通道等的容量。

    copy 函数
    copy 函数用于复制切片中的元素。

    delete 函数
    delete 函数用于从映射中删除指定的键值对。

    print 和 println 函数
    print 和 println 函数用于打印输出信息。

    panic 和 recover 函数
    panic 函数用于引发运行时错误,recover 函数用于捕获并处理运行时错误。

    close 函数
    close 函数用于关闭通道。

    5.有参返回类型可以直接return

    以下等价,但是return的作用域内(代码块内)必须有ret变量的存在,
    下图中ret被新的ret隐藏了,导致return所在作用域丢失ret.所以无法编译.

    func add1(a, b int) (ret int) {
    	ret = a + b
    	return
    }
    
    func add2(a, b int) (ret int) {
    	ret = a + b
    	return ret
    }
    

    6.json.Unmarshal必须传入目标指针

    虽然map的根本就是hmap指针,但是 reflect.ValueOf(v).Kind()获取到的不是指针,所以也必须传入指针.

    func (d *decodeState) unmarshal(v any) error {
    	rv := reflect.ValueOf(v)
    	if rv.Kind() != reflect.Pointer || rv.IsNil() {//!!
    		return &InvalidUnmarshalError{reflect.TypeOf(v)}
    	}
    
    	d.scan.reset()
    	d.scanWhile(scanSkipSpace).
    	err := d.value(rv)
    	if err != nil {
    		return d.addErrorContext(err)
    	}
    	return d.savedError
    }
    

    7.slice因为切片底层公用数组,容易导致数据共享和不能内存不能及时释放

    func get() []byte {
        raw := make([]byte, 10000)
        fmt.Println(len(raw), cap(raw), &raw[0])
        return raw[:3]
    }
    
    func main() {
        data := get()
        fmt.Println(len(data), cap(data), &data[0])
    }
    

    8.go 语言中的可比较类型和不可比较类型

    注意不可比较类型就三个:map slice func,可nil类型还包括chan,指针
    但是chan,指针可以用于等值比较

    操作符 变量类型
    等值操作符 (==、!=) 整型、浮点型、字符串、布尔型、复数、
    指针、管道、接口、结构体、数组
    排序操作符 (<、<=、> 、 >=) 整型、浮点型、字符串
    不可比较类型 map、slice、function

    所以下面代码结果为false,同一类型指针是可以进行相等比较的

    type foo struct{ Val int }
    
    type bar struct{ Val int }
    
    func main() {
    	a := &foo{Val: 5}
    	b := &foo{Val: 5}
    
    	fmt.Print(a == b)
    }
    
    

    9.go每个case上的表达式都会执行,但只有一个chan可以读取或设置值

    https://xie.infoq.cn/article/49526fb0dde758d663dfe0cd0 完整地介绍chan使用

    func A() int {
    	fmt.Println("A", GoID())
    	time.Sleep(500 * time.Millisecond)
    
    	return 1
    }
    
    func B() int {
    	fmt.Println("B", GoID())
    	time.Sleep(1000 * time.Millisecond)
    
    	return 2
    }
    
    func GoID() uint64 {
    	b := make([]byte, 64)
    	b = b[:runtime.Stack(b, false)]
    	b = bytes.TrimPrefix(b, []byte("goroutine "))
    	b = b[:bytes.IndexByte(b, ' ')]
    	n, _ := strconv.ParseUint(string(b), 10, 64)
    	return n
    }
    
    func main() {
    	ch := make(chan int, 1)
    	go func() {
    		select {
    		case ch <- A():
    			{
    				fmt.Println("caseA")
    			}
    		case ch <- B():
    			{
    				fmt.Println("caseB")
    			}
    		default:
    			ch <- 3
    		}
    	}()
    	for v := range ch {
    		fmt.Println(v)
    	}
    }
    
    

    10.基础类型的map可以++,非基础类型不行.

    这种差异造成诡异的心智负担.

    func main() {
    	m := make(map[string]int)
    	m["foo"]++ //基础类型没有问题
    	fmt.Println(m["foo"])
    
    	m2 := map[string]Person{}
    	m2["foo"] = Person{1}
    	m2["foo"].Age = 22 //非法操作
    
    }
    
    type Person struct {
    	Age int
    }
    

    11.一个接口是否==nil,要看其类型和数值是否同时为nil

    打印则看数值

    func Foo() error {
    	var err *os.PathError = nil
    	return err
    }
    
    func main() {
    	err := Foo()
    	fmt.Println(err) //nil,打印则看数值
    	fmt.Println(err == nil) //false
    }
    
    func main() {
        x := interface{}(nil)
        y := (*int)(nil)
        a := y == x //类型不一样
        b := y == nil //y不是接口,所以直接看值.如果var y any = (*int)(nil)就变成接口了.最后接口就是false.
        _, c := x.(interface{}) //没有类型,断言失败
        println(a, b, c) //flase true false
    }
    
    

    12.在go里面0开始的数字是八进制

    const (
    	Decade = 010
    )
    
    func main() {
    	fmt.Println(Decade) //8
    }
    

    13.字符串Trim操作注意

    Trim结尾处的字符串,对应其他语言的TrimEnd

    TrimSuffix
    切勿使用TrimRight,会将第二个参数字符串里面所有的字符拿出来处理,只要与其中任何一个字符相等,便会将其删除

        fmt.Println(strings.TrimRight("ABBA", "BA")) //最后全部被删了
    
    Trim以某段开始的字符串,对应其他语言的TrimStart

    TrimPrefix

        fmt.Println(strings.TrimLeft("ABBAC", "BA")) //最后只会剩下C
    

    14.关于for range

    如果是chan则,没有k,只有v;
    	for v := range ch {
    		fmt.Println(v)
    	}
    

    15.关于切片两个冒号的说明

    https://segmentfault.com/a/1190000018356577

    注意

    我们潜意识觉得cap就是底层数组的长度,但是在尽显冒号切片时,cap的长度是有max-low得到的,max的默认值为源切片(或源数组)的cap.

    通过两个冒号创建切片,slice[x:y:z]切片实体[x:y]切片长度len = y-x,切片容量cap = z-x\

    data := [...]int{0, 1, 2, 3, 4, 5, 6} //初始化一个数组
    slice := data[1:4:5] // [low : high : max]  通过两个冒号创建切片
    

    使用两个冒号[1:4:5] 从数组中创建切片,长度为4-1=3,也就是索引从1到3 的数据(1,2,3)

    然后,后面是最大是5,即容量是5-1=4,即,创建的切片是长度为从索引为 1、2、3 的切片,底层数组为[ 1,2,3,4]

    data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
    
    data[:6:8] [0 1 2 3 4 5]         //默认low为0
    data[5:] [5 6 7 8 9]             //省略 high、 max,默认值为源切片(或源数组)的cap
    data[:3] [0 1 2]                 //low=0,max=默认值为源切片(或源数组)的cap
    data[:] [0 1 2 3 4 5 6 7 8 9]    //10 10 全部省略。
    

    一道题懂了,那真懂了.

    func main() {
    	a := []int{0, 1, 2, 3}
    	s := a[1:3]  //1,2,[3]
    	s2 := s[1:2] //2,[3]
    	fmt.Println(cap(s), len(s))
    	fmt.Println(cap(s2), len(s2))
    	fmt.Println("--------------------------------")
    	s2 = append(s2, 333)
    
    	fmt.Println(a)
    	fmt.Println(s)
    	fmt.Println(s2)
    }
    3 2
    2 1
    --------------------------------
    [0 1 2 333]
    [1 2]
    [2 333]
    

    16.关于切片的一些手法

    创建空切片
    // 使用 make 创建空的整型切片
    slice := make([]int, 0)
    // 使用切片字面量创建空的整型切片
    slice := []int{}
    

    17.for range 常数

    for i := range 10 {
    		fmt.Println(i)
    }
    

    18.关于一切等值运算的根本是比较补码

    
    func main() {
    	count := 0
    	for i := range [256]struct{}{} {
    		m, n := byte(i), int8(i)
    		if n == -n {
    			count++
    			fmt.Println("n==-n", n, -n, i)
    		}
    		if m == -m {
    			count++
    			fmt.Println("m==-m", m, -m, i)
    		}
    	}
    	fmt.Println(count)
    
    }
    
    n==-n 0 0 0
    m==-m 0 0 0
    n==-n -128 -128 128
    m==-m 128 128 128
    4
    

    -128 的补码:

    首先,我们需要知道 -128 的原码。原码是表示数值的二进制形式,其中最高位表示符号位(0 表示正数,1 表示负数),其余位表示数值部分。对于 -128,其原码为 1000 0000
    
    接下来,我们求出 -128 的反码。反码是将原码的除符号位外的各位取反。对于 -128,其反码为 1111 1111
    
    最后,我们计算 -128 的补码。补码是在反码的末位加1,从而使后7位再次发生溢出,进位丢弃,符号位不变。因此,-128 的补码为 1000 0000
    

    需要注意的是,-128 是一个特殊的数,因为它的绝对值比最小的32位整数还要大1,所以在计算机中表示为 -128 的补码时,我们直接使用其反码加1的结果。

    128的补码
    128 的原码是 10000000。三码都是这个,所以也是 10000000。

    19.多重赋值的优先级

    多重赋值分为两个步骤,有先后顺序:

    • 计算等号左边的索引表达式和取址表达式,接着计算等号右边的表达式;
    • 赋值;
    func main() {
        var a []int = nil
        a, a[0] = []int{1, 2}, 9
        fmt.Println(a)
    }
    

    解析:运行时错误。知识点:多重赋值。

    19.sync.WaitGroup不能复制值,且需要在启动协程前Add
    func main() {
        wg := sync.WaitGroup{}
        for i := 0; i < 5; i++ {
            go func(wg sync.WaitGroup, i int) {
                wg.Add(1)
                fmt.Printf("i:%d\n", i)
                wg.Done()
            }(wg, i)
        }
        wg.Wait()
        fmt.Println("exit")
    }
    

    20.chan的基本数据结构

    ype hchan struct {
      qcount   uint  // 队列中的总元素个数
      dataqsiz uint  // 环形队列大小,即可存放元素的个数
      buf      unsafe.Pointer // 环形队列指针
      elemsize uint16  //每个元素的大小
      closed   uint32  //标识关闭状态
      elemtype *_type // 元素类型
      sendx    uint   // 发送索引,元素写入时存放到队列中的位置
    
      recvx    uint   // 接收索引,元素从队列的该位置读出
      recvq    waitq  // 等待读消息的goroutine队列
      sendq    waitq  // 等待写消息的goroutine队列
      lock mutex  //互斥锁,chan不允许并发读写
    }
    

    21.cap 关于 cap 函数适用下面哪些类型?

    A. 数组;
    B. channel;
    C. map;
    D. slice;

    func main() {
    	c := make(chan int, 2)
    	c <- 11
    	fmt.Println(len(c)) //1
    	fmt.Println(cap(c)) //2
    }
    

    22.启动一个goroutine的前提是,还有数

    func main() {
        runtime.GOMAXPROCS(1)
    
        go func() {
            for i:=0;i<10 ;i++  {
                fmt.Println(i)
            }
        }()
    
        for {}
    }
    

    23.接收器方法在defer时也会优先求值

    type Slice []int
    
    func NewSlice() Slice {
        return make(Slice, 0)
    }
    func (s *Slice) Add(elem int) *Slice {
        *s = append(*s, elem) //!! 注意无名类型的特别之处.
        fmt.Print(elem)
        return s
    }
    func main() {
        s := NewSlice()
        defer s.Add(1).Add(2)
        s.Add(3)
    }
    //1 3 2
    

    24.闭包引用相同变量,那么他们的作用效果都在这个变量上

    func test(x int) (func(), func()) {
        return func() {
            println(x)
            x += 10
        }, func() {
            println(x)
        }
    }
    
    func main() {
        a, b := test(100)
        a()
        b()
    }
    // 100 110
    

    25.range相当于一个函数,会对传入的数据进行拷贝.

    因此,下面切片和数组的场景会产生不同的值

    type T struct {
    	n int
    }
    
    func main() {
    	ts := [2]T{}
    	//ts := make([]T, 2)
    	for i, t := range ts {
    		switch i {
    		case 0:
    			t.n = 3
    			ts[1].n = 9 //改变了ts,但t的数据,源头是range产生的拷贝
    		case 1:
    			fmt.Print(t.n, " ")
    		}
    	}
    	fmt.Print(ts)
    }
    
    // 数组 0 [{0} {9}]
    // 切片 9 [{0} {9}]
    
    
    //--------------
    func main() {
         var a = []int{1, 2, 3, 4, 5}
         var r = make([]int, 0)
         for i, v := range a { //这里产生了新的副本,所以下面的添加操作不影响遍历.
             if i == 0 {
                 a = append(a, 6, 7)
             }
            r = append(r, v)
        }
        fmt.Println(r)
    }
    

    25.关于defer func recover panic

    • panic会将恐慌压入一个后进先出的栈里面;
    • recover 必须内置于defer func 内才有效;
    • recover从panic栈中取值;
    func main() {
        defer func() {
            fmt.Print(recover())
        }()
        defer func() {
            defer func() {
                fmt.Print(recover())
            }()
            panic(1)
        }()
        defer recover() //这里的recover没有效果,因为没有在defer func内部
        panic(2)
    }
    

    上面的代码panic依次入站 2 ,1,出栈为1 ,2,所以结果 1 2

    func main() {
        defer func() {
            fmt.Print(recover())
        }()
        defer func() {
            defer fmt.Print(recover()) //首先参数需要求值,所以在当前执行func完毕时要打印2
            panic(1) //压入栈中,当前有没有机会了,上级会捕捉到.
        }()
        defer recover() //这里的recover没有效果,因为没有在defer func内部
        panic(2)
    }
    

    结果为 2 1

    26. 当闭包含有外层变量时,会让外面的变量一直活着

    func F(n int) func() int {
        return func() int {
            n++
            return n
        }
    }
    
    func main() {
        f := F(5) //参数为5
        defer func() {
            fmt.Println(f())
        }()
        defer fmt.Println(f()) //需要立即求值,所以最后打印6,参数变成为了6
        i := f() //参数变成了7,
        fmt.Println(i)//打印7
    }
    

    //最后结果768

    27.关于for,range对枚举器的赋值

    func main() {
    	var k = 9
    	for k = range []int{} { //没有数据,所以对k的赋值,没能执行
    	}
    	fmt.Println(k)
    
    	for k = 0; k < 3; k++ {
    	}
    	fmt.Println(k) //很明显上面导致k变成了3
    
    	for k = range (*[3]int)(nil) { //一个[三个数据的数组]的指针,虽然是nil,但是在go里面这是合法的,最终k变成2
    	}
    	fmt.Println(k)
    }
    
    

    28.关于继承的本质问题

    
    
    type T struct{}
    
    func (*T) foo() {
    }
    
    func (T) bar() {
    }
    
    type S struct {
        *T //注意指针也可以,也即,没有名称只有类型,那么我们就可以通过语法糖的方式,好像调用自己的方法一样调用
    }
    
    func main() {
        s := S{}
        _ = s.foo
        s.foo() //这个是没有问题的.虽然就nil,但因为没有发生调用nil,所以没有问题
        _ = s.bar // (* s.T).bar,但指针s.T为nil,解引用失败报错
    }
    
    //---------------这是可以的
    type X struct {}
    func (x *X) test()  {
       println(x)
    }
    func main() {
        var a *X
        a.test()//nil,不是null
        X{}.test() //这里有问题,右值不可寻址
    }
    //--------------------------------------
    
    type Father struct{ Name string }
    type Monther struct {
        Name string
    }
    
    func (m Monther) World() {
    
    }
    
    type Child struct {
        Father
        Monther
    }
    
    func (entity Father) Hello() {
    
    }
    func (entity Father) World() {
    
    }
    
    func main() {
        child := Child{}
        child.World() //二义性语法糖就用不了,需要指明Father or Monther的方法
    }
    
    //-----------------------
    
    
    - 匿名字段的本质字段名是
    S *S
    S S
    
    - 然后调用他们对应的接收器方法。
    - 如果T为指针,则全有,不问上面的两种情况了
    
    - 能不能改变原有数据,取决于接收器是不是指针类型
     
    func main() {
       var v T
       t := &v
       fmt.Printf("%p\n", t.S)
       t.SPtr(1) //因为t指针类型,所以拥有S,*S的接收器方法
       t.SPtr(1)
       fmt.Println(t.S.Age)
    }
    type S struct {
       Age int
    }
    type T struct {
       S
    }
    func (s S) SVal(dd int) {
       s.Age = 10
       fmt.Printf("%p\n", &s)
    }
    func (s *S) SPtr(dd int) {
       fmt.Printf("%p\n", s)
       s.Age = 100
    }
    func (t T) TVal(dd int) {
    }
    func (t *T) TPtr(dd int) {
    }
    func methodSet(arg interface{}) {
       argType := reflect.TypeOf(arg)
       fmt.Println(argType.NumMethod())
       for i := 0; i < argType.NumMethod(); i++ {
          m := argType.Method(i)
          fmt.Printf("%s: %v\n", m.Name, m.Type)
       }
    }
    //----------
    type Fragment interface {
            Exec(transInfo *TransInfo) error
    }
    type GetPodAction struct {
    }
    func (g GetPodAction) Exec(transInfo *TransInfo) error {
            ...
            return nil
    }
    指针接收器拥有值接口器的方法
    var fragment Fragment = new(GetPodAction)
    
    
    
    图片名称

    ####29.关于map的怪异之处,面试时尤其注意

    func main() {
    	var m map[int]bool // nil
    	var a = m[123]
    	fmt.Println(a)//false,原因很简单,nil不是null,map不仅允许读取没有的key,还允许读取nil的map
    }
    

    30.关于切片

    • 关于切片之前的理解是有问题的,我们切片的对象是底层真实的数组长度(即:容量)
    • low,high,max high默认是slice的长度,max则为容量
    func main() {
    	x := make([]int, 2, 10)
    	a := x[6:10]
    	b := x[6:] //x[6:2],所以这里要报错
    	c := x[2:] //2-2=0,所以最后是空的
    	fmt.Println(a)
    	fmt.Println(b)
    	fmt.Println(c)
    }
    
    
    

    31.不可比较类型 slice map func

    func main() {
    
    	var x interface{}
    	var y interface{} = []int{3, 5}
    	_ = x == x
    	_ = y == x //注意虽然slice是不可以比较的类型,但是它可以和any nil比较
    	println("end")
    	_ = y == y //2 slice是不可以比较的
    }
    

    32.对于接收器方法的表达,我们都很陌生

    type N int
    
    func (n N) test() {
    	fmt.Println(n)
    }
    
    func main() {
    	var n N = 10
    	fmt.Println(n)
    
    	n++
    	//func (N).test()
    	f1 := N.test
    	f1(n)
    
    	n++
    	//var f2 func(*N)
    	f2 := (*N).test
    	f2(&n)
    }
    

    33.读写nil类型的chan都会永远阻塞

    func main() {
        var ch chan int
        select {
        case v, ok := <-ch:
            println(v, ok)
        default:
            println("default") 
        }
    }
    

    34.:=操作符不能给结构体字段赋值

    type foo struct {
        bar int
    }
    
    func main() {
        var f foo
        f.bar, tmp := 1, 2
    }
    

    35.go中所有的变量申明了就必须用,但常量除外,常量不能取地址

    func main() {
        const x = 123
        const y = 1.23
        fmt.Println(x)
    }
    

    36.byte在go中是uint8的别名,他们完全等价

    type byte = uint8

    func test(x byte)  {
        fmt.Println(x)
    }
    
    func main() {
        var a byte = 0x11 
        var b uint8 = a
        var c uint8 = a + b
        test(c)
    }
    

    37.关于给切片加索引赋值的注意项

    • 字面量初始化切片时候,可以指定索引,没有指定索引的元素,其索引=前一个索引+1,
    • 空缺的索引的位置,数据就是零值
    var x = []int{2: 2, 3, 0: 1}
    
    func main() {
        fmt.Println(x) // [1 0 2 3]
    }
    
    

    38.关于select

    A. select机制用来处理异步IO问题;//输入输出,要读取或写入chan

    B. select机制最大的一条限制就是每个case语句里必须是一个IO操作;

    C. golang在语言级别支持select关键字;

    39.指针类型的map和slice均不能使用索引

    40.chan的写入方负责关闭,不然造成泄露

    func main() {
         ch := make(chan int, 100)
         // A
         go func() {              
             for i := 0; i < 10; i++ {
                 ch <- i
             }
         }()
         // B
        go func() {
            for {
                a, ok := <-ch
                if !ok {
                    fmt.Println("close")
                    return
                }
                fmt.Println("a: ", a)
            }
        }()
        close(ch)
        fmt.Println("ok")
        time.Sleep(time.Second * 10)
    }
    

    41. chan的几个特性

    A. 给一个 nil channel 发送数据,造成永远阻塞

    B. 从一个 nil channel 接收数据,造成永远阻塞

    C. 给一个已经关闭的 channel 发送数据,引起 panic

    D. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值

    42.类型转换的方式和c系有差异需要注意

    B.
    type MyInt int
    var i int = 1
    var j MyInt = (MyInt)i //c系的方式,在go里面是非法的

    C.

    type MyInt int
    var i int = 1
    var j MyInt = MyInt(i)//在才是正确的方式.

    43.关于for range枚举器,不同版本有不同定义

    type Foo struct {
    bar string
    }
    func main() {
    s1 := []Foo{
    {"A"},
    {"B"},
    {"C"},
    }
    s2 := make([]*Foo, len(s1))
    for i, value := range s1 {
    s2[i] = &value
    }
    fmt.Println(s1[0], s1[1], s1[2])
    fmt.Println(s2[0], s2[1], s2[2])
    }
    1.22输出:
    {A} {B} {C}
    &{A} &{B} &{C}

    1.22之前
    s2 的输出是 &{C} &{C} &{C}

    43.关于defer nil方法

    func f(n int) (r int) {
    	defer func() {
    		r += n
    		recover() //这里捕捉了nil方法的调用
    	}()
    
    	var f func()
    
    	defer f() //f是一个nil,所以没有机会执行下面的方法
    	f = func() {
    		fmt.Println("f called")
    		r += 2
    	}
    	return n + 1
    }
    
    func main() {
    	fmt.Println(f(3)) //7
    }
    
    

    https://www.topgoer.cn/docs/gomianshiti/mian28

  • 相关阅读:
    安全狗云眼的主要功能有哪些?
    懵了,阿里一面就被虐了,幸获内推华为技术四面,成功拿到offer
    使命、愿景、价值观到底有什么区别
    测试经验总结
    基于SSM(非maven)框架的二手商城平台(系统)
    金仓数据库KingbaseES物理备份恢复最佳实践(使用sys_rman的准备工作)
    LCD电子手提秤电路板方案软硬件开发
    Linux ALSA驱动之PCM创建流程源码分析
    muduo源码剖析之TcpServer服务端
    debug技巧之使用arthas调试
  • 原文地址:https://www.cnblogs.com/thinkingmore/p/18031232