Golang 每日一题 202304篇

每月一次,重在积累

Go每日一题(slice)

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

1
2
3
4
5
6
7
8
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]

当使用 s1[1:] 获得切片 s2,和 s1 共享同一个底层数组,这会导致 s2[1] = 4 语句影响 s1。

而 append 操作会导致底层数组扩容,生成新的数组,因此追加数据后的 s2 不会影响 s1。

Go每日一题(if)

下面选项正确的是?

1
2
3
4
5
6
7
func main() {
if a := 1; false {
} else if b := 2; false {
} else {
println(a, b)
}
}
  • A. 1 2
  • B. compilation error

答案解析

答案及解析:A。
https://studygolang.com/articles/35587

Go每日一题(map 遍历)

下面这段代码输出什么?

1
2
3
4
5
6
func main() {
m := map[int]string{0:"zero",1:"one"}
for k,v := range m {
fmt.Println(k,v)
}
}

答案解析

0 zero
1 one
// 或者
1 one
0 zero

map 的输出是无序的。

Go每日一题(defer)

下面这段代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
a := 1
b := 2
defer calc("1", a, calc("10", a, b))
a = 0
defer calc("2", a, calc("20", a, b))
b = 1
}

func calc(index string, a, b int) int {
ret := a + b
fmt.Println(index, a, b, ret)
return ret
}

答案解析

1
2
3
4
10 1 2 3
20 0 2 2
2 0 2 2
1 1 3 4

程序执行到 main() 函数三行代码的时候,会先执行 calc() 函数的 b 参数
程序执行到末尾的时候,按照栈先进后出的方式依次执行defer

Go每日一题(结构体指针)

以下代码输出什么:

1
2
3
4
5
6
7
8
9
package main

func main() {
var x *struct {
s [][32]byte
}

println(len(x.s[99]))
}
  • A:运行时 panic;
  • B:32;
  • C:编译错误;
  • D:0

答案解析

正确答案:B。

注意这里不是定义一个结构体类型,而是定义一个结构体类型指针变量,即 x 是一个指针,指针类型是一个匿名结构体。
很显然,x 的值是 nil,因为没有初始化,可以打印证实这一点。

内建函数 len 返回 v 的长度,这取决于具体类型:

  • 数组:v 中元素的数量
  • 数组指针:*v 中元素的数量(v 为 nil 时 panic)
  • 切片、map:v 中元素的数量;若 v 为 nil,len(v) 即为零
  • 字符串:v 中字节的数量
  • 通道:通道缓存中队列(未读取)元素的数量;若 v 为 nil,len(v) 即为零

Go每日一题(类型方法定义)

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

1
2
3
4
5
6
7
8
func (i int) PrintInt ()  {
fmt.Println(i)
}

func main() {
var i int = 1
i.PrintInt()
}
  • A. 1
  • B. compilation error

答案解析

基于类型创建的方法必须定义在同一个包内,上面的代码基于 int 类型创建了 PrintInt() 方法,
由于 int 类型和方法 PrintInt() 定义在不同的包内,所以编译出错。解决的办法可以定义一种新的类型:

1
2
3
4
5
6
7
8
9
10
type Myint int

func (i Myint) PrintInt () {
fmt.Println(i)
}

func main() {
var i Myint = 1
i.PrintInt()
}

Go每日一题(接口实现)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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))
}
  • A. speak
  • B. compilation error

答案解析

答案及解析:B。
值类型 Student 没有实现接口的 Speak() 方法,而是指针类型 *Student 实现改方法。

Go每日一题(const iota)

下面这段代码输出什么?

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 是 golang 语言的常量计数器,只能在常量的表达式中使用
iota 在 const 关键字出现时将被重置为 0,const 中每新增一行常量声明将使 iota 计数一次

Go每日一题(iota String())

下面这段代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 的用法推断 South 的值是 2;另外,
如果定义了 String()方法,当使用 fmt.Printf()、fmt.Print() 和 fmt.Println()会自动使用 String()方法, 实现字符串打印
所以,最终输出 South

Go每日一题(map)

下面代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
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
类似于 X=Y 的赋值操作,必须知道 X 的地址,才能够将 Y 的值赋给 X,但 go 中的 map 的 value 本身是不可寻地址。

Go每日一题(chan deadlock)

以下代码的输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "sync"

func main() {
var wg sync.WaitGroup
foo := make(chan int)
bar := make(chan int)
wg.Add(1)
go func() {
defer wg.Done()
select {
case foo <- <-bar:
default:
println("default")
}
}()
wg.Wait()
}
  • A:default
  • B:panic

答案解析

答案及解析:B
deadlock:无数据取,满数据写

Go每日一题(类型比较)

下面的代码有什么问题?

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

答案解析

答案及解析:有两处错误

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

Go每日一题(变量作用域)

下面这段代码输出什么?

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)
}
  • A. 5 5
  • B. runtime error

答案解析

答案及解析:B。
变量作用域。问题出在操作符 :=,对于使用 := 定义的变量,
如果新变量与同名已定义的变量不在同一个作用域中,那么 Go 会新定义这个变量。

对于本例来说,main() 函数里的 p 是新定义的变量,会遮住全局变量 p,导致执行到 bar()时程序,全局变量 p 依然还是 nil,程序随即 Crash。

Go每日一题(range 循环)

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

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

答案解析

答案及解析:不会出现死循环,能正常结束。
循环次数在循环开始前就已经确定,循环内改变切片的长度,不影响循环次数。

Go每日一题(可变参数 append)

下面这段代码输出什么?

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]

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

Go每日一题(array)

下面这段代码输出什么?

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]

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

Go每日一题(range)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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}。

Go每日一题(map)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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。

Go每日一题(goroutine)

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

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

答案解析

答案及解析:AD。

Go每日一题(循环)

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

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

答案解析

答案及解析:CD。
平行赋值: a[i], a[j] = a[j], a[i]

Go每日一题(多重赋值)

下面代码输出正确的是?

1
2
3
4
5
6
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。
多重赋值。

多重赋值分为两个步骤,有先后顺序: 计算等号左边的索引表达式和取址表达式,接着计算等号右边的表达式; 赋值;
所以本例,会先计算 s[i-1],等号右边是两个表达式是常量,所以赋值运算等同于 i, s[0] = 2, “Z”

Go每日一题(强制类型转化)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
知识点:强制类型转化。
D 是类型断言

Go每日一题(switch)

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

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

答案解析

答案及解析:BD。

Go每日一题(类型断言 方法集)

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

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

则 Add 函数定义正确的是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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
}

答案解析

答案及解析:AC。
知识点:类型断言、方法集。

go 中有些的变量不可以寻址,指的是不能通过&获得其地址。

所以 func( *A ) 只能接收 *A, func( A ) 可以接收 A 或者 *A ,通过指针一定能得到变量的值 *A -> A

Go每日一题(range)

下面这段代码输出什么,说明原因。

1
2
3
4
5
6
7
8
9
10
11
12
func main() {
slice := []int{0,1,2,3}
m := make(map[int]*int)

for key,val := range slice {
m[key] = &val
}

for k,v := range m {
fmt.Println(k,"->",*v)
}
}

答案解析

0 -> 3
1 -> 3
2 -> 3
3 -> 3

for range 循环的时候会创建每个元素的副本,而不是元素的引用,所以 m[key] = &val 取的都是变量 val 的地址,所以最后 map 中的所有元素的值都是变量 val 的地址,因为最后 val 被赋值为 3,所有输出都是 3

Powered by Hexo and Hexo-theme-hiker

Copyright © 2017 - 2023 Keep It Simple And Stupid All Rights Reserved.

访客数 : | 访问量 :