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