Loading

Golang面试题

目录

Golang面试题

所有题目,一行一行敲过亲自反复验证后.100%没有问题.里面加了一些自己的测试结果和理论,应该都是对的.

这一套题最棒的地方在于可以学习到很多书上没有的东西,有很多平时注意不到的地方这里都提到了, 不光是在准备面试,同时也有很多应用非常巧妙的地方可以在工作中借鉴.

这一套题是我在一个PDF上看到的PDF名称<Golang语言社区-04141153.pdf>

1. defer的执行顺序

package main

import "fmt"

func main() {
   deferCall()
}

func deferCall() {

   defer func() { fmt.Println("前") }()
   defer func() { fmt.Println("中") }()
   defer func() { fmt.Println("后") }()

   panic("触发异常")
/**
执行结果
后
中
前
panic: 触发异常

考点:defer执行顺序
解答:
defer 是后进先出。
panic 需要等defer 结束后才会向上传递。 出现panic恐慌时候,会先按照defer的后入先出的顺序执行,最后才 会执行panic。
*/
}

for循环时使用指针赋值为副本形式

package main

import "fmt"

type person struct {
   Name string
   Age  int
}

func pasePerson() {

   m := make(map[string]*person)

   per := []person{
      {Name: "liu", Age: 20},
      {Name: "wang", Age: 21},
      {Name: "li", Age: 22},
   }

   //与Java的foreach和PHP的foreach一样,都是使用副本的方式。所以 m[per.Name]=&per实际上一致指向同一个指针, 最终该指针的值为遍历的最后一个struct的值拷贝。
   for _, value := range per {
      //将per内的元素赋值到m中去
      m[value.Name] = &value
   }

   //打印m的person类,可以看到结果
   for _, value := range m {
      fmt.Println(value)
   }
   /**
   全部都是最后一个值
   &{li 22}
   &{li 22}
   &{li 22}
   */

   //正确的写法
   for i := 0; i < len(per); i++ {
      m[per[i].Name] = &per[i]
   }
   //打印m的person类
   for _, value := range m {
      fmt.Println(value)
   }
   /**
   正确输出内容
   &{wang 21}
   &{li 22}
   &{liu 20}
   */

}

func main() {
   pasePerson()
}

go执行的随机性和闭包的局部变量

package main

import (
   "fmt"
   "runtime"
   "sync"
)

func main() {

   //设置多核执行任务
   runtime.GOMAXPROCS(1)
    //为了使当前进程内所有的线程或协程同时执行完毕后才推出进程,需要相互等待
   wg := sync.WaitGroup{}
    // 把计数器设置为20
   wg.Add(20)
   //错误赋值,导致i会永远都是i的最后一个值,即10
   for i := 0; i < 10; i++ {
      go func() {
         fmt.Println("A:", i)
          //每次把计数器-1
         wg.Done()
      }()
   }

   //正确赋值,i会随着循环不变变化
   for i := 0; i < 10; i++ {
      go func(i int) {
         fmt.Println("B:", i)
          //每次把计数器-1
         wg.Done()
      }(i)
   }
    //阻塞代码的运行,直到计数器地值减为0
   wg.Wait()
   /**
   可以看到goroutine的执行结果是乱序的
   而且由于第一个for里面的goroutine没有传值,导致i每次都是10
   B: 9
   A: 10
   A: 10
   A: 10
   A: 10
   A: 10
   A: 10
   A: 10
   A: 10
   A: 10
   A: 10
   B: 0
   B: 1
   B: 2
   B: 3
   B: 4
   B: 5
   B: 6
   B: 7
   B: 8
   */
}

go的组合继承实现OOP的继承

package main

import "fmt"

type People struct {
}

func (p *People) showA() {
   fmt.Println("people showA")
   p.showB()
}

func (p *People) showB() {
   fmt.Println("people showB`")
}

//继承people类
type teacher struct {
   People
}

//重写teacher的父类people的showB方法
func (t *teacher) showB() {
   fmt.Println("teacher showB")
}

func main() {
   t := teacher{}
   //此处实际上调用的teacher的父类people的showA方法
   //需要特别注意:people的showA方法中的p.showB()会调用父类自己的方法而不是子类teacher中的showB方法
   t.showA()
   //完美重写了teacher的父类people的showB方法
   t.showB()
   /**
   //下面的结果是t.showA()打印的结果
   people showA
   people showB`
   //下面的结果是t.showB()打印的结果
   teacher showB
   */
}

5. select的随机性和buffer channel

package main

import (
   "fmt"
   "runtime"
)

func main() {
   //设置多核执行任务,Go默认执行使用的CPU核心数为系统CPU最大核心
   //并行比较适合cpu计算密集型。如果IO密集型使用多核反而会增加cpu切换的成本。
   runtime.GOMAXPROCS(1)
	//不带缓冲的通道,在写入时,就会发生阻塞,直到通道中信息被读取后,才会结束阻塞。
   intChain := make(chan int, 1)
   stringChain := make(chan string, 1)
    //向缓冲区写入数据
   intChain <- 1
   stringChain <- "this is string chain"

   //随机读取channel内的数据
   select {
   case value := <-intChain:
      fmt.Println(value)
   case value := <-stringChain:
      fmt.Println(value)
   }
   //select是随机执行的
   //有时候输出this is string chain
   //有时候输出1
}

defer调用函数时的执行顺序

package main

import "fmt"

func calc(index string, a, b int) int {
   ret := a + b
   fmt.Println(index, a, b, ret)
   return ret
}
func main() {
   a := 1
   b := 2
   defer calc("1", a, calc("10", a, b))
   a = 0
   defer calc("2", a, calc("20", a, b))
   b = 1
   /**
   需要注意到defer执行顺序和值传递
   index:1肯定是最后执行的,但是index:1的第三个参数是一 个函数,所以最先被调用calc("10",1,2)==>10,1,2,3
   执行index:2时,与之前一样,需要先调用 calc("20",0,2)==>20,0,2,2执行到b=1时候开始调用,
   index:2==>calc("2",0,2)==>2,0,2,2
   最后执行 index:1==>calc("1",1,3)==>1,1,3,4
   10 1 2 3
   20 0 2 2
   2 0 2 2
   1 1 3 4
   */
}

make默认值和append

package main

import "fmt"

func main() {
   //make初始化是由默认值的哦,此处默认值为0,长度为10个元素
   sli := make([]int, 10)
   sli = append(sli, 1, 2, 3)
   fmt.Println(sli)
   //[0 0 0 0 0 0 0 0 0 0 1 2 3]

   //如果sli1设置的长度为0的话,append时长度不够,会新建一个slice来做append
   sli1 := make([]int, 0)
   sli1 = append(sli1, 1, 2, 3, 4, 5)
   fmt.Println(sli1)
   //[1 2 3 4 5]

}

map线程安全

package main

import (
	"fmt"
	"sync"
)

type UserAges struct {
	ages map[string]int
	//读写锁,两个锁都可以实现
	//sync.RWMutex
	//互斥锁,两个锁都可以实现
	sync.Mutex}

func (ua *UserAges) Add(name string, age int) {
	ua.Lock()
	defer ua.Unlock()
	//map必须初始化,或者在外面实例化结构体时实例化map,否则不可用,panic: assignment to entry in nil map
	//ua.ages = make(map[string]int)
	ua.ages[name] = age
}

func (ua *UserAges) Get(name string) int {
	//必须加锁,否则可能会出现fatal error: concurrent map read and map write
	ua.Lock()
	defer ua.Unlock()
	if age, ok := ua.ages[name]; ok {
		return age
	}

	return 0
}

func main() {
	//添加10个用户
	count := 10
	//开启线程等待
	gw := sync.WaitGroup{}
	//设置总等待数量
	gw.Add(count * 3)

	//实例化结构体和map,否则map不可用
	u := UserAges{ages: map[string]int{}}
	add := func(i int) {
		u.Add(fmt.Sprintf("user_%d", i), i)
		//每次把计数器-1
		gw.Done()
	}
	//使用goroutine多次添加数据
	for i := 0; i < count; i++ {
		go add(i)
		go add(i)
	}

	//使用goroutine多次获取数据
	for i := 0; i < count; i++ {
		go func(i int) {
			//每次把计数器-1
			defer gw.Done()
			fmt.Println(u.Get(fmt.Sprintf("user_%d", i)))
		}(i)
	}

	//阻塞代码的运行,直到计数器地值减为0
	gw.Wait()
	fmt.Println("done")

	/**
	
	map属于引用类型,并发读写时多个协程见是通过指针访问同一个地址,即访问共享变量,此时同时读写资源存在竞争关系。
 如果没有ua.Lock();和defer ua.Unlock()会报错误信息:“fatal error: concurrent map read and map write”。
 Go1.9新版本中将提供并发安全的map
 
	0
	0
	3
	2
	4
	1
	7
	6
	8
	0
	done
	*/
}

channel的缓冲区

package main

import (
   "fmt"
   "sync"
)

type ThreadSafeSet struct {
   sync.RWMutex
   s []int
}

func (set *ThreadSafeSet) Iter(channelLen int) <-chan interface{} {
   //声明一个channel,设置长度,如果不设置长度,并且没有接受者的话内部迭代出现阻塞,默认初始化时无缓冲区,需要等待接收者读取后才能继续写入
   //所以外面的那个unReadChannel方法什么都不会输出
   ch := make(chan interface{})
   go func() {
      //加锁
      set.RLock()
      //遍历set.s的slice将所有的结果全部写入到上面声明的channel中
      for elem := range set.s {
         ch <- elem
         fmt.Printf("slice value: %d\n", elem)
      }
      //关闭channel
      close(ch)
      //解锁
      set.RUnlock()
   }()

   return ch
}

//函数会读取channel中的内容
func readChannel() {
   //实例化结构体,声明slice
   set := ThreadSafeSet{}
   channelLen := 10
   set.s = make([]int, channelLen)
   //调用方法遍历slice,将slice中的数据全部传入到channel中
   ch := set.Iter(channelLen)
   closed := false
   for {
      select {
      case v, ok := <-ch:
         if ok {
            fmt.Printf("read:%d\n", v)
         } else {
            closed = true
         }
      }
      if closed {
         fmt.Println("close channel")
         break
      }
   }
   fmt.Println("Done")
}

//函数不会没有读取channel中的内容
func unReadChannel() {
   //实例化结构体,同时设置切片长度
   set := ThreadSafeSet{}
   channelLen := 10
   set.s = make([]int, channelLen)

   //调用方法遍历slice,将slice中的数据全部传入到channel中
   ch := set.Iter(channelLen)
   //不用结果,所以这里这么写
   _ = ch
   //进程休眠五秒
   //time.Sleep(5 * time.Second)
   fmt.Print("done")
}

func main() {

    //这里有输出结果主要是将Iter方法的ch := make(chan interface{})的channel内的数据逐个读取了出来
   //readChannel()
   /**
   read:0
   slice value: 0
   slice value: 1
   slice value: 2
   slice value: 3
   slice value: 4
   slice value: 5
   slice value: 6
   read:1
   read:2
   read:3
   read:4
   read:5
   read:6
   read:7
   slice value: 7
   slice value: 8
   slice value: 9
   read:8
   read:9
   closed
   Done
   */
    //可以看到这里什么都没有输出,因为Iter方法的ch := make(chan interface{})的channel没有设置缓冲区大小,而且unReadChannel没有接收channel里的数据,导致循环内的数据无法写入
   unReadChannel()
   /**

   done

   */
}

10. golang的方法集仅仅影响接口实现和方法表达式转化

package main

import "fmt"

type People interface {
	Speak(string) string
}

type Person struct {
}

func (per *Person) Speak(think string) (talk string) {
	if think == "boy" {
		talk = "good boy"
	} else {
		talk = "hey girl"
	}
	return
}

func main() {
	//值类型 Person{} 未实现接口People的方法,不能定义为 People类型。
	var peo People = Person{}

	think := "bitch"
	fmt.Println(peo.Speak(think))
	/**
	./main.go:22:6: cannot use Person literal (type Person) as type People in assignment:
		Person does not implement People (Speak method has pointer receiver)

	注意:  编译失败, golang的方法集仅仅影响接口实现和方法表达式转化,与通过实例或者指针调用方法无关。
	要想执行成功:
	定义为指针 go var peo People = &Person{}
	Speak()方法定义在值类型上,指针类型本身是包含值类型的方法。func (per Person) Speak(think string) (talk string) { //... }
	*/
}

interface的内部结构

package main

import "fmt"

type People interface {
}

type student struct {
}

func (stu *student) show() {

}

func live() People {
   var stu *student
   return stu

}

func main() {
   if live() == nil {
      fmt.Println("AAA")
   } else {
      fmt.Println("BBB")
   }

   /***
   也就是输此时live()不是nil,及时没有show方法
   BBB
   */
}

这里这个interfece的说法,PDF上说的很经典这里我没有细究:

这个考点是很多人忽略的interface内部结构, go中的接口分为两种。

一种是空的接口类似这样:

var in interface{}

另一种如题目:

type People interface {
      Show()
}

他们的底层结构如下:

type eface struct {      //空接口
    _type *_type         //类型信息
    data  unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type iface struct {      //带有方法的接口
    tab  *itab           //存储type信息还有结构实现方法的集合
    data unsafe.Pointer  //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*)
}
type _type struct {
    size       uintptr  //类型大小
    ptrdata    uintptr  //前缀持有所有指针的内存大小
    hash       uint32   //数据hash值
    tflag      tflag
    align      uint8    //对齐
    fieldalign uint8    //嵌入结构体时的对齐
    kind       uint8    //kind 有些枚举值kind等于0是无效的
    alg        *typeAlg //函数指针数组,类型实现的所有方法
    gcdata    *byte
    str       nameOff
    ptrToThis typeOff
}
type itab struct {
    inter  *interfacetype  //接口类型
    _type  *_type          //结构类型
    link   *itab
    bad    int32
    inhash int32
    fun    [1]uintptr      //可变大小 方法集合
}

可以看出iface比eface 中间多了一层itab结构。 itab 存储_type信息和[]fun方法集,从上面的结构我们就可得出,因为data指向了nil 并不代表interface 是nil, 所以返回值并不为空,这里的fun(方法集)定义了接口的接收规则,在编译的过程中需要验证是否实现接口 结果:

BBBBBBB

不能编译通过!.(type)只能用在interface中

package main

func GetValue() int {
   return 1
}
func main() {
   i := GetValue()
   //.(type)只能用在interface中
   switch i.(type) {
   case int:
      println("int")
   case string:
      println("string")
   case interface{}:
      println("interface")
   default:
      println("unknow")
   }
}

下面函数有什么问题?第一个返回值有sum名称,第二个未命名,所以错误

func funcMui(x, y int) (sum int, error) {
   return x + y, nil
}
/**
在函数有多个返回值时,只要有一个返回值有指定命名,其他的也必须有命名。 
如果返回值有有多个返回值必须加上括号;
如果只有一个返回值并且有命名也需要加上括号; 
此处函数第一个返回值有sum名称,第二个未命名,所以错误。
 */

是否可以编译通过?如果通过,输出什么?考点:defer和函数返回值

package main

func deferFunc1(i int) (t int) {
    //注意这里t=i和deferFunc2的不同之处
   t = i
   defer func() {
      t += 3
   }()

   return t
}

func deferFunc2(i int) int {
    //注意这里的t:=i
   t := i
   defer func() {
      t += 3
   }()
   return t
}

func deferFunc3(i int) (t int) {
   defer func() {
      t += i
   }()

   return 2
}

func main() {
   println(deferFunc1(1))
   println(deferFunc2(1))
   println(deferFunc3(1))
   /**

   需要明确一点是defer需要在函数结束前执行。
   defer在函数结束前执行(在return前被执行)
   函数返回值名字会在函数起始处被初始化为对应类型的零值并且作用域为整个函数

   DeferFunc1中有函数返回值t作用域为整个函数,在return之前defer会被执行,t会被修改,t = 1+3 返回4
   DeferFunc2中匿名函数func() 中的t作用域为func()函数,初始值为0。在return之前defer会被执行,t值被修改为1
   DeferFunc3中 t作用域为整个函数,最后的return 2 中的2 即为要返回的t的值,return之前,defer会被执行,t值被修改,t=2+i 为3
   4
   1
   3
   */
}

15. 是否可以编译通过?对new([]int)的slice进行append()必然失败

package main

import "fmt"

func main() {

   //new返回的是一个指针类型,不能就行append,所以这里只能用make,而且书上也明确说明: slice,map,channel要用make
   list := new([]int)
    //list := make([]int,0)//正确的用法
   list = append(list, 1)
   fmt.Println(list)
   /**
   运行失败,无法执行
    */
}

是否可以编译通过?对两个slice进行append()

package main

import "fmt"

func main() {
   s1 := []int{1, 2, 3}
   s2 := []int{4, 5}
   s1 = append(s1, s2)
   //两个slice进行组合的时候必须要有...
   //s1 = append(s1, s2...)//正确用法
   fmt.Println(s1)
    //执行失败什么都不会输出,只会输出错误信息
}

是否可以编译通过?结构体对比

package main

import (
	"fmt"
	"reflect"
)

func main() {
	//匿名结构体
	sn1 := struct {
		age  int
		name string
	}{age: 1, name: "liu"}
	//匿名结构体
	sn2 := struct {
		age  int
		name string
	}{age: 1, name: "liu"}

	//对比两个匿名结构体
	if sn1 == sn2 {
		println("sn1 == sn2")
	}
	/**
	输出结果
	sn1 == sn2
	 */

	//匿名结构体
	sm1 := struct {
		age  int
		name map[string]string
	}{age: 1, name: map[string]string{"a": "1"}}
	//匿名结构体
	sm2 := struct {
		age  int
		name map[string]string
	}{age: 1, name: map[string]string{"a": "1"}}

	//对比结构体,报错,因为只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关。
	//但是结构体属性中有不可以比较的类型,如map,slice。 如果该结构属性都是可以比较的,那么就可以使用“==”进行比较操作。
	//这里就属于不可比较的类型,map
	if sm1 == sm2 {
		fmt.Println("sm1 == sm2")
	}
	/**
	输出结果
	invalid operation: sm1 == sm2 (struct containing map[string]string cannot be compared)
	*/
	//如果非要比较,可以使用reflect.DeepEqual()来进行比较,该方式可以对比map,slice类型,还有属性顺序不一样的,不过属性顺序不一样的就会不相等.
	if reflect.DeepEqual(sm1, sm2) {
		fmt.Println("sm1 == sm2")
	}else {
		fmt.Println("sm1 != sm2")
	}
	/**
	输出结果
	sm1 == sm2
	*/


	//下面这个就和sn1和sn2的属性顺序不同,sn3与sn1就不是相同的结构体了,不能直接用==进行比较。只能用reflect.DeepEqual反射来比较,但是结果还是sn1 !=sn3
	sn3 := struct {
		name string
		age  int
	}{age: 11, name: "qq"}
	if reflect.DeepEqual(sn1, sn3) {
		fmt.Println("sn1 ==sn3")
	}else {
		fmt.Println("sn1 !=sn3")
	}
	/**
	输出结果
	sn1 !=sn3
	 */
}

是否可以编译通过?interface内部结构(实际上是函数形参为Interface{}时为不限制类型)

package main

import "fmt"

func Foo(x interface{}) {
   if x == nil {
      fmt.Println("empty")
      return
   }
   fmt.Println("not empty")

}
func main() {

   var x *int = nil
   Foo(x)
   /**
   输出结果
   not empty
   */
}

是否可以编译通过?函数返回值类型,函数返回时不是什么类型都能用nil当作空值来返回的

package main

import "fmt"

func getValue(m map[int]string, id int) (string, bool) {
   if _, exist := m[id]; exist {
      return "数据存在", true
   }
   //这里不能返回nil,nil 只可以用作 interface、function、pointer、map、slice 和 channel 的“空值”。但是如果不特别指定的话,Go 语言不能识别类型,所以会报错。
   return nil, false
}

func main() {

   intMap := map[int]string{
      1: "a",
      2: "b",
      3: "c",
   }
   value, err := getValue(intMap, 1)
   fmt.Println(value, err)
   /**
   输出结果
   cannot use nil as type string in return argument
   */

}

20. 是否可以编译通过?iota在自增中间指定一个字符串值

package main

import "fmt"

const (
   x = iota  //这里的值为自增的 0
   y         //这里的值为自增的 1
   z = "aaa" //在这里强制指定值为一个字符串,后面如果没有 p = iota的话,以后全部都是默认 aaa
   k         //这里的值就是aaa
   j         //这里的值就是aaa
   p = iota  //这里的值为继续自增的 5
)

func main() {
   fmt.Println(x, y, z, k, j, p)

   /**
   输出结果
   0 1 aaa aaa aaa 5
   */
}

编译执行下面代码会出现什么?只能在函数内使用变量简短模式

package main

var (
	//这里会报错: syntax error: unexpected :=, expecting type
    //必须要记住:变量简短模式只能在函数内使用,包内无法使用,一方面来说这里已经在var内了没必要再次:=
	size := 1024
    //正确的方式
    //size = 1024
	maxSize = size * 2
)

func main() {

	println(size, maxSize)
	/**
	变量简短模式
	变量简短模式限制:
	定义变量同时显式初始化
	不能提供数据类型
	只能在函数内部使用
	
	输出内容:
	./main.go:4:7: syntax error: unexpected :=, expecting type
	*/
}

下面函数有什么问题?常量不能使用取值符取址

package main

//常量不能取址
const cl = 100

//变量可以正常取址
var bl = 123

func main() {
   //变量可以正常使用取值符取址
   println(&bl, bl)
   //注意: 常量和变量不同,常量不在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,所以这里取地址会报错
   //这里要争取运行就不能取址
   println(&cl, cl)
   /**
   输出结果:
   ./main.go:9:10: cannot take the address of cl
   */
}

编译执行下面代码会出现什么?goto不能跳转到其他函数或内层代码

package main

func main() {
   for i := 0; i < 10; i++ {
      //在这里声明lap是不允许的,直接语法报错
   loop:
      println(i)
   }
   //goto不能跳转到其他函数或者内层代码
   goto loop
   /**
   输出结果:
   ./main.go:9:7: goto loop jumps into block starting at ./main.go:4:26

   */
    	
    
    /**
	这里我引申出一段代码,下面代码不能直接运行因为诸多变量都没有声明,但是这里我们主要是看里面的forloop 和 break forloop
	这里这段的break forloop和上面的goto 有异曲同工之妙,goto要写也只能这么写不能再for循环内部使用或跳转到其他函数

		e := func(dc dataChan) error {
	forloop:
		for {
			select {
			case d := <-dc:
				log.Printf("Executor received:%v", d)
			default:
				break forloop
			}
		}
		return nil
	}
	*/
}

编译执行下面代码会出现什么?Go 1.9 新特性 Type Alias基于一个类型创建一个新类型,其两种创建方式的赋值区别

package main

import "fmt"

func main() {
   type MyInt1 int
   type MyInt2 = int
   var i int = 9
   //基于一个类型创建一个新类型,称之为defintion;基于一个类型用等号创建一个别名,称之为alias。
   //MyInt1为称之为defintion,虽然底层类型为int类型,但是不能直接赋值,需要强转;
   //MyInt2称之为alias,可以直接赋值。 alias拥有所有的赋值的内容.
   var i1 MyInt1 = i//运行失败, 不能直接赋值
   var i2 MyInt2 = i//运行成功, 可以直接赋值
   //正确方式
   //var i1 MyInt1 = MyInt1(i)
   //或者
   //i1 := i
   fmt.Println(i1, i2)
   /**
   输出结果
   ./main.go:9:6: cannot use i (type int) as type MyInt1 in assignment
   ./main.go:10:6: cannot use i (type int) as type MyInt2 in assignment
   */
}

25. 编译执行下面代码会出现什么?Go 1.9 新特性 Type Alias基于一个类型创建一个新类型,其两种创建方式的函数调用的区别

package main

import "fmt"

type user struct {
}

//注意着两个的不同,在24题我们说过:基于一个类型创建一个新类型,称之为defintion;基于一个类型用=(等号赋值)创建一个别名,称之为alias。
type myUser1 user   //defintion 不能直接赋值
type myUser2 = user //alias    可以直接赋值,因为alis用=,相当myUser2等价于user,拥有了user所有的内容

func (u1 myUser1) m1() {
   fmt.Println("this is myUser1.m1")
}
func (u user) m2() {
   fmt.Println("this is User.m2")
}

func main() {
   var u1 myUser1 //defintion
   var u2 myUser2 //alias 拥有赋值类型的所有内容,所以这里可以直接调用user的m2()方法
   u1.m1()
   u2.m2()
   /**
   输出结果:
   this is myUser1.m1
   this is User.m2
   */

}

编译执行下面代码会出现什么?Go 1.9 新特性 Type Alias组合继承中出现重复的方法名的情况

package main

import "fmt"

type T struct {
}

func (t T) m1() {
   fmt.Println("T.m1")
}

type T1 = T
type MyStruct struct {
   T
   T1
}

func main() {
   my := MyStruct{}
    //type alias的定义,本质上是一样的类型,只是起了一个别名,源类型怎么用,别名类型也怎么用,保留源类型的所有方法、字段等。
   //因为alias拥有赋值类型的所有内容,而且这里的MyStruct内部又包含了t,和t1,所有直接调用m1的话,会不知道调用的是哪个m1,必须明确指出.
   //结果不限于方法,字段也也一样;也不限于type alias,type defintion也是一样的,只要有重复的方法、字段,就会有这种提示,因为不知道该选择哪个。
   my.m1()
   //正确方式
   //my.T.m1()
   //my.T1.m1()

   /**
   执行结果
   ./main.go:21:4: ambiguous selector my.m1
   */

}

编译执行下面代码会出现什么?变量作用域-变量覆盖

package main

import (
   "errors"
   "fmt"
)

var ErrDidNotWork = errors.New("not work")

//错误函数,不管输入什么结果都是返回nil
func doSomething(doIt bool) (err error) {
   //注意: 这是个雷区, 因为 if 语句块内的 err 变量会遮罩函数作用域内的 err 变量,要想得到正确结果下面不能使用:=trySomething()而是要使用=赋值
   if doIt {
      result, err := trySomething()
      if err != nil || result != "worked" {
         err = ErrDidNotWork
      }
   }
   return err
}

//正确函数
func doTheThing(doIt bool) (err error) {
   //注意: 这是个雷区, 因为 if 语句块内的 err 变量会遮罩函数作用域内的 err 变量,要想得到正确结果下面不能使用:=trySomething()而是要使用=赋值
   var result string
   if doIt {
      result, err = trySomething()
      if err != nil || result != "worked" {
         err = ErrDidNotWork
      }
   }
   return err
}
func trySomething() (string, error) {
   return "", ErrDidNotWork
}
func main() {
   //错误方式,不管输入什么其结果都是nil
   fmt.Println(doSomething(true))
   fmt.Println(doSomething(false))
   /**
   执行结果:
   <nil>
   <nil>
   */
   //正确的方式
   fmt.Println(doTheThing(true))
   fmt.Println(doTheThing(false))
   /**
   执行结果
   not work
   <nil>
   */

}

编译执行下面代码会出现什么?for循环内闭包延迟求值,for循环复用局部变量i,每一次放入匿名函数的应用都是循环的最后一个值。

package main

//错误的使用方式,每次的结果都是循环的最后一个值
func myTest() []func() {
   var fun []func()
   for i := 0; i < 5; i++ {
      //for循环内闭包延迟求值,for循环复用局部变量i,每一次放入匿名函数的应用都是循环的最后一个值。
      fun = append(fun, func() {
         println(&i, i)
      })
   }
   return fun
}

//正确的使用方式
func myTest1() []func() {
   var fun []func()
   for i := 0; i < 5; i++ {
      x := i
      //for循环内闭包延迟求值,for循环复用局部变量i,每一次放入匿名函数的应用都是最后一个变量。所以这里我们拿到值以后重新在内部声明一个局部变量
      fun = append(fun, func() {
         println(&x, x)
      })
   }
   return fun
}

func main() {
   fun := myTest()
   for _, f := range fun {
      f()
   }
   /**
   输出结果:
   0xc000016070 5
   0xc000016070 5
   0xc000016070 5
   0xc000016070 5
   0xc000016070 5

   */

   //正确的使用方式
   fun1 := myTest1()
   for _, f := range fun1 {
      f()
   }
   /**
   输错结果:
   0xc000016078 0
   0xc000016080 1
   0xc000016088 2
   0xc000016090 3
   0xc000016098 4

   */
}

编译执行下面代码会出现什么? 闭包引用相同变量*,即闭包内使用相同变量名时为指针模式

package main

func myTest(x int) (func(), func()) {
   //闭包引用相同变量*,即闭包内使用相同变量名时为指针模式
   return func() {
         println(x) //此时打印为100
         x += 10
      }, func() {
         println(x) //此时打印为110
      }
}

func main() {
   x, y := myTest(100)
   x()
   y()
   /**
   输出结果:
   100
   110

   */
}

30. 编译执行下面代码会出现什么?panic仅有最后一个可以被revover捕获

package main

import (
   "fmt"
   "reflect"
)

//func main()  {
// defer func() {
//    if err:=recover();err!=nil{
//       fmt.Println(err)
//    }else {
//       fmt.Println("fatal")
//    }
// }()
//
// defer func() {
//    panic("defer panic")
// }()
// panic("panic")
//
// /**
// 执行结果
// defer panic
// */
//}

func main() {
   defer func() {
      if err := recover(); err != nil {
         fmt.Println("----------")
         f := err.(func() string)
         fmt.Println(err, f(), reflect.TypeOf(err).Kind().String())
      } else {
         fmt.Println("fatal")
      }
   }()

   defer func() {
      panic(func() string {
         return "defer panic"
      })
   }()
   panic("panic")
   /**
   触发panic("panic")后顺序执行defer,但是defer中还有一个panic,所以覆盖了之前的panic("panic")
   输出结果:
   ----------
   0x10932e0 defer panic func
   */
}
posted @ 2021-07-30 15:37  我爱吃炒鸡  阅读(324)  评论(0编辑  收藏  举报