Go语言学习基础刷题记(Golang roadmap)

Posted Demonwuwen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Go语言学习基础刷题记(Golang roadmap)相关的知识,希望对你有一定的参考价值。

无意间发现一个go语言学习很不错的一个网站:https://www.golangroadmap.com/,推荐值:五颗星。
注册需要邀请码:Gopher-1664-0418
在这里插入图片描述
go语言基础测试题:

1.

package main

import "fmt"

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,再执行第二个defer,var f func()声明函数后,直接defer f()会报错,因为没有实体被定义。所以随即执行第一个defer,异常被recover(), 程序正常执行,就是4+3 =7
如果将defer f()放到 f = func(){...}之后就不会报错。
将此题进行改写:

package main

import "fmt"

func f(n int) (r int) {

	defer func() {
		r += n
		//recover()
	}()

	var f func()
	f = func() {
		r += 2
	}
	defer f()
	
	return n + 1
}

func main() {
	fmt.Println(f(3))
}

此时执行流程就应该是:r = n + 1 = 4 ➡️ defer f() 4 + 2 =6 ➡️ defer func() { 6 + 3} 所以最后结果为 9。

2.

package main

import "fmt"

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

此时第14行代码是报错:Cannot assign to m["foo"].x.原因是 map 元素是无法取址的,也就说可以得到 m[“foo”].x, 但是无法对其进行修改。
可以采用指针的形式进行修改:

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)
}
//运行结果 4	

3.

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

会直接报错:cannot use Student{} (type Student) as type People in assignment: Student does not implement People (Speak method has pointer receiver)
fix方法:

  • 一:将var peo People = Student{} 改成 var peo People = &Student{}即可。得到speak。
  • 二: 将unc (stu *Student) Speak(think string) (talk string)改为func(stu Student) Speak(think string) (talk string)

4.

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]
  • 解析
    Go 提供的语法糖…,可以将 slice 传进可变函数,不会创建新的切片。第一次调用 change() 时,append() 操作使切片底层数组发生了扩容,原 slice 的底层数组不会改变;第二次调用change() 函数时,使用了操作符[i,j]获得一个新的切片,假定为 slice1,它的底层数组和原切片底层数组是重合的,不过 slice1 的长度、容量分别是 2、5,所以在 change() 函数中对 slice1 底层数组的修改会影响到原切片。

5.

题目,第一行和第二行谁报错

func main() {
	fmt.Println([...]int{1} == [2]int{1})//第一行
	fmt.Println([]int{1} == []int{1})//第二行
}
  • 第一行、第二行都错误

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

  • 切片不能比较;

6.

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}
&{C} &{C} &{C}
  • 题目解析
    s2 的输出结果错误。s2 的输出是 &{C} &{C} &{C},for range 使用短变量声明(:=)的形式迭代变量时,变量 i、value 在每次循环体中都会被重用,而不是重新声明。所以 s2 每次填充的都是临时变量 value 的地址,而在最后一次循环中,value 被赋值为{c}。因此,s2 输出的时候显示出了三个 &{c}。

可行的解决办法如下:

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

7 .

下面这段代码输出什么?思考为什么。

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

8.

下面这段代码输出什么?

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 可以获得修改后的数组元素。

稍作修改,题改成这样:
下面这段代码输出什么?

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

9.

下面这段代码是否正确?

type Param map[string]interface{}

 type Show struct {
     *Param
 }

 func main() {
     s := new(Show)
     s.Param["day"] = 2
}

解析:
存在两个问题:1.map 需要初始化才能使用;2.指针不支持索引。修复代码如下:

func main() {
    s := new(Show)
    // 修复代码
    p := make(Param)
    p["day"] = 2
    s.Param = &p
    tmp := *s.Param
    fmt.Println(tmp["day"])
}

10.

下面代码编译能通过吗?

func main()  
{ 
    fmt.Println("hello world")
}

解析:
编译错误报错:missing function body
syntax error: unexpected semicolon or newline before {
Go 语言中,大括号不能放在单独的一行。

正确的代码如下:

func main(){ 
    fmt.Println("hello world")
}

11.

下面代码能正常运行嘛?

const i = 100
var j = 123

func main() {
    fmt.Println(&j, j)
    fmt.Println(&i, i)
}

解析:编译报错cannot take the address of i。常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,所以常量无法寻址。

12.

下面代码会触发异常吗?

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 会随机选择一个可用通道做收发操作,所以可能触发异常,也可能不会。

13.

下面代码输出正确的是?

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

输出:s: [Z,B,C]
多重赋值分为两个步骤,有先后顺序:

  • 计算等号左边的索引表达式和取址表达式,接着计算等号右边的表达式;
  • 赋值;

所以本例,会先计算 s[i-1],等号右边是两个表达式是常量,所以赋值运算等同于 i, s[0] = 2, “Z”。

14.

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

func Foo(x interface{}) {
     if x == nil {
         fmt.Println("empty interface")
         return
     }
     fmt.Println("non-empty interface")
}
func main() {
     var x *int = nil
    Foo(x)
}

输出:“non-empty interface”
接口除了有静态类型,还有动态类型和动态值,当且仅当动态类型和动态值均为nil时,结偶类型值才为nil。这里的x动态类型为*int型。所以x不为nil。

15.

下面代码能否编译通过?

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

不能通过编译。知识点:函数返回值类型。nil 可以用作 interface、function、pointer、map、slice 和 channel 的“空值”。但是如果不特别指定的话,Go 语言不能识别类型,所以会报错:cannot use nil as type string in return argument.

16.

下面的代码是否正确?

func Stop(stop <-chan bool) {
    close(stop)
}

编译器会报错:Cannot use 'stop' (type <-chan bool) as type chan<- Type
<-chan 类型不能关闭,但chan<-可以。

17.

下面代码输出什么?

func test(x byte)  {
    fmt.Println(x)
}

func main() {
    var a byte = 0x11 
    var b uint8 = a
    var c uint8 = a + b
    test(c)
}

输出:34
解析:与 rune 是 int32 的别名一样,byte 是 uint8 的别名,别名类型无序转换,可直接转换。

18.

下面这段代码输出什么?

type T struct {
    ls []int
}
func foo(t T) {
    t.ls[0] = 100
}
func main() {
    var t = T{
        ls: []int{1, 2, 3},
    }
    foo(t)
    fmt.Println(t.ls[0])
}

输出:100
结构体里面ls是切片,调用 foo() 函数时虽然是传值,但 foo() 函数中,字段 ls 依旧可以看成是指向底层数组的指针。
如果将切片改为数组,如下:

type T struct {
    ls [3]int
}
func foo(t T) {
    t.ls[0] = 100
}
func main() {
    var t = T{
        ls: [3]int{1, 2, 3},
    }
    foo(t)
    fmt.Println(t.ls[0])
}

此时输出就为 1。因为数组赋值没有直接对底层数组进行操作。

19.

下面这段代码输出什么?

var x = []int{2: 2, 3, 0: 1}

func main() {
    fmt.Println(x)
}

输出:[1 0 2 3].字面量初始化切片时候,可以指定索引,没有指定索引的元素会在前一个索引基础之上加一,所以输出[1 0 2 3],而不是[1 3 2]。

例:

var x = []int{2: 2, 3Go语言学习基础刷题记(Golang roadmap)2021-07-13

Go语言学习基础刷题记(Golang roadmap)2021-07-13

用golang刷LeetCode

golang 目录

go语言(golang)学习之路

GO 语言基础语法一 (快速入门 Go 语言)