chaoz的杂货铺

生命有息、学无止境、折腾不止

0%

2022-Golang-错题本

错题集

0227

  1. 下面这段代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    func main() {  
    var i interface{}
    if i == nil {
    fmt.Println("nil")
    return
    }
    fmt.Println("not nil")
    }

nil

解析:当且仅当接口的动态值和动态类型都为 nil 时,接口类型值才为 nil。
指针,函数,interface,slice,channel和map的零值都是nil
空接口赋值之后其值为nil, 但是其本身不是nil,所以打印的时候是nil, 判等的时候并不是nil。

  1. 下面代码下划线处可以填入哪个选项?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    func main() {
    var s1 []int // nil切片
    var s2 = []int{} // 空切片
    if __ == nil {
    fmt.Println("yes nil")
    }else{
    fmt.Println("no nil")
    }
    }

s1 s2 都可以

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

nil切片和空切片指向的地址不一样。nil空切片引用数组指针地址为0(无指向任何实际地址)
空切片的引用数组指针地址是有的,且固定为一个值。
nil切片和空切片最大的区别在于指向的数组引用地址是不一样的,所有的空切片指向的数组引用地址都是一样的。
20220227170725
20220227170732

通过var a []int创建的切片是一个nil切片
通过var s2 []int = []int{} 等价于 var s2 = []int{},声明并赋值一个空切片
通过b:=make([]int,0)创建的是一个空切片,(底层数组为空,但底层数组指针非空)

  1. 下面的代码有几处语法问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main
    import (
    "fmt"
    )
    func main() {
    var x string = nil
    if x == nil {
    x = "default"
    }
    fmt.Println(x)
    }

2

解析:var x string = nil、x == nil两个地方有语法问题。golang 的字符串类型是不能赋值 nil 的,也不能跟 nil 比较。

  1. 下面代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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())
    }

0 1
increaseA() 的返回参数是匿名,increaseB() 是具名。

  1. 下面代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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}
    }

29 28 28

  1. 下面这段代码输出什么?
    1
    2
    3
    4
    func main() {  
    i := 65
    fmt.Println(string(i))
    }

A

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

  1. 下面代码段输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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

  1. 下面这段代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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()
    }

showA showB

  1. 切片 a、b、c 的长度和容量分别是多少?
    1
    2
    3
    4
    5
    6
    7
    func main() {

    s := [3]int{1, 2, 3}
    a := s[:0]
    b := s[:2]
    c := s[1:2:cap(s)]
    }

03 23 12

知识点:数组或切片的截取操作。
截取操作有带 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。

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

编译错误
常量,Go 语言中的字符串是只读的

  1. 下面这段代码输出什么?
    1
    2
    3
    4
    5
    func main() {  
    s := make(map[string]int)
    delete(s, "h")
    fmt.Println(s["h"])
    }

0

  1. 下面代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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())
    }

编译错误

  1. 下面代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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())
    }

13 23

  1. 下面这段代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    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())
    }

13 23

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

  1. 关于 cap() 函数的适用类型
    channel slice array

  2. 1、2、3、4 哪些选项有语法错误?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    type S struct {
    }

    func f(x interface{}) {
    }

    func g(x *interface{}) {
    }

    func main() {
    s := S{}
    p := &s
    f(s) //1
    g(s) //2
    f(p) //3
    g(p) //4
    }

2 4

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

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

0228

  1. 下面这段代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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]

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

  1. 下面这段代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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 计数一次。

  1. 下面这段代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    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 的元素。

  1. 下面这段代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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)
    }

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

  1. 下面这段代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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
20220228022421

  1. 下面这段代码输出什么?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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()操作。Go 提供的语法糖…,可以将 slice 传进可变函数,不会创建新的切片。
第一次调用 change() 时,append() 操作使切片底层数组发生了扩容,原 slice 的底层数组不会改变;
第二次调用change() 函数时,使用了操作符[i,j]获得一个新的切片,假定为 slice1,它的底层数组和原切片底层数组是重合的,不过 slice1 的长度、容量分别是 2、5,所以在 change() 函数中对 slice1 底层数组的修改会影响到原切片。

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

知识点:iota 的用法、类型的 String() 方法。

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

  1. 下面这段代码输出结果正确吗?
    for range 的坑
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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 的输出结果错误。s2 的输出是 &{C} &{C} &{C},for range 使用短变量声明(:=)的形式迭代变量时,变量 i、value 在每次循环体中都会被重用,而不是重新声明。所以 s2 每次填充的都是临时变量 value 的地址,而在最后一次循环中,value 被赋值为{c}。因此,s2 输出的时候显示出了三个 &{c}。
可行的解决办法如下:
for i := range s1 {
s2[i] = &s1[i]
}

  1. 下面这段代码输出什么?思考为什么。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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)
}()
}

0228

  1. 20220228234835

  2. 20220228235856

  3. 20220301000000
  4. 20220301000022
  5. 20220301001004
  6. 20220301001736
  7. 20220301003024
  8. 20220301003057

0328

  1. 20220328215622
    常量。常量组中如不指定类型和初始化值,则与上一行非空常量右值相同
  2. 20220328220236
    调用 foo() 函数时虽然是传值,但 foo() 函数中,字段 ls 依旧可以看成是指向底层数组的指针
  3. 20220328220333
    类型断言语法:i.(Type),其中 i 是接口,Type 是类型或接口。编译时会自动检测 i 的动态类型与 Type 是否一致。但是,如果动态类型不存在,则断言总是失败
  4. 20220328220403
    输出[1 0 2 3],字面量初始化切片时候,可以指定索引,没有指定索引的元素会在前一个索引基础之上加一,所以输出[1 0 2 3],而不是[1 3 2]。
    var x1 = []int{2: 2}
    fmt.Println(x1) // 0 0 2
    var x1 = []int{2: 2, 3}
    fmt.Println(x1) // 0 0 2 3
  5. 20220328220454
    运行时错误。如果类型实现 String() 方法,当格式化输出时会自动使用 String() 方法。上面这段代码是在该类型的 String() 方法内使用格式化输出,导致递归调用,最后抛错。
  6. 20220328220743
    err 没有用到
  7. 20220328220810
    常量。常量是一个简单值的标识符,在程序运行时,不会被修改的量。不像变量,常量未使用是能编译通过的。

  8. 20220328220843
    不能对 nil 的 map 直接赋值,需要使用 make() 初始化。但可以使用 append() 函数对为 nil 的 slice 增加元素。

    1
    2
    3
    4
    5
    func main() {
    var m map[string]int
    m = make(map[string]int)
    m["one"] = 1
    }
喜欢这篇文章?打赏一下作者吧!

欢迎关注我的其它发布渠道