老男孩教育专注IT教育10余年,只培养IT技术精英

全国免费咨询电话(渠道合作):400-609-2893

GO语言面试题(七)!老男孩GO培训价格

老男孩IT教育

常见问题

2021年7月12日 17:45

Go是一个开源的编程语言,它能让构造简单、可靠、高效的软件变得容易,并且拥有一个活跃的社区。之前为大家介绍了很多GO语言面试题,相信很多小伙伴已经收藏了,那么本文的GO语言面试题有哪些呢?千万不要错过,这些你们也需要懂。

  Go是一个开源的编程语言,它能让构造简单、可靠、高效的软件变得容易,并且拥有一个活跃的社区。之前为大家介绍了很多GO语言面试题,相信很多小伙伴已经收藏了,那么本文的GO语言面试题有哪些呢?千万不要错过,这些你们也需要懂。

  6、下面代码输出什么?

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                               //line 1
    b := 2                               //2
    defer calc("1", a, calc("10", a, b)) //3
    a = 0                                //4
    defer calc("2", a, calc("20", a, b)) //5
    b = 1                                //6
}

  答:

  运行结果:

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 2、1 1 3 4 。

  7、请写出以下输入内容

package main

import (
    "fmt"
)

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、下面的代码有什么问题?

package main

import (
    "fmt"
    "sync"
)

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
}

func main() {
    userAges := &UserAges{
        ages: make(map[string]int),
    }
    for i := 0; i < 10000; i++ {
        go userAges.Add("oldboy", i)
        go func() {
            age := userAges.Get("oldboy")
            fmt.Println(age)
        }()
    }
}

  答:

  在执行 Get方法时可能被panic

  解析:

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

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

  sync.Mutex互斥锁

  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()

  正确代码:

package main

import (
    "fmt"
    "sync"
)

type UserAges struct {
    ages map[string]int
    sync.RWMutex
}

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

func main() {
    userAges := &UserAges{
        ages: make(map[string]int),
    }
    for i := 0; i < 10000; i++ {
        go userAges.Add("oldboy", i)
        go func() {
            age := userAges.Get("oldboy")
            fmt.Println(age)
        }()
    }
}

  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
}

  答:

  内部迭代出现阻塞。默认初始化时无缓冲区,需要等待接收者读取后才能继续写入。

  解析:

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

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

  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是值类型。

  修改方法:

  定义为指针:

var peo People = &Stduent{}

  方法定义在值类型上,指针类型本身是包含值类型的方法。

func (stu Stduent) Speak(think string) (talk string) { 
    //... 
}

  老男孩GO培训价格是多少?我校GO语言课程分为周末班及网络班,几千元即可学习,欢迎大家前来试听。

  推荐阅读:

  GO语言面试题(六)!老男孩GO语言学习周期

  GO语言面试题(五)!老男孩GO语言怎么样?

  Go语言培训之获取命令行参数

本文经授权发布,不代表老男孩教育立场。如若转载请联系原作者。