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": Math2, 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": &Math2, 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([...]int1 == [2]int1)//第一行
fmt.Println([]int1 == []int1)//第二行
-
第一行、第二行都错误
-
go 中不同类型是不能比较的,而数组长度是数组类型的一部分,所以 […]int1 和 [2]int1 是两种不同的类型,不能比较;
-
切片不能比较;
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 = [...]int1, 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 = []int1, 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]int1, 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]int1, 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: []int1, 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]int1, 2, 3,
foo(t)
fmt.Println(t.ls[0])
此时输出就为 1。因为数组赋值没有直接对底层数组进行操作。
19.
下面这段代码输出什么?
var x = []int2: 2, 3, 0: 1
func main()
fmt.Println(x)
输出:[1 0 2 3].字面量初始化切片时候,可以指定索引,没有指定索引的元素会在前一个索引基础之上加一,所以输出[1 0 2 3],而不是[1 3 2]。
例:
var x = []int2: 2, 3, 0: 1, 4以上是关于Go语言学习基础刷题记(Golang roadmap)的主要内容,如果未能解决你的问题,请参考以下文章
Go语言学习基础刷题记(Golang roadmap)2021-07-13