zoukankan      html  css  js  c++  java
  • Go 面试题(附答案解析)

    1、写出下面代码输出内容

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        defer_call()
    }
    
    func defer_call() {
        defer func() { fmt.Println("打印前") }()
        defer func() { fmt.Println("打印中") }()
        defer func() { fmt.Println("打印后") }()
    
        panic("触发异常")
    }     

    在线运行

    答: 输出内容为:

    打印后
    打印中
    打印前
    panic: 触发异常

    解析:考察对 defer 的理解,defer 函数属延迟执行,延迟到调用者函数执行 return 命令前被执行。多个 defer 之间按 LIFO 先进后出顺序执行。

    故考题中,在 Panic 触发时结束函数运行,在 return 前先依次打印:打印后、打印中、打印前 。最后由 runtime 运行时抛出打印 panic 异常信息。

    需要注意的是,函数的 return value 不是原子操作,而是在编译器中分解为两部分:返回值赋值 和 return 。而 defer 刚好被插入到末尾的 return 前执行。故可以在 derfer 函数中修改返回值。如下示例:

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        fmt.Println(doubleScore(0))    //0
        fmt.Println(doubleScore(20.0)) //40
        fmt.Println(doubleScore(50.0)) //50
    }
    
    func doubleScore(source float32) (score float32) {
        defer func() {
            if score < 1 || score >= 100 {
                //将影响返回值
                score = source
            }
        }()
        score = source * 2
        return
    
        //或者
        //return source * 2
    }

    在线运行

    该实例可以在 defer 中修改返回值 score 的值。具体参见官方文档

    2、以下代码有什么问题,说明原因

    package main
    
    import (
        "fmt"
    )
    
    type student struct {
        Name string
        Age  int
    }
    
    func pase_student() map[string]*student {
        m := make(map[string]*student)
        stus := []student{
            {Name: "zhou", Age: 24},
            {Name: "li", Age: 23},
            {Name: "wang", Age: 22},
        }
        for _, stu := range stus {
            m[stu.Name] = &stu
        }
        return m
    }
    
    func main() {
        students := pase_student()
        for k, v := range students {
            fmt.Printf("key=%s,value=%v 
    ", k, v)
        }
    }

    在线运行

    答:输出的均是相同的值:&{wang 22}

    解析:因为 for 遍历时,变量 stu 指针不变,每次遍历仅进行 struct 值拷贝,故 m[stu.Name]=&stu 实际上一致指向同一个指针,最终该指针的值为遍历的最后一个 struct 的值拷贝。形同如下代码:

    var stu student
    for _, stu = range stus {
        m[stu.Name] = &stu
    } 

    修正方案,取数组中原始值的指针:

    for i, _ := range stus {
        stu:=stus[i]
        m[stu.Name] = &stu
    }

    3、下面的代码会输出什么,并说明原因

    func main() {
        runtime.GOMAXPROCS(1)
        wg := sync.WaitGroup{}
        wg.Add(20)
        for i := 0; i < 10; i++ {
            go func() {
                fmt.Println("i: ", i)
                wg.Done()
            }()
        }
        for i := 0; i < 10; i++ {
            go func(i int) {
                fmt.Println("i: ", i)
                wg.Done()
            }(i)
        }
        wg.Wait()
    }

    在线运行

    答: 将随机输出数字,但前面一个循环中并不会输出所有值。

    解析:实际上第一行是否设置 CPU 为 1 都不会影响后续代码。两个 for 循环内部 go func 调用参数i的方式是不同的,导致结果完全不同。这也是新手容易遇到的坑。

    第一个 go func 中 i 是外部 for 的一个变量,地址不变化。遍历完成后,最终 i=10。故 go func 执行时,i的值始终是 10(10次遍历很快完成)。

    第二个 go func 中 i 是函数参数,与外部 for 中的i完全是两个变量。尾部(i)将发生值拷贝,go func 内部指向值拷贝地址。

    4、下面代码会输出什么?

    type People struct{}
    
    func (p *People) ShowA() {
        fmt.Println("showA")
        p.ShowB()
    }
    
    func (p *People) ShowB() {
        fmt.Println("showB")
    }
    
    type Teacher struct {
        People
    }
    
    func (t *Teacher) ShowB() {
        fmt.Println("teacher showB")
    }
    
    func main() {
        t := Teacher{}
        t.ShowA()
    }

    在线运行

    答: 将输出:

    showA
    showB

    解析:Go 中没有继承! 没有继承!没有继承!是叫组合!组合!组合!

    这里 People 是匿名组合 People。被组合的类型 People 所包含的方法虽然升级成了外部类型 Teacher 这个组合类型的方法,但他们的方法(ShowA())调用时接受者并没有发生变化。

    这里仍然是 People。毕竟这个 People 类型并不知道自己会被什么类型组合,当然也就无法调用方法时去使用未知的组合者 Teacher 类型的功能。

    因此这里执行 t.ShowA() 时,在执行 ShowB() 时该函数的接受者是 People,而非 Teacher。具体参见官方文档

    5、下面代码会触发异常吗?请详细说明

    func main() {
        runtime.GOMAXPROCS(1)
        int_chan := make(chan int, 1)
        string_chan := make(chan string, 1)
    
        int_chan <- 1
        string_chan <- "hello"
    
        select {
        case value := <-int_chan:
            fmt.Println(value)
        case value := <-string_chan:
            panic(value)
        }
    }

    在线运行

    答: 有可能触发异常,是随机事件。

    解析:单个 chan 如果无缓冲时,将会阻塞。但结合 select 可以在多个 chan 间等待执行。有三点原则:

    • select 中只要有一个 case 能 return,则立刻执行。
    • 当如果同一时间有多个 case 均能 return 则伪随机方式抽取任意一个执行。
    • 如果没有一个 case 能 return 则可以执行“default”块。

    此考题中的两个 case 中的两个 chan 均能 return,则会随机执行某个 case 块。故在执行程序时,有可能执行第二个 case,触发异常。具体参见官方文档

    6、下面代码输出什么?

    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
    }

    在线运行

    答:输出结果为:

    10 1 2 3
    20 0 2 2
    2 0 2 2
    1 1 3 4

    解析:在解题前需要明确两个概念: + defer 是在函数末尾的 return 前执行,先进后执行,具体见问题1。 + 函数调用时 int 参数发生值拷贝。

    不管代码顺序如何,defer calc func 中参数 b 必须先计算,故会在运行到第三行时,执行 calc("10",a,b) 输出:10 1 2 3 得到值 3,将 cal("1",1,3) 存放到延后执执行函数队列中。

    执行到第五行时,现行计算 calc("20", a, b) 即 calc("20", 0, 2) 输出:20 0 2 2 得到值 2,将 cal("2",0,2) 存放到延后执行函数队列中。

    执行到末尾行,按队列先进后出原则依次执行:cal("2",0,2)cal("1",1,3) ,依次输出:2 0 2 21 1 3 4 。

    7、请写出以下输入内容

    func main() {
        s := make([]int, 5)
        s = append(s, 1, 2, 3)
        fmt.Println(s)
    }

    在线运行

    答: 将输出:[0 0 0 0 0 1 2 3]

    解析:make 可用于初始化数组,第二个可选参数表示数组的长度。数组是不可变的。

    当执行 make([]int,5) 时返回的是一个含义默认值(int的默认值为0)的数组:[0,0,0,0,0]。而 append 函数是便是在一个数组或 slice 后面追加新的元素,并返回一个新的数组或 slice。

    这里 append(s,1,2,3) 是在数组s的继承上追加三个新元素:1、2、3,故返回的新数组为 [0 0 0 0 0 1 2 3]

    8、下面的代码有什么问题?

    type UserAges struct {
        ages map[string]int
        sync.Mutex
    }
    
    func (ua *UserAges) Add(name string, age int) {
        ua.Lock()
        defer ua.Unlock()
        ua.ages[name] = age
    }
    
    func (ua *UserAges) Get(name string) int {
        if age, ok := ua.ages[name]; ok {
            return age
        }
        return -1
    }

    在线运行

    答: 在执行 Get 方法时可能被 panic

    解析:虽然有使用 sync.Mutex 做写锁,但是 map 是并发读写不安全的。map 属于引用类型,并发读写时多个协程见是通过指针访问同一个地址,即访问共享变量,此时同时读写资源存在竞争关系。会报错误信息:“fatal error: concurrent map read and map write”。

    可以在在线运行中执行,复现该问题。那么如何改善呢? 当然 Go1.9 新版本中将提供并发安全的 map。首先需要了解两种锁的不同:

    1. sync.Mutex 互斥锁
    2. sync.RWMutex 读写锁,基于互斥锁的实现,可以加多个读锁或者一个写锁。

    利用读写锁可实现对 map 的安全访问,在线运行改进版 。利用 RWutex 进行读锁。

    type RWMutex
        func (rw *RWMutex) Lock()
        func (rw *RWMutex) RLock()
        func (rw *RWMutex) RLocker() Locker
        func (rw *RWMutex) RUnlock()
        func (rw *RWMutex) Unlock()

    9、下面的迭代会有什么问题?

    func (set *threadSafeSet) Iter() <-chan interface{} {
        ch := make(chan interface{})
        go func() {
            set.RLock()
    
            for elem := range set.s {
                ch <- elem
            }
    
            close(ch)
            set.RUnlock()
    
        }()
        return ch
    }

    在线运行

    答: 内部迭代出现阻塞。ch 初始化的容量默认为 1,如果外部不及时读取,则内部迭代是阻塞状态。

    解析:chan 在使用 make 初始化时可附带一个可选参数来设置缓冲区。默认无缓冲,题目中便初始化的是无缓冲区的 chan,这样只有写入的元素直到被读取后才能继续写入,不然就一直阻塞。

    设置缓冲区大小后,写入数据时可连续写入到缓冲区中,直到缓冲区被占满。从 chan 中接收一次便可从缓冲区中释放一次。可以理解为 chan 是可以设置吞吐量的处理池。

    纠正一下:

    内部迭代出现阻塞。ch 初始化的容量默认为1,如果外部不及时读取,则内部迭代是阻塞状态。 这句话是错误的

    ch := make(chan interface{}) 和 ch := make(chan interface{},1)是不一样的

    无缓冲的 不仅仅是只能向 ch 通道放 一个值 而是一直要有人接收,那么 ch <- elem 才会继续下去,要不然就一直阻塞着,也就是说有接收者才去放,没有接收者就阻塞。

    而缓冲为1则即使没有接收者也不会阻塞,因为缓冲大小是 1 只有当 放第二个值的时候 第一个还没被人拿走,这时候才会阻塞

    10、以下代码能编译过去吗?为什么?

    package main
    
    import (
        "fmt"
    )
    
    type People interface {
        Speak(string) string
    }
    
    type Stduent struct{}
    
    func (stu *Stduent) Speak(think string) (talk string) {
        if think == "bitch" {
            talk = "You are a good boy"
        } else {
            talk = "hi"
        }
        return
    }
    
    func main() {
        var peo People = Stduent{}
        think := "bitch"
        fmt.Println(peo.Speak(think))
    }

    在线运行

    答: 编译失败,值类型 Student{} 未实现接口 People 的方法,不能定义为 People 类型。

    解析:考题中的 func (stu *Stduent) Speak(think string) (talk string) 是表示结构类型 *Student 的指针有提供该方法,但该方法并不属于结构类型 Student 的方法。因为 struct 是值类型。

    修改方法:

    • 定义为指针 go var peo People = &Stduent{}
    • 方法定义在值类型上,指针类型本身是包含值类型的方法。 go func (stu Stduent) Speak(think string) (talk string) { //... }

    摘自:

    https://gocn.io/article/395

    https://yushuangqi.com/blog/2017/golang-mian-shi-ti-da-an-yujie-xi.html

  • 相关阅读:
    【数学】三分法
    【数学】【背包】【NOIP2018】P5020 货币系统
    【数学】【CF27E】 Number With The Given Amount Of Divisors
    【单调队列】【P3957】 跳房子
    【极值问题】【CF33C】 Wonderful Randomized Sum
    【DP】【CF31E】 TV Game
    【神仙题】【CF28D】 Don't fear, DravDe is kind
    【线段树】【CF19D】 Points
    【字符串】KMP字符串匹配
    【二维树状数组】【CF10D】 LCIS
  • 原文地址:https://www.cnblogs.com/52php/p/7213885.html
Copyright © 2011-2022 走看看