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

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 计数一次。

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

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 nilp is not nil

解析:

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

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

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() 方法。

64. 下面代码输出什么?

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 mapX = YXYX

有两个解决办法:

  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 格式化输出详细信息 }

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

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

答:有两处错误

解析:

[…]int{1}[2]int{1}

66. 下面这段代码输出什么?如果编译错误的话,为什么?

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

解析:

知识点:变量作用域。

:=:=bar()

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

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

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

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

答:不会出现死循环,能正常结束

解析:

循环次数在循环开始前就已经确定,循环内改变切片的长度,不影响循环次数。

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

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值。可以理解为闭包引用,使用的是上下文环境的值。

两种可行的 fix 方法:

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

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

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

解析:

r = n +1

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

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 修改后的值。

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

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

答:

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

解析:

知识点:可变函数、append()操作。

...[i,j]



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

func main() {
    var a = []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]

解析:

切片在 go 的内部结构有一个指向底层数组的指针,当 range 表达式发生复制时,副本的指针依旧指向原底层数组,所以对切片的修改都会反应到底层数组上,所以通过 v 可以获得修改后的数组元素。

73. 下面这段代码输出结果正确正确吗?

type Foo struct {
    bar string
}
func main() {
    s1 := []Foo{
        {"A"},
        {"B"},
        {"C"},
    }
    s2 := make([]*Foo, len(s1))
    for i, value := range s1 {
        s2[i] = &value
    }
    fmt.Println(s1[0], s1[1], s1[2])
    fmt.Println(s2[0], s2[1], s2[2])
}
输出:
{A} {B} {C}
&{A} &{B} &{C}

答:s2 的输出结果错误

解析:

&{C} &{C} &{C}

可行的解决办法如下:

for i := range s1 {
    s2[i] = &s1[i]
}

74. 下面代码里的 counter 的输出值?

func main() {
    var m = map[string]int{
        "A": 21,
        "B": 22,
        "C": 23,
    }
    counter := 0
    for k, v := range m {
        if counter == 0 {
            delete(m, "A")
        }
        counter++
        fmt.Println(k, v)
    }
    fmt.Println("counter is ", counter)
}
  • A. 2
  • B. 3
  • C. 2 或 3

答:C

解析:

for range map 是无序的,如果第一次循环到 A,则输出 3;否则输出 2。

75. 关于协程,下面说法正确是()

  • A. 协程和线程都可以实现程序的并发执行;
  • B. 线程比协程更轻量级;
  • C. 协程不存在死锁问题;
  • D. 通过 channel 来进行协程间的通信;

答:A D

76. 关于循环语句,下面说法正确的有()

  • A. 循环语句既支持 for 关键字,也支持 while 和 do-while;
  • B. 关键字 for 的基本使用方法与 C/C++ 中没有任何差异;
  • C. for 循环支持 continue 和 break 来控制循环,但是它提供了一个更高级的 break,可以选择中断哪一个循环;
  • D. for 循环不支持以逗号为间隔的多个赋值语句,必须使用平行赋值的方式来初始化多个变量;

答:C D

77. 下面代码输出正确的是?

func main() {
    i := 1
    s := []string{"A", "B", "C"}
    i, s[i-1] = 2, "Z"
    fmt.Printf("s: %v \n", s)
}
  • A. s: [Z,B,C]
  • B. s: [A,Z,C]

答:A

解析:

知识点:多重赋值。

多重赋值分为两个步骤,有先后顺序:

  • 计算等号左边的索引表达式和取址表达式,接着计算等号右边的表达式;
  • 赋值;
i, s[0] = 2, "Z"

78. 关于类型转化,下面选项正确的是?

A.
type MyInt int
var i int = 1
var j MyInt = i
B.
type MyInt int
var i int = 1
var j MyInt = (MyInt)i
C.
type MyInt int
var i int = 1
var j MyInt = MyInt(i)
D.
type MyInt int
var i int = 1
var j MyInt = i.(MyInt)

答:C

解析:

知识点:强制类型转化

79. 关于switch语句,下面说法正确的有?

  • A. 条件表达式必须为常量或者整数;
  • B. 单个case中,可以出现多个结果选项;
  • C. 需要用break来明确退出一个case;
  • D. 只有在case中明确添加fallthrough关键字,才会继续执行紧跟的下一个case;

答:B D

80. 如果 Add() 函数的调用代码为:

func main() {
    var a Integer = 1
    var b Integer = 2
    var i interface{} = &a
    sum := i.(*Integer).Add(b)
    fmt.Println(sum)
}

则Add函数定义正确的是()

A.
type Integer int
func (a Integer) Add(b Integer) Integer {
        return a + b
}
B.
type Integer int
func (a Integer) Add(b *Integer) Integer {
        return a + *b
}
C.
type Integer int
func (a *Integer) Add(b Integer) Integer {
        return *a + b
}
D.
type Integer int
func (a *Integer) Add(b *Integer) Integer {
        return *a + *b
}

答:A C

解析:

知识点:类型断言、方法集。

81. 关于 bool 变量 b 的赋值,下面错误的用法是?

  • A. b = true
  • B. b = 1
  • C. b = bool(1)
  • D. b = (1 == 2)

答:B C

82. 关于变量的自增和自减操作,下面语句正确的是?

A.
i := 1
i++
B.
i := 1
j = i++
C.
i := 1
++i
D.
i := 1
i--

答:A D

解析:

知识点:自增自减操作。

i++ 和 i-- 在 Go 语言中是语句,不是表达式,因此不能赋值给另外的变量。此外没有 ++i 和 --i。

83. 关于GetPodAction定义,下面赋值正确的是

type Fragment interface {
        Exec(transInfo *TransInfo) error
}
type GetPodAction struct {
}
func (g GetPodAction) Exec(transInfo *TransInfo) error {
        ...
        return nil
}
  • A. var fragment Fragment = new(GetPodAction)
  • B. var fragment Fragment = GetPodAction
  • C. var fragment Fragment = &GetPodAction{}
  • D. var fragment Fragment = GetPodAction{}

答:A C D

84. 关于函数声明,下面语法正确的是?

  • A. func f(a, b int) (value int, err error)
  • B. func f(a int, b int) (value int, err error)
  • C. func f(a, b int) (value int, error)
  • D. func f(a int, b int) (int, int, error)

答:A B D

85. 关于整型切片的初始化,下面正确的是?

  • A. s := make([]int)
  • B. s := make([]int, 0)
  • C. s := make([]int, 5, 10)
  • D. s := []int{1, 2, 3, 4, 5}

答:B C D

86. 下面代码会触发异常吗?请说明。

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

解析:

select

87. 关于channel的特性,下面说法正确的是?

  • A. 给一个 nil channel 发送数据,造成永远阻塞
  • B. 从一个 nil channel 接收数据,造成永远阻塞
  • C. 给一个已经关闭的 channel 发送数据,引起 panic
  • D. 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值

答:A B C D

88. 下面代码有什么问题?

const i = 100
var j = 123
func main() {
    fmt.Println(&j, j)
    fmt.Println(&i, i)
}

答:编译报错

解析:

cannot take the address of i

89. 下面代码能否编译通过?如果通过,输出什么?

func GetValue(m map[int]string, id int) (string, bool) {
    if _, exist := m[id]; exist {
        return "exist", true
    }
    return nil, false
}
func main() {
    intmap := map[int]string{
        1: "a",
        2: "b",
        3: "c",
    }
    v, err := GetValue(intmap, 3)
    fmt.Println(v, err)
}

答:不能通过编译

解析:

cannot use nil as type string in return argument

90. 关于异常的触发,下面说法正确的是?

  • A. 空指针解析;
  • B. 下标越界;
  • C. 除数为0;
  • D. 调用panic函数;

答:A B C D