zoukankan      html  css  js  c++  java
  • go面试题[3]

    第一题

    1.下面的两个切片声明中有什么区别?哪个更可取?

    A. var a []int
    B. a := []int{}
    参考答案及解析:A 声明的是 nil 切片;B 声明的是长度和容量都为 0 的空切片。第一种切片声明不会分配内存,优先选择。

    2. A、B、C、D 哪些选项有语法错误?

    type S struct {
    }
    
    func f(x interface{}) {
    }
    
    func g(x *interface{}) {
    }
    
    func main() {
        s := S{}
        p := &s
        f(s) //A
        g(s) //B
        f(p) //C
        g(p) //D
    }

    参考答案及解析:BD。函数参数为 interface{} 时可以接收任何类型的参数,包括用户自定义类型等,即使是接收指针类型也用 interface{},而不是使用 *interface{}

    永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。
     

    3.下面 A、B 两处应该填入什么代码,才能确保顺利打印出结果?

    type S struct {
        m string
    }
    
    func f() *S {
        return __  //A
    }
    
    func main() {
        p := __    //B
        fmt.Println(p.m) //print "foo"
    }
    A. &S{"foo"} 
    B. *f() 或者 f()

    f() 函数返回参数是指针类型,所以可以用 & 取结构体的指针;B 处,如果填 *f(),则 p 是 S 类型;如果填 f(),则 p 是 *S 类型,不过都可以使用 p.m 取得结构体的成员。

    第二题

    1.下面的代码有几处语法问题,各是什么?

    package main
    import (
        "fmt"
    )
    func main() {
        var x string = nil
        if x == nil {
            x = "default"
        }
        fmt.Println(x)
    }

    参考答案及解析:两个地方有语法问题。golang 的字符串类型是不能赋值 nil 的,也不能跟 nil 比较。

    2.return 之后的 defer 语句会执行吗,下面这段代码输出什么?

    var a bool = true
    func main() {
        defer func(){
            fmt.Println("1")
        }()
        if a == true {
            fmt.Println("2")
            return
        }
        defer func(){
            fmt.Println("3")
        }()
    }

    参考答案及解析:2 1。defer 关键字后面的函数或者方法想要执行必须先注册,return 之后的 defer 是不能注册的, 也就不能执行后面的函数或方法

    第三题

    1.下面这段代码输出什么?为什么?

    func main() {
    
        s1 := []int{1, 2, 3}
        s2 := s1[1:]
        s2[1] = 4
        fmt.Println(s1)
        s2 = append(s2, 5, 6, 7)
        fmt.Println(s1)
    }

    参考答案及解析:

    [1 2 4]

    [1 2 4]

    我们知道,golang 中切片底层的数据结构是数组。当使用 s1[1:] 获得切片 s2,和 s1 共享同一个底层数组,这会导致 s2[1] = 4 语句影响 s1。

    而 append 操作会导致底层数组扩容,生成新的数组,因此追加数据后的 s2 不会影响 s1。

    但是为什么对 s2 赋值后影响的却是 s1 的第三个元素呢?这是因为切片 s2 是从数组的第二个元素开始,s2 索引为 1 的元素对应的是 s1 索引为 2 的元素。

    2.下面选项正确的是?

    func main() {
        if a := 1; false {
        } else if b := 2; false {
        } else {
            println(a, b)
        }
    }
    • A. 1 2

    • B. compilation error

    参考答案及解析:A。知识点:代码块和变量作用域。

    第四题

    1.下面这段代码输出什么?

    func main() {
        m := map[int]string{0:"zero",1:"one"}
        for k,v := range m {
            fmt.Println(k,v)
        }
    }

    参考答案及解析:

    0 zero
    1 one
    // 或者
    1 one
    0 zero

    map 的输出是无序的。

    2.下面这段代码输出什么?

    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
    }
    
    func calc(index string, a, b int) int {
        ret := a + b
        fmt.Println(index, a, b, ret)
        return ret
    }

    参考答案及解析:

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

    程序执行到 main() 函数三行代码的时候,会先执行 calc() 函数的 b 参数,即:calc(“10”,a,b),输出:10 1 2 3,得到值 3,因为
    defer 定义的函数是延迟函数,故 calc(“1”,1,3) 会被延迟执行;

    程序执行到第五行的时候,同样先执行 calc(“20”,a,b) 输出:20 0 2 2 得到值 2,同样将 calc(“2”,0,2) 延迟执行;

    程序执行到末尾的时候,按照栈先进后出的方式依次执行:calc(“2”,0,2),calc(“1”,1,3),则就依次输出:2 0 2 2,1 1 3 4。

    第五题:

    1.下面这段代码输出什么?为什么?

    func (i int) PrintInt ()  {
        fmt.Println(i)
    }
    
    func main() {
        var i int = 1
        i.PrintInt()
    }
    • A. 1

    • B. compilation error

    参考答案及解析:B。基于类型创建的方法必须定义在同一个包内,上面的代码基于 int 类型创建了 PrintInt() 方法,由于 int 类型和方法 PrintInt() 定义在不同的包内,所以编译出错。

    解决的办法可以定义一种新的类型:

    type Myint int
    
    func (i Myint) PrintInt ()  {
        fmt.Println(i)
    }
    
    func main() {
        var i Myint = 1
        i.PrintInt()
    }

    2.下面这段代码输出什么?为什么?

    type People interface {
        Speak(string) string
    }
    
    type Student struct{}
    
    func (stu *Student) Speak(think string) (talk string) {
        if think == "speak" {
            talk = "speak"
        } else {
            talk = "hi"
        }
        return
    }
    
    func main() {
        var peo People = Student{}
        think := "speak"
        fmt.Println(peo.Speak(think))
    }
    • A. speak

    • B. compilation error

    参考答案及解析:B。编译错误 Student does not implement People (Speak method has pointer receiver),值类型 Student 没有实现接口的 Speak() 方法,而是指针类型 *Student 实现该方法。

    第六题

     

    1.下面这段代码输出什么?

    const (
        a = iota
        b = iota
    )
    const (
        name = "name"
        c    = iota
        d    = iota
    )
    func main() {
        fmt.Println(a)
        fmt.Println(b)
        fmt.Println(c)
        fmt.Println(d)
    }

    参考答案及解析:0 1 1 2。知识点:iota 的用法。

    iota 是 golang 语言的常量计数器,只能在常量的表达式中使用。

    iota 在 const 关键字出现时将被重置为0,const中每新增一行常量声明将使 iota 计数一次。

    2.下面这段代码输出什么?为什么?

    type People interface {
        Show()
    }
    
    type Student struct{}
    
    func (stu *Student) Show() {
    
    }
    
    func main() {
    
        var s *Student
        if s == nil {
            fmt.Println("s is nil")
        } else {
            fmt.Println("s is not nil")
        }
        var p People = s
        if p == nil {
            fmt.Println("p is nil")
        } else {
            fmt.Println("p is not nil")
        }
    }

    参考答案及解析:s is nil 和 p is not nil。这道题会不会有点诧异,我们分配给变量 p 的值明明是 nil,然而 p 却不是 nil。记住一点,当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。上面的代码,给变量 p 赋值之后,p 的动态值是 nil,但是动态类型却是 *Student,是一个 nil 指针,所以相等条件不成立

    第七题

    1.下面这段代码输出什么?

    type Direction int
    
    const (
        North Direction = iota
        East
        South
        West
    )
    
    func (d Direction) String() string {
        return [...]string{"North", "East", "South", "West"}[d]
    }
    
    func main() {
        fmt.Println(South)
    }

    参考答案及解析:South。知识点:iota 的用法、类型的 String() 方法。

    根据 iota 的用法推断出 South 的值是 2;另外,如果类型定义了 String() 方法,当使用 fmt.Printf()、fmt.Print() 和 fmt.Println() 会自动使用 String() 方法,实现字符串的打印。

    2.下面代码输出什么?

    type Math struct {
        x, y int
    }
    
    var m = map[string]Math{
        "foo": Math{2, 3},
    }
    
    func main() {
        m["foo"].x = 4
        fmt.Println(m["foo"].x)
    }
    • A. 4

    • B. compilation error

    参考答案及解析:B,编译报错 cannot assign to struct field m[“foo”].x in map。错误原因:对于类似 X = Y的赋值操作,必须知道 X 的地址,才能够将 Y 的值赋给 X,但 go 中的 map 的 value 本身是不可寻址的。

    有两个解决办法:

    1.使用临时变量

    type Math struct {
        x, y int
    }
    
    var m = map[string]Math{
        "foo": Math{2, 3},
    }
    
    func main() {
        tmp := m["foo"]
        tmp.x = 4
        m["foo"] = tmp
        fmt.Println(m["foo"].x)
    }

    2.修改数据结构

    type Math struct {
        x, y int
    }
    
    var m = map[string]*Math{
        "foo": &Math{2, 3},
    }
    
    func main() {
        m["foo"].x = 4
        fmt.Println(m["foo"].x)
        fmt.Printf("%#v", m["foo"])   // %#v 格式化输出详细信息
    }

     第八题

    1.下面的代码有什么问题?

    func main() {
        fmt.Println([...]int{1} == [2]int{1})
        fmt.Println([]int{1} == []int{1})
    }

    参考答案及解析:有两处错误

    • go 中不同类型是不能比较的,而数组长度是数组类型的一部分,所以 […]int{1} 和 [2]int{1} 是两种不同的类型,不能比较;

    • 切片是不能比较的;

    2.下面这段代码输出什么?

    var p *int
    
    func foo() (*int, error) {
        var i int = 5
        return &i, nil
    }
    
    func bar() {
        //use p
        fmt.Println(*p)
    }
    
    func main() {
        p, err := foo()
        if err != nil {
            fmt.Println(err)
            return
        }
        bar()
        fmt.Println(*p)
    }
    • A. 5 5

    • B. runtime error

    参考答案及解析:B。知识点:变量作用域。问题出在操作符:=,对于使用:=定义的变量,如果新变量与同名已定义的变量不在同一个作用域中,那么 Go 会新定义这个变量。对于本例来说,main() 函数里的 p 是新定义的变量,会遮住全局变量 p,导致执行到bar()时程序,全局变量 p 依然还是 nil,程序随即 Crash。

    正确的做法是将 main() 函数修改为:

    func main() {
        var err error
        p, err = foo()
        if err != nil {
            fmt.Println(err)
            return
        }
        bar()
        fmt.Println(*p)
    }

     第九题

    1.下面这段代码能否正常结束?

    func main() {
        v := []int{1, 2, 3}
        for i := range v {
            v = append(v, i)
        }
    }

    参考答案及解析:不会出现死循环,能正常结束。
    循环次数在循环开始前就已经确定,循环内改变切片的长度,不影响循环次数。

    2.下面这段代码输出什么?为什么?

    func main() {
    
        var m = [...]int{1, 2, 3}
    
        for i, v := range m {
            go func() {
                fmt.Println(i, v)
            }()
        }
    
        time.Sleep(time.Second * 3)
    }

    参考答案及解析:

    2 3
    2 3
    2 3

    for range 使用短变量声明(:=)的形式迭代变量,需要注意的是,变量 i、v 在每次循环体中都会被重用,而不是重新声明。

    各个 goroutine 中输出的 i、v 值都是 for range 循环结束后的 i、v 最终值,而不是各个goroutine启动时的i, v值。可以理解为闭包引用,使用的是上下文环境的值。

    1.使用函数传递

    for i, v := range m {
        go func(i,v int) {
            fmt.Println(i, v)
        }(i,v)
    }

    2.使用临时变量保留当前值

    for i, v := range m {
        i := i           // 这里的 := 会重新声明变量,而不是重用
        v := v
        go func() {
            fmt.Println(i, v)
        }()
    }

    第十天

    1.下面这段代码输出什么?

    func f(n int) (r int) {
        defer func() {
            r += n
            recover()
        }()
    
        var f func()
    
        defer f()
        f = func() {
            r += 2
        }
        return n + 1
    }
    
    func main() {
        fmt.Println(f(3))
    }

    参考答案及解析:7。根据 5 年 Gopher 都不知道的 defer 细节,你别再掉进坑里! 提到的“三步拆解法”,第一步执行r = n +1,接着执行第二个 defer,由于此时 f() 未定义,引发异常,随即执行第一个 defer,异常被 recover(),程序正常执行,最后 return。

    此题引自知识星球《Go项目实战》。

    2.下面这段代码输出什么?

    func main() {
        var a = [5]int{1, 2, 3, 4, 5}
        var r [5]int
    
        for i, v := range a {
            if i == 0 {
                a[1] = 12
                a[2] = 13
            }
            r[i] = v
        }
        fmt.Println("r = ", r)
        fmt.Println("a = ", a)
    }

    参考答案及解析:

    r =  [1 2 3 4 5]
    a =  [1 12 13 4 5]

    range 表达式是副本参与循环,就是说例子中参与循环的是 a 的副本,而不是真正的 a。就这个例子来说,假设 b 是 a 的副本,则 range 循环代码是这样的

    for i, v := range b {
        if i == 0 {
            a[1] = 12
            a[2] = 13
        }
        r[i] = v
    }

    因此无论 a 被如何修改,其副本 b 依旧保持原值,并且参与循环的是 b,因此 v 从 b 中取出的仍旧是 a 的原值,而非修改后的值。

    如果想要 r 和 a 一样输出,修复办法:

    func main() {
        var a = [5]int{1, 2, 3, 4, 5}
        var r [5]int
    
        for i, v := range &a {
            if i == 0 {
                a[1] = 12
                a[2] = 13
            }
            r[i] = v
        }
        fmt.Println("r = ", r)
        fmt.Println("a = ", a)
    }

    输出:

    r =  [1 12 13 4 5]
    a =  [1 12 13 4 5]

    修复代码中,使用 *[5]int 作为 range 表达式,其副本依旧是一个指向原数组 a 的指针,因此后续所有循环中均是 &a 指向的原数组亲自参与的,因此 v 能从 &a 指向的原数组中取出 a 修改后的值。

  • 相关阅读:
    DataReader相关知识点⭐⭐⭐⭐⭐
    C# Distanct List集合
    RePlace函数
    DataTable和DataRow和DataColumn ⭐⭐⭐⭐⭐
    scrapy 基础组件专题(八):scrapy-redis 框架分析
    scrapy 基础组件专题(九):scrapy-redis 源码分析
    scrapy 基础组件专题(七):scrapy 调度器、调度器中间件、自定义调度器
    scrapy 基础组件专题(六):自定义命令
    scrapy 基础组件专题(五):自定义扩展
    scrapy 基础组件专题(四):信号运用
  • 原文地址:https://www.cnblogs.com/zh718594493/p/14473942.html
Copyright © 2011-2022 走看看