Golang面试题

孤者浪人 提交于 2019-12-04 04:02:46

[TOC]

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
   */
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!