一起攻克面试难关:Go面试每日一题


一起攻克面试难关:Go 面试每天一篇

Day 1

https://mp.weixin.qq.com/s/8xcx0L89njD24iXo-FTSVw

Question 1

  • Question
    下面这段代码输出的内容:
func main() {
    defer_call()
}

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

    panic("触发异常")
}
  • Answer
    输出:
打印后
打印中
打印前
panic: 触发异常

参考解析:defer 的执行顺序是后进先出。当出现 panic 语句的时候,会先按照 defer 的后进先出的顺序执行,最后才会执行panic。

Day 2

https://mp.weixin.qq.com/s/Vwb-48SF19OaJr0zHOdfzg

Question 1

  • Question
    下面这段代码输出什么,说明原因。
func main() {

    slice := []int{0, 1, 2, 3}
    m := make(map[int]*int)

    for key, val := range slice {
        m[key] = &val
    }

    for k, v := range m {
        fmt.Println(k, "->", *v)
    }
}
  • Answer
    输出:
0 -> 3
1 -> 3
2 -> 3
3 -> 3

参考解析:这是新手常会犯的错误写法,for range 循环的时候会创建每个元素的副本,而不是元素的引用,所以 m[key] = &val 取的都是变量 val 的地址,所以最后 map 中的所有元素的值都是变量 val 的地址,因为最后 val 被赋值为3,所有输出都是3.

正确的写法:

func main() {

    slice := []int{0, 1, 2, 3}
    m := make(map[int]*int)

    for key, val := range slice {
        value := val
        m[key] = &value
    }

    for k, v := range m {
        fmt.Println(k, "===>", *v)
    }
}

Day 3

https://mp.weixin.qq.com/s/V00k8vfNanjYRAra5cviog

Question 1

  • Question
    下面两段代码输出什么。
//    1...
func main() {
    s := make([]int, 5)
    s = append(s, 1, 2, 3)
    fmt.Println(s)
}
//    2...
func main() {
    s := make([]int, 0)
    s = append(s, 1, 2, 3, 4)
    fmt.Println(s)
}
  • Answer
    两段代码分别输出:
[0 0 0 0 0 1 2 3]
[1 2 3 4]

参考解析:这道题考的是使用 append 向 slice 添加元素,第一段代码常见的错误是 [1 2 3],需要注意。

Question 2

  • Question
    下面这段代码有什么缺陷
func funcMui(x, y int) (sum int, error) {
    return x + y, nil
}
  • Answer
    参考答案:第二个返回值没有命名。
    在函数有多个返回值时,只要有一个返回值有命名,其他的也必须命名。如果有多个返回值必须加上括号();如果只有一个返回值且命名也必须加上括号()。这里的第一个返回值有命名 sum,第二个没有命名,所以错误。

Question 3

  • Question
    new() 与 make() 的区别

  • Answer
    new(T) 和 make(T,args) 是 Go 语言内建函数,用来分配内存,但适用的类型不同。

new(T) 会为 T 类型的新值分配已置零的内存空间,并返回地址(指针),即类型为 *T 的值。换句话说就是,返回一个指针,该指针指向新分配的、类型为 T 的零值。适用于值类型,如数组、结构体等。

make(T,args) 返回初始化之后的 T 类型的值,这个值并不是 T 类型的零值,也不是指针 *T,是经过初始化之后的 T 的引用。make() 只适用于 slice、map 和 channel.

Day 4

https://mp.weixin.qq.com/s/BiIMh9QFNYuMa3ngTk-z-g

Question 1

  • Question
    下面这段代码能否通过编译,不能的话原因是什么;如果能,输出什么。
func main() {
    list := new([]int)
    list = append(list, 1)
    fmt.Println(list)
}
  • Answer
    不能通过编译,new([]int) 之后的 list 是一个 *[]int 类型的指针,不能对指针执行 append 操作。可以使用 make() 初始化之后再用。同样的,map 和 channel 建议使用 make() 或字面量的方式初始化,不要用 new() 。

Question 2

  • Question
    下面这段代码能否通过编译,如果可以,输出什么?
func main() {
    s1 := []int{1, 2, 3}
    s2 := []int{4, 5}
    s1 = append(s1, s2)
    fmt.Println(s1)
}
  • Answer
    不能通过编译。append() 的第二个参数不能直接使用 slice,需使用 … 操作符,将一个切片追加到另一个切片上:append(s1,s2…)。或者直接跟上元素,形如:append(s1,1,2,3)。

Question 3

  • Question
    下面这段代码能否通过编译,如果可以,输出什么?
var (
    size     := 1024
    max_size = size * 2
)

func main() {
    fmt.Println(size, max_size)
}
  • Answer
    参考答案及解析:不能通过编译。这道题的主要知识点是变量声明的简短模式,形如:x := 100。但这种声明方式有限制:
    1. 必须使用显示初始化;
    2. 不能提供数据类型,编译器会自动推导;
    3. 只能在函数内部使用简短模式;

Day 5

https://mp.weixin.qq.com/s/BiIMh9QFNYuMa3ngTk-z-g

Question 1

  • Question
    下面这段代码能否通过编译?不能的话,原因是什么?如果通过,输出什么?

    func main() {
      sn1 := struct {
          age  int
          name string
      }{age: 11, name: "qq"}
      sn2 := struct {
          age  int
          name string
      }{age: 11, name: "qq"}
    
      if sn1 == sn2 {
          fmt.Println("sn1 == sn2")
      }
    
      sm1 := struct {
          age int
          m   map[string]string
      }{age: 11, m: map[string]string{"a": "1"}}
      sm2 := struct {
          age int
          m   map[string]string
      }{age: 11, m: map[string]string{"a": "1"}}
    
      if sm1 == sm2 {
          fmt.Println("sm1 == sm2")
      }
    }
    
  • Answer

编译不通过 invalid operation: sm1 == sm2
这道题目考的是结构体的比较,有几个需要注意的地方:
1.结构体只能比较是否相等,但是不能比较大小。
2.相同类型的结构体才能够进行比较,结构体是否相同不但与属性类型有关,还与属性顺序相关,sn3 与 sn1 就是不同的结构体;

sn3 := struct {
    name string
    age  int
}{age: 11, name: "qq"}

3.如果 struct 的所有成员都可以比较,则该 struct 就可以通过 == 或 != 进行比较是否相等,比较时逐个项进行比较,如果每一项都相等,则两个结构体才相等,否则不相等;

那什么是可比较的呢,常见的有 bool、数值型、字符、指针、数组等,像切片、map、函数等是不能比较的。 具体可以参考 Go 说明文档。http://docs.studygolang.com/ref/spec#Comparison_operators

Day 6

https://mp.weixin.qq.com/s/6UQ1JyYn_C0zL8U3aUMFLg

Question 1

  • Question
    通过指针变量 p 访问其成员变量 name,有哪几种方式?
A.p.name
B.(&p).name
C.(*p).name
D.p->name
  • Answer
    参考答案及解析:AC。& 取址运算符,* 指针解引用。

Question 2

  • Question
    下面这段代码能否通过编译?如果通过,输出什么?
type MyInt1 int
type MyInt2 = int

func main() {
    var i int = 0
    var i1 MyInt1 = i
    var i2 MyInt2 = i
    fmt.Println(i1, i2)
}
  • Answer
    参考答案及解析:编译不通过,cannot use i (type int) as type MyInt1 in assignment。
    这道题考的是类型别名与类型定义的区别。

第 5 行代码是基于类型 int 创建了新类型 MyInt1,第 6 行代码是创建了 int 的类型别名 MyInt2,注意类型别名的定义时 = 。所以,第 10 行代码相当于是将 int 类型的变量赋值给 MyInt1 类型的变量,Go 是强类型语言,编译当然不通过;而 MyInt2 只是 int 的别名,本质上还是 int,可以赋值。
第 10 行代码的赋值可以使用强制类型转化 var i1 MyInt1 = MyInt1(i).

Question 3

  • Question
    以下代码输出什么?
func main() {
    a := []int{7, 8, 9}
    fmt.Printf("%+v\n", a)
    ap(a)
    fmt.Printf("%+v\n", a)
    app(a)
    fmt.Printf("%+v\n", a)

}

func ap(a []int) {
    a = append(a, 10)
}

func app(a []int) {
    a[0] = 1
}
  • Answer
[7,8,9]
[7,8,9]
[1,8,9]

Day 7

https://mp.weixin.qq.com/s/OfauBwvdcjsxlJ6YvcV1rg
关于字符串连接,下面语法正确的是?

A. str := 'abc' + '123'
B. str := "abc" + "123"
C. str := '123' + "abc"
D. fmt.Sprintf("abc%d", 123)
  • Answer
    参考答案及解析:BD。知识点:字符串连接。除了以上两种连接方式,还有 strings.Join()、buffer.WriteString()等。

Question 2

  • Question
    下面这段代码能否编译通过?如果可以,输出什么?
const (
    x = iota
    _
    y
    z = "zz"
    k
    p = iota
)

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

Question 3

  • Question
    下面赋值正确的是
A. var x = nil
B. var x interface{} = nil
C. var x string = nil
D. var x error = nil
  • Answer
    参考答案及解析:BD。知识点:nil 值。nil 只能赋值给指针、chan、func、interface、map 或 slice 类型的变量。强调下 D 选项的 error 类型,它是一种内置接口类型,看下方贴出的源码就知道,所以 D 是对的。

Day 8

https://mp.weixin.qq.com/s/-uioKjs5J8n1zispE_2Hgw

Question 1

  • Question
    关于init函数,下面说法正确的是
A. 一个包中,可以包含多个 init 函数;
B. 程序编译时,先执行依赖包的 init 函数,再执行 main 包内的 init 函数;
C. main 包中,不能有 init 函数;
D. init 函数可以被其他函数调用;
  • Answer

参考答案及解析:AB。关于 init() 函数有几个需要注意的地方:

  1. init() 函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等;
  2. 一个包可以出线多个 init() 函数,一个源文件也可以包含多个 init() 函数;
  3. 同一个包中多个 init() 函数的执行顺序没有明确定义,但是不同包的init函数是根据包导入的依赖关系决定的(看下图);
  4. init() 函数在代码中不能被显示调用、不能被引用(赋值给函数变量),否则出现编译错误;
  5. 一个包被引用多次,如 A import B,C import B,A import C,B 被引用多次,但 B 包只会初始化一次;
  6. 引入包,不可出现死循坏。即 A import B,B import A,这种情况编译失败;

Golang init

Question 2

  • Question
    下面这段代码输出什么以及原因
func hello() []string {
    return nil
}

func main() {
    h := hello
    if h == nil {
        fmt.Println("nil")
    } else {
        fmt.Println("not nil")
    }
}
A. nil
B. not nil
C. compilation error  
  • Answer
    答案及解析:B。这道题目里面,是将 hello() 赋值给变量 h,而不是函数的返回值,所以输出 not nil。

Question 3

  • Question
    下面这段代码能否编译通过?如果可以,输出什么
func GetValue() int {
    return 1
}

func main() {
    i := GetValue()
    switch i.(type) {
    case int:
        println("int")
    case string:
        println("string")
    case interface{}:
        println("interface")
    default:
        println("unknown")
    }
}
  • Answer
    参考答案及解析:编译失败。考点:类型选择,类型选择的语法形如:i.(type),其中 i 是接口,type 是固定关键字,需要注意的是,只有接口类型才可以使用类型选择。看下关于接口的文章。

Day 9

https://mp.weixin.qq.com/s/ycVU9Jywq3Z9HhKHXP3GPQ

Question 1

  • Question
    关于channel,下面语法正确的是()
A. var ch chan int
B. ch := make(chan int)
C. <- ch
D. ch <-
  • Answer
    参考答案及解析:ABC。A、B都是声明 channel;C 读取 channel;写 channel 是必须带上值,所以 D 错误。

Question 2

  • Question
    关于channel,下面语法正确的是()
type person struct {
    name string
}

func main() {
    var m map[person]int
    p := person{"mike"}
    fmt.Println(m[p])
}
A.0
B.1
C.Compilation error 
  • Answer
    参考答案及解析:A。打印一个 map 中不存在的值时,返回元素类型的零值。这个例子中,m 的类型是 map[person]int,因为 m 中不存在 p,所以打印 int 类型的零值,即 0。

Question 3

  • Question
    关于channel,下面语法正确的是()
func hello(num ...int) {
    num[0] = 18
}

func main() {
    i := []int{5, 6, 7}
    hello(i...)
    fmt.Println(i[0])
}
A.18
B.5
C.Compilation error  
  • Answer

Author: Kyle Liu
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Kyle Liu !