golang面试(每日一篇汇总)

204 阅读22分钟

本文转自公众号<Golang来啦> 仅作为一篇汇总

Day1

下面代码输出内容

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: 触发异常

Day2

下面这段代码输出什么,说明原因。

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

输出

0 -> 3
1 -> 3
2 -> 3
3 -> 3

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

Day3

1.下面两段代码输出什么。

 // 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)
}

2.下面这段代码有什么缺陷

func funcMui(x,y int)(sum int,error){
    return x+y,nil
}

3.new() 与 make() 的区别


1.两段代码分别输出:

[0 0 0 0 0 1 2 3]
[1 2 3 4]

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

参考答案:第二个返回值没有命名。 参考解析: 在函数有多个返回值时,只要有一个返回值有命名,其他的也必须命名。如果有多个返回值必须加上括号();如果只有一个返回值且命名也必须加上括号()。这里的第一个返回值有命名 sum,第二个没有命名,所以错误。

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

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

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

Day4

1.下面这段代码能否通过编译,不能的话原因是什么;如果能,输出什么。

func main() {
    list := new([]int)
    list = append(list, 1)
    fmt.Println(list)
}

2.下面这段代码能否通过编译,如果可以,输出什么?

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

3.下面这段代码能否通过编译,如果可以,输出什么?

var(
    size := 1024
    max_size = size*2
)

func main() {
    fmt.Println(size,max_size)
}

参考解析

不能通过编译,new([]int) 之后的 list 是一个 *[]int 类型的指针,不能对指针执行 append 操作。可以使用 make() 初始化之后再用。同样的,map 和 channel 建议使用 make() 或字面量的方式初始化,不要用 new() 。

不能通过编译。append() 的第二个参数不能直接使用 slice,需使用 … 操作符,将一个切片追加到另一个切片上:append(s1,s2…)。或者直接跟上元素,形如:append(s1,1,2,3)。

参考答案及解析:不能通过编译。这道题的主要知识点是变量声明的简短模式,形如:x := 100。但这种声明方式有限制:

    1.必须使用显示初始化;
    2.不能提供数据类型,编译器会自动推导;
    3.只能在函数内部使用简短模式;

Day5

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

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")
    }
}

参考解析

编译不通过 invalid operation: sm1 == sm2

这道题目考的是结构体的比较,有几个需要注意的地方:

  1. 结构体只能比较是否相等,但是不能比较大小。
  2. 相同类型的结构体才能够进行比较,结构体是否相同不但与属性类型有关,还与属性顺序相关,sn3 与 sn1 就是不同的结构体;
    sn3:= struct {
    name string
    age  int
    }{age:11,name:"qq"}
    
  3. 如果 struct 的所有成员都可以比较,则该 struct 就可以通过 == 或 != 进行比较是否相等,比较时逐个项进行比较,如果每一项都相等,则两个结构体才相等,否则不相等;

注:bool、数值型、字符、指针、数组等可以比较,切片、map、函数等是不能比较的。

Day6

  1. 通过指针变量 p 访问其成员变量 name,有哪几种方式?

     A.p.name
     B.(&p).name
     C.(*p).name
     D.p->name
    
  2. 下面这段代码能否通过编译?如果通过,输出什么?

 package main
 
 import "fmt"
 
 type MyInt1 int
 type MyInt2 = int
 
 func main() {
     var i int =0
    var i1 MyInt1 = i 
    var i2 MyInt2 = i
    fmt.Println(i1,i2)
}

参考解析

  1. AC。& 取址运算符,* 指针解引用。

  2. 编译不通过,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).

Day7

  1. 关于字符串连接,下面语法正确的是?

     A. str := 'abc' + '123'
     B. str := "abc" + "123"
     C. str := '123' + "abc"
     D. fmt.Sprintf("abc%d", 123)
    
  2. 下面这段代码能否编译通过?如果可以,输出什么?

 const (
     x = iota
     _
     y
     z = "zz"
     k 
     p = iota
 )
 
func main()  {
    fmt.Println(x,y,z,k,p)
}
  1. 下面赋值正确的是()

     A. var x = nil
     B. var x interface{} = nil
     C. var x string = nil
     D. var x error = nil
    

参考解析

  1. 知识点:字符串连接。除了以上两种连接方式,还有 strings.Join()、buffer.WriteString()等。
  2. 编译通过,输出:0 2 zz zz 5。知识点:iota 的使用。给大家贴篇文章,讲的很详细 www.cnblogs.com/zsy/p/53700…
  3. BD。知识点:nil 值。nil 只能赋值给指针、chan、func、interface、map 或 slice 类型的变量。强调下 D 选项的 error 类型,它是一种内置接口类型,看下方贴出的源码就知道,所以 D 是对的。
    type error interface {
        Error() string
    }
    

Day8

  1. 关于init函数,下面说法正确的是()

     A. 一个包中,可以包含多个 init 函数;
     B. 程序编译时,先执行依赖包的 init 函数,再执行 main 包内的 init 函数;
     C. main 包中,不能有 init 函数;
     D. init 函数可以被其他函数调用;
    
  2. 下面这段代码输出什么以及原因?

 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  
  1. 下面这段代码能否编译通过?如果可以,输出什么?
 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")
    }
}

参考解析

  1. 参考答案及解析:AB。关于 init() 函数有几个需要注意的地方:
    • init() 函数是用于程序执行前做包的初始化的函数,比如初始化包里的变量等;
    • 一个包可以出线多个 init() 函数,一个源文件也可以包含多个 init() 函数;
    • 同一个包中多个 init()函数的执行顺序没有明确定义,但是不同包的init函数是根据包导入的依赖关系决定的(看下图);
    • init() 函数在代码中不能被显示调用、不能被引用(赋值给函数变量),否则出现编译错误;
    • 一个包被引用多次,如 A import B,C import B,A import C,B 被引用多次,但 B 包只会初始化一次;
    • 引入包,不可出现死循坏。即 A import B,B import A,这种情况编译失败;
  2. B。这道题目里面,是将 hello() 赋值给变量 h,而不是函数的返回值,所以输出 not nil。
  3. 编译失败。考点:类型选择,类型选择的语法形如:i.(type),其中 i 是接口,type 是固定关键字,需要注意的是,只有接口类型才可以使用类型选择。

Day9

  1. 关于channel,下面语法正确的是()

     A. var ch chan int
     B. ch := make(chan int)
     C. <- ch
     D. ch <-
    
  2. 下面这段代码输出什么?

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      
  1. 下面这段代码输出什么?
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  

参考解析

  1. ABC。A、B都是声明 channel;C 读取 channel;写 channel 是必须带上值,所以 D 错误。

  2. A。打印一个 map 中不存在的值时,返回元素类型的零值。这个例子中,m 的类型是 map[person]int,因为 m 中不存在 p,所以打印 int 类型的零值,即 0。

  3. 18。知识点:可变函数

Day10

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

    func main() {  
        a := 5
        b := 8.1
        fmt.Println(a + b)
    }
    
     A.13.1  
     B.13
     C.compilation error  
    
  2. 下面这段代码输出什么?

    package main
    
    import (  
        "fmt"
    )
    
    func main() {  
        a := [5]int{1, 2, 3, 4, 5}
        t := a[3:4:4]
        fmt.Println(t[0])
    }
    
     A.3
     B.4
     C.compilation error  
    
  3. 下面这段代码输出什么?

    func main() {
        a := [2]int{5, 6}
        b := [3]int{5, 6}
        if a == b {
            fmt.Println("equal")
        } else {
            fmt.Println("not equal")
        }
    }
    
     A. compilation error  
     B. equal  
     C. not equal  
    

参考解析

  1. C。a 的类型是 int,b 的类型是 float,两个不同类型的数值不能相加,编译报错。

  2. B。知识点:操作符 [i,j]。基于数组(切片)可以使用操作符 [i,j] 创建新的切片,从索引 i,到索引 j 结束,截取已有数组(切片)的任意部分,返回新的切片,新切片的值包含原数组(切片)的 i 索引的值,但是不包含 j 索引的值。i、j 都是可选的,i 如果省略,默认是 0,j 如果省略,默认是原数组(切片)的长度。i、j 都不能超过这个长度值。 假如底层数组的大小为 k,截取之后获得的切片的长度和容量的计算方法:长度:j-i,容量:k-i。

    截取操作符还可以有第三个参数,形如 [i,j,k],第三个参数 k 用来限制新切片的容量,但不能超过原数组(切片)的底层数组大小。截取获得的切片的长度和容量分别是:j-i、k-i。

    所以例子中,切片 t 为 [4],长度和容量都是 1。 详情参考 mp.weixin.qq.com/s/rLpJ3iOho…

  3. A。Go 中的数组是值类型,可比较,另外一方面,数组的长度也是数组类型的组成部分,所以 a 和 b 是不同的类型,是不能比较的,所以编译错误。

Day11

  1. 关于 cap() 函数的适用类型,下面说法正确的是()

     A. array
     B. slice
     C. map
     D. channel
    
  2. 下面这段代码输出什么?

    func main() {  
        var i interface{}
        if i == nil {
            fmt.Println("nil")
            return
        }
        fmt.Println("not nil")
    }
    
     A. nil
     B. not nil
     C. compilation error  
    
  3. 下面这段代码输出什么?

    func main() {  
        s := make(map[string]int)
        delete(s, "h")
        fmt.Println(s["h"])
    }
    
     A. runtime panic
     B. 0
     C. compilation error  
    

参考解析

  1. ABD。知识点:cap(),cap() 函数不适用 map。
  2. A。当且仅当接口的动态值和动态类型都为 nil 时,接口类型值才为 nil。参考link
  3. B。删除 map 不存在的键值对时,不会报错,相当于没有任何作用;获取不存在的减值对时,返回值类型对应的零值,所以返回 0。

Day12

  1. 下面属于关键字的是()

     A.func
     B.struct
     C.class
     D.defer
    
  2. 下面这段代码输出什么?

    func main() {  
        i := -5
        j := +5
        fmt.Printf("%+d %+d", i, j)
    }
    
     A. -5 +5
     B. +5 +5
     C. 0  0
    
  3. 下面这段代码输出什么?

    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.ShowB()
    }
    

参考解析

  1. ABD。知识点:Go 语言的关键字。Go 语言有 25 个关键字,看下图:

  2. A。%d表示输出十进制数字,+表示输出数值的符号。这里不表示取反。

  3. teacher showB。知识点:结构体嵌套。在嵌套结构体中,People 称为内部类型,Teacher 称为外部类型;通过嵌套,内部类型的属性、方法,可以为外部类型所有,就好像是外部类型自己的一样。此外,外部类型还可以定义自己的属性和方法,甚至可以定义与内部相同的方法,这样内部类型的方法就会被“屏蔽”。这个例子中的 ShowB() 就是同名方法。

    关于结构体嵌套可以看看这篇文章

Day13

  1. 定义一个包内全局字符串变量,下面语法正确的是()

     A. var str string
     B. str := ""
     C. str = ""
     D. var str = ""
    
  2. 下面这段代码输出什么?

    func hello(i int) {  
        fmt.Println(i)
    }
    func main() {  
        i := 5
        defer hello(i)
        i = i + 10
    }
    
  3. 下面这段代码输出什么?

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

参考解析

  1. AD。B 只支持局部变量声明;C 是赋值,str 必须在这之前已经声明;
  2. 5。这个例子中,hello() 函数的参数在执行 defer 语句的时候会保存一份副本,在实际调用 hello() 函数时用,所以是 5.
  3.  showA showB
    
    结构体嵌套。这道题可以结合第 12 天的第三题一起看,Teacher 没有自己 ShowA(),所以调用内部类型 People 的同名方法,需要注意的是第 5 行代码调用的是 People 自己的 ShowB 方法。

Day14

  1. 下面代码输出什么?
    func main() {
        str := "hello"
        str[0] = 'x'
        fmt.Println(str)
    }
    

A. hello B. xello C. compilation error

  1. 下面代码输出什么?
    func incr(p *int) int {
        *p++
        return *p
    }
    
    func main() {
        p :=1
        incr(&p)
        fmt.Println(p)
    }
    

A. 1 B. 2 C. 3

  1. 对 add() 函数调用正确的是()
    func add(args ...int) int {
    
        sum := 0
        for _, arg := range args {
            sum += arg
        }
        return sum
    }
    

A. add(1, 2) B. add(1, 3, 7) C. add([]int{1, 2}) D. add([]int{1, 3, 7}…)


参考解析

  1. C。知识点:常量,Go 语言中的字符串是只读的。
  2. B。知识点:指针,incr() 函数里的 p 是 *int 类型的指针,指向的是 main() 函数的变量 p 的地址。第 2 行代码是将该地址的值执行一个自增操作,incr() 返回自增后的结果。
  3. ABD。知识点:可变函数

Day15

  1. 下面代码下划线处可以填入哪个选项?

    func main() {
        var s1 []int
        var s2 = []int{}
        if __ == nil {
            fmt.Println("yes nil")
        }else{
            fmt.Println("no nil")
        }
    }
    

    A. s1 B. s2 C. s1、s2 都可以

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

    func main() {  
        i := 65
        fmt.Println(string(i))
    }
    

    A. A B. 65 C. compilation error

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

    type A interface {
        ShowA() int
    }
    
    type B interface {
        ShowB() int
    }
    
    type Work struct {
        i int
    }
    
    func (w Work) ShowA() int {
        return w.i + 10
    }
    
    func (w Work) ShowB() int {
        return w.i + 20
    }
    
    func main() {
        c := Work{3}
        var a A = c
        var b B = c
        fmt.Println(a.ShowA())
        fmt.Println(b.ShowB())
    }
    

参考解析

  1. A。知识点:nil 切片和空切片。nil 切片和 nil 相等,一般用来表示一个不存在的切片;空切片和 nil 不相等,表示一个空的集合。

  2. A。UTF-8 编码中,十进制数字 65 对应的符号是 A。

  3. 13 23。知识点:接口。一种类型实现多个接口,结构体 Work 分别实现了接口 A、B,所以接口变量 a、b 调用各自的方法 ShowA() 和 ShowB(),输出 13、23。

Day16

  1. 切片 a、b、c 的长度和容量分别是多少?

    func main() {
    
        s := [3]int{1, 2, 3}
        a := s[:0]
        b := s[:2]
        c := s[1:2:cap(s)]
    }
    
  2. 下面代码中 A B 两处应该怎么修改才能顺利编译?

    func main() {
        var m map[string]int        //A
        m["a"] = 1
        if v := m["b"]; v != nil {  //B
            fmt.Println(v)
        }
    }
    
  3. 下面代码输出什么?

    type A interface {
        ShowA() int
    }
    
    type B interface {
        ShowB() int
    }
    
    type Work struct {
        i int
    }
    
    func (w Work) ShowA() int {
        return w.i + 10
    }
    
    func (w Work) ShowB() int {
        return w.i + 20
    }
    
    func main() {
        c := Work{3}
        var a A = c
        var b B = c
        fmt.Println(a.ShowB())
        fmt.Println(b.ShowA())
    }
    

    A. 23 13 B. compilation error


参考解析

  1. a、b、c 的长度和容量分别是 0 3、2 3、1 2。知识点:数组或切片的截取操作。截取操作有带 2 个或者 3 个参数,形如:[i:j] 和 [i:j:k],假设截取对象的底层数组长度为 l。在操作符 [i:j] 中,如果 i 省略,默认 0,如果 j 省略,默认底层数组的长度,截取得到的切片长度和容量计算方法是 j-i、l-i。操作符 [i:j:k],k 主要是用来限制切片的容量,但是不能大于数组的长度 l,截取得到的切片长度和容量计算方法是 j-i、k-i。

  2. func main() {
        m := make(map[string]int)
        m["a"] = 1
        if v,ok := m["b"]; ok {
            fmt.Println(v)
        }
    }
    

    在 A 处只声明了map m ,并没有分配内存空间,不能直接赋值,需要使用 make(),都提倡使用 make() 或者字面量的方式直接初始化 map。

    B 处,v,k := m["b"] 当 key 为 b 的元素不存在的时候,v 会返回值类型对应的零值,k 返回 false。

  3. B。知识点:接口的静态类型。a、b 具有相同的动态类型和动态值,分别是结构体 work 和 {3};a 的静态类型是 A,b 的静态类型是 B,接口 A 不包括方法 ShowB(),接口 B 也不包括方法 ShowA(),编译报错。看下编译错误:

     a.ShowB undefined (type A has no field or method ShowB)
     b.ShowA undefined (type B has no field or method ShowA)
    

Day17

  1. 下面代码中,x 已声明,y 没有声明,判断每条语句的对错。

     1.x, _ := f()
     2.x, _ = f()
     3.x, y := f()
     4.x, y = f()
    
  2. 下面代码输出什么?

    func increaseA() int {
        var i int
        defer func() {
            i++
        }()
        return i
    }
    
    func increaseB() (r int) {
        defer func() {
            r++
        }()
        return r
    }
    
    func main() {
        fmt.Println(increaseA())
        fmt.Println(increaseB())
    }
    

    A. 1 1 B. 0 1 C. 1 0 D. 0 0

  3. 下面代码输出什么?

    type A interface {
        ShowA() int
    }
    
    type B interface {
        ShowB() int
    }
    
    type Work struct {
        i int
    }
    
    func (w Work) ShowA() int {
        return w.i + 10
    }
    
    func (w Work) ShowB() int {
        return w.i + 20
    }
    
    func main() {
        var a A = Work{3}
        s := a.(Work)
        fmt.Println(s.ShowA())
        fmt.Println(s.ShowB())
    }
    

    A. 13 23 B. compilation error


参考解析

  1. 错、对、对、错。变量的声明。1.错,x 已经声明,不能使用:=;2.对;3.对,当多值赋值时,:= 左边的变量无论声明与否都可以;4.错,y 没有声明。

  2. B。defer、返回值。注意一下,increaseA()的返回参数是匿名,increaseB()是具名。

  3. A。类型断言。这道题可以和第 15 天的第三题 和第 16 天的第三题结合起来看,具体的解析看这里

Day18

  1. f1()、f2()、f3() 函数分别返回什么?

    func f1() (r int) {
        defer func() {
            r++
        }()
        return 0
    }
    
    func f2() (r int) {
        t := 5
        defer func() {
            t = t + 5
        }()
        return t
    }
    
    func f3() (r int) {
        defer func(r int) {
            r = r + 5
        }(r)
        return 1
    }
    

参考解析

这里参考文章

Day19

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

    type Person struct {
        age int
    }
    
    func main() {
        person := &Person{28}
    
        // 1. 
        defer fmt.Println(person.age)
    
        // 2.
        defer func(p *Person) {
            fmt.Println(p.age)
        }(person)  
    
        // 3.
        defer func() {
            fmt.Println(person.age)
        }()
    
        person.age = 29
    }
    

参考解析

29 29 28。变量 person 是一个指针变量 。

1.person.age 此时是将 28 当做 defer 函数的参数,会把 28 缓存在栈中,等到最后执行该 defer 语句的时候取出,即输出 28;

2.defer 缓存的是结构体 Person{28} 的地址,最终 Person{28} 的 age 被重新赋值为 29,所以 defer 语句最后执行的时候,依靠缓存的地址取出的 age 便是 29,即输出 29;

3.闭包引用,输出 29;

又由于 defer 的执行顺序为先进后出,即 3 2 1,所以输出 29 29 28。

Day20

  1. 下面这段代码正确的输出是什么?

    func f() {
        defer fmt.Println("D")
        fmt.Println("F")
    }
    
    func main() {
        f()
        fmt.Println("M")
    }
    

    A. F M D B. D F M C. F D M

  2. 下面代码输出什么?

    type Person struct {
        age int
    }
    
    func main() {
        person := &Person{28}
    
        // 1.
        defer fmt.Println(person.age)
    
        // 2.
        defer func(p *Person) {
            fmt.Println(p.age)
        }(person)
    
        // 3.
        defer func() {
            fmt.Println(person.age)
        }()
    
        person = &Person{29}
    }
    

参考解析

  1. C。被调用函数里的 defer 语句在返回之前就会被执行,所以输出顺序是 F D M。

  2. 29 28 28。这道题在第 19 天题目的基础上做了一点点小改动,前一题最后一行代码 person.age = 29 是修改引用对象的成员 age,这题最后一行代码 person = &Person{29} 是修改引用对象本身,来看看有什么区别。

    1处.person.age 这一行代码跟之前含义是一样的,此时是将 28 当做 defer 函数的参数,会把 28 缓存在栈中,等到最后执行该 defer 语句的时候取出,即输出 28;

    2处.defer 缓存的是结构体 Person{28} 的地址,这个地址指向的结构体没有被改变,最后 defer 语句后面的函数执行的时候取出仍是 28;

    3处.闭包引用,person 的值已经被改变,指向结构体 Person{29},所以输出 29.

    由于 defer 的执行顺序为先进后出,即 3 2 1,所以输出 29 28 28。

Day21

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

     A. var a []int
     B. a := []int{}
    
  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
    }
    
  3. 下面 A、B 两处应该填入什么代码,才能确保顺利打印出结果?

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

参考解析

  1. A 声明的是 nil 切片;B 声明的是长度和容量都为 0 的空切片。第一种切片声明不会分配内存,优先选择。

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

     永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。
    
  3.  A. &S{"foo"} 
     B. *f() 或者 f()
    

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

Day22

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

    package main
    import (
        "fmt"
    )
    func main() {
        var x string = nil
        if x == nil {
            x = "default"
        }
        fmt.Println(x)
    }
    
  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")
        }()
    }
    

参考解析

  1. go中只有引用类型才能和nil做比较或赋值为nil,go内置的引用类型只有三种:slice,map,channel,对于string的空值为""。
  2. 2 1。defer 关键字后面的函数或者方法想要执行必须先注册,return 之后的 defer 是不能注册的, 也就不能执行后面的函数或方法。

Day23

  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)
    }
    
  2. 下面选项正确的是?

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

    A. 1 2 B. compilation error


参考解析

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

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

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

2. A。知识点:代码块和变量作用域。 参考文章

Day24

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

    func main() {
        m := map[int]string{0:"zero",1:"one"}
        for k,v := range m {
            fmt.Println(k,v)
        }
    }
    
  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
    }
    

参考解析

  1. map 的输出是无序的。

  2.  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。

Day25

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

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

    A. 1 B. compilation error

  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


参考解析

  1. B。基于类型创建的方法必须定义在同一个包内,上面的代码基于 int 类型创建了 PrintInt() 方法,由于 int 类型和方法 PrintInt() 定义在不同的包内,所以编译出错
  2. B。编译错误 Student does not implement People (Speak method has pointer receiver),值类型 Student 没有实现接口的 Speak() 方法,而是指针类型 *Student 实现该方法。

Day26



参考解析