Golang 每日一题 202302篇

好似又过了几个秋

Go每日一题 (str)

关于字符串连接,下面语法正确的是?

  • A. str := ‘abc’ + ‘123’
  • B. str := “abc” + “123”
  • C. str := ‘123’ + “abc”
  • D. fmt.Sprintf(“abc%d”, 123)

答案解析

BD
除了以上两种连接方式,还有 strings.Join()buffer.WriteString() 等。

Go每日一题 (结构体 引用)

以下代码能否编译?

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

import "fmt"

type Student struct {
Name string
}

var list map[string]Student

func main() {

list = make(map[string]Student)

student := Student{"Aceld"}

list["student"] = student
list["student"].Name = "LDB"

fmt.Println(list["student"])
}

答案解析

编译失败
./main.go:18:2: cannot assign to struct field list[“student”].Name in map

  • 修改成以下可编译运行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main

import "fmt"

type Student struct {
Name string
}

var list map[string]*Student

func main() {

list = make(map[string]*Student)

student := Student{"Aceld"}

list["student"] = &student
list["student"].Name = "LDB"

fmt.Println(list["student"])
}

Go每日一题 (结构体 range 遍历)

以下代码有什么问题,说明原因

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
28
29
30
31
32
package main

import (
"fmt"
)

type student struct {
Name string
Age int
}

func main() {
// 定义map
m := make(map[string]*student)

// 定义student数组
stus := []student{
{Name: "zhou", Age: 24},
{Name: "li", Age: 23},
{Name: "wang", Age: 22},
}

// 将数组依次添加到map中
for _, stu := range stus {
m[stu.Name] = &stu
}

// 打印map
for k,v := range m {
fmt.Println(k ,"=>", v.Name)
}
}

答案解析

遍历结果出现错误,输出结果为
zhou => wang
li => wang
wang => wang

  • 正确写法
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
28
29
30
31
32
package main

import (
"fmt"
)

type student struct {
Name string
Age int
}

func main() {
// 定义map
m := make(map[string]*student)

// 定义student数组
stus := []student{
{Name: "zhou", Age: 24},
{Name: "li", Age: 23},
{Name: "wang", Age: 22},
}

// 遍历结构体数组,依次赋值给map
for i := 0; i < len(stus); i++ {
m[stus[i].Name] = &stus[i]
}

// 打印map
for k,v := range m {
fmt.Println(k ,"=>", v.Name)
}
}
  • 或者修改
1
2
3
4
5
// 将数组依次添加到map中
for _, stu := range stus {
val := stu
m[stu.Name] = &val
}

Go每日一题 (nil)

下面赋值正确的是:

  • A. var x = nil
  • B. var x interface{} = nil
  • C. var x string = nil
  • D. var x error = nil

答案解析

答案及解析:BD。
nil 只能赋值给指针、chan、func、interface、map 或 slice 类型的变量。
强调下 D 选项的 error 类型,它是一种内置接口类型,看它的源码就知道,所以 D 是对的。

Go每日一题 (继承与多态)

以下代码能通过编译吗?为什么?

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
package main

import (
"fmt"
)

type People interface {
Speak(string) string
}

type Student struct{}

func (stu *Student) Speak(think string) (talk string) {
if think == "love" {
talk = "You are a good boy"
} else {
talk = "hi"
}
return
}

func main() {
var peo People = Student{}
think := "love"
fmt.Println(peo.Speak(think))
}

答案解析

编译失败

  • 需要修改成以下
1
2
3
4
5
func main() {
var peo People = &Student{}
think := "love"
fmt.Println(peo.Speak(think))
}

Go每日一题 (interface 接口底层类型问题)

以下代码打印出来什么内容,说出为什么。

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
28
package main

import (
"fmt"
)

type People interface {
Show()
}

type Student struct{}

func (stu *Student) Show() {

}

func live() People {
var stu *Student
return stu
}

func main() {
if live() == nil {
fmt.Println("AAAAAAA")
} else {
fmt.Println("BBBBBBB")
}
}

答案解析

BBBBBBB

  • 空接口eface
1
2
3
4
type eface struct {      // 空接口
_type *_type // 类型信息
data unsafe.Pointer // 指向数据的指针(go 语言中特殊的指针类型 unsafe.Pointer 类似于 c 语言中的void*)
}
  • 非空接口iface
1
2
3
4
type iface struct {
tab *itab
data unsafe.Pointer
}

Go每日一题 (encoding/json 导出)

以下代码输出什么?

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

import (
"encoding/json"
"fmt"
"time"
)

func main() {
t := struct {
time.Time
N int
}{
time.Date(2020, 12, 20, 0, 0, 0, 0, time.UTC),
5,
}

m, _ := json.Marshal(t)
fmt.Printf("%s", m)
}
  • A:{“Time”: “2020-12-20T00:00:00Z”, “N”: 5};
  • B:”2020-12-20T00:00:00Z”;
  • C:{“N”: 5};
  • D

答案解析

B
json.Marshal 函数优先调用 MarshalJSON,然后是 MarshalText,如果都没有,才会走正常的类型编码逻辑。
如果值实现了 json.Marshaler 接口并且不是 nil 指针,则 Marshal 函数会调用其 MarshalJSON 方法以生成 JSON。
如果不存在 MarshalJSON 方法,但该值实现 encoding.TextMarshaler 接口,则 Marshal 函数调用其 MarshalText 方法并将结果编码为 JSON 字符串。
在本题中匿名结构体内嵌了Time结构体,而该结构体实现了MarshalJSON 方法,因此会调用MarshalJSON方法以生成 JSON。

Go每日一题 (类型 数据溢出 补码)

今天给两道类似的题目,注意,有半数以上的人可能会做错!

  • 题一
1
2
3
4
5
6
7
8
package main

func main() {
var a int8 = -1
var b int8 = -128 / a

println(b)
}
  • 题二
1
2
3
4
5
6
7
8
package main

func main() {
const a int8 = -1
var b int8 = -128 / a

println(b)
}

它们分别输出什么?

答案解析

题一 -128,题二编译错误?

因为 var b int8 = -128 / a 不是常量表达式,因此 untyped 常量 -128 隐式转换为 int8 类型(即和 a 的类型一致),
所以 -128 / a 的结果是 int8 类型,值是 128,超出了 int8 的范围。因为结果不是常量,允许溢出,
128 的二进制表示是 10000000,正好是 -128 的补码。所以,第一题的结果是 -128。

对于 var b int8 = -128 / a,因为 a 是 int8 类型常量,所以 -128 / a 是常量表达式,在编译器计算,结果必然也是常量。
因为 a 的类型是 int8,因此 -128 也会隐式转为 int8 类型,128 这个结果超过了 int8 的范围,但常量不允许溢出,因此编译报错。

Go每日一题 (init)

关于 init 函数,下面说法正确的是:

  • A. 一个包中,可以包含多个 init 函数;
  • B. 程序运行时,先执行依赖包的 init 函数,再执行 main 包内的 init 函数;
  • C. main 包中,不能有 init 函数;
  • D. init 函数可以被其他函数调用;

答案解析

案及解析:AB。

go文件的初始化顺序

  1. 引入的包
  2. 当前包中的常量变量
  3. 当前包的init
  4. main函数

Go每日一题 (Go 中 函数 是 一等公民)

下面这段代码输出什么以及原因?

1
2
3
4
5
6
7
8
9
10
11
12
func hello() []string {  
return nil
}

func main() {
h := hello
if h == nil {
fmt.Println("nil")
} else {
fmt.Println("not nil")
}
}
  • A. nil
  • B. not nil
  • C. compilation error

答案解析

B
这道题目里面,是将函数 hello 赋值给变量 h,而不是函数的返回值(即不是进行函数调用),所以输出 not nil。注意 Go 中函数是一等公民。

Go每日一题 (类型断言)

下面这段代码能否编译通过?如果可以,输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func GetValue() int {
return 1
}

func main() {
i := GetValue()
switch i.(type) {
case int:
println("int")
case string:
println("string")
case interface{}:
println("interface")
default:
println("unknown")
}
}

答案解析

答案及解析:编译失败。

考点:类型断言,类型断言的语法形如:i.(type),其中 i 是接口,type 是固定关键字,需要注意的是,只有接口类型才可以使用类型断言。

Go每日一题 (channel 延迟关闭)

执行下面的代码会发生什么?

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
28
package main

import (
"fmt"
"time"
)

func main() {
ch := make(chan int, 1000)
go func() {
for i := 0; i < 10; i++ {
ch <- i
}
}()
go func() {
for {
a, ok := <-ch
if !ok {
fmt.Println("close")
return
}
fmt.Println("a: ", a)
}
}()
close(ch)
fmt.Println("ok")
time.Sleep(time.Second * 100)
}

答案解析

ok
close
panic: send on closed channel

记住channel的一些关键特性

  • 给一个 nil channel 发送数据,造成永远阻塞
  • 从一个 nil channel 接收数据,造成永远阻塞
  • 给一个已经关闭的 channel 发送数据,引起 panic
  • 从一个已经关闭的 channel 接收数据,如果缓冲区中为空,则返回一个零值
  • 无缓冲的channel是同步的,而有缓冲的channel是非同步的

15字口诀:“空读写阻塞,写关闭异常,读关闭空零”

Go每日一题 (WaitGroup)

以下代码有什么问题?

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

import (
"sync"
)

const N = 10

var wg = &sync.WaitGroup{}

func main() {
for i := 0; i < N; i++ {
go func(i int) {
wg.Add(1)
println(i)
defer wg.Done()
}(i)
}
wg.Wait()
}

答案解析

输出结果不唯一,代码存在风险, 所有 go 语句未必都能执行到。
这是使用 WaitGroup 经常犯下的错误!请各位同学多次运行就会发现输出都会不同甚至又出现报错的问题。
这是因为 go 执行太快了,导致 wg.Add(1) 还没有执行 main 函数就执行完毕了。wg.Add 的位置放错了

  • 改为下面的代码试试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"sync"
)

const N = 10

var wg = &sync.WaitGroup{}

func main() {

for i:= 0; i< N; i++ {
wg.Add(1)
go func(i int) {
println(i)
defer wg.Done()
}(i)
}

wg.Wait()
}

原子操作边界问题,在边界前加锁,边界后解锁。

Go每日一题 (channel)

关于 channel,下面语法正确的是

  • A. var ch chan int
  • B. ch := make(chan int)
  • C. <- ch
  • D. ch <-

答案解析

答案及解析:ABC。
A、B 都是声明 channel;C 读取 channel;写 channel 是必须带上值,所以 D 错误。

Go每日一题 (map 0 值)

下面这段代码输出什么?

1
2
3
4
5
6
7
8
9
type person struct {  
name string
}

func main() {
var m map[person]int
p := person{"mike"}
fmt.Println(m[p])
}
  • A. 0
  • B. 1
  • C. Compilation error

答案解析

答案及解析:A。
m 是一个 map,值是 nil。从 nil map 中取值不会报错,而是返回相应的零值,这里值是 int 类型,因此返回 0。

map[p] 在经过 编译后,走的是 runtime.mapaccess1 的逻辑;
而看到 mapaccess1 函数,对于 hmap 是 nil 的情况是直接返回空值;源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
func mapaccess1(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
// ...

if h == nil || h.count == 0 {// h 就是map指向的地址,因为题目中map还没有申请分配内存空间,所以h是nil
if t.hashMightPanic() {
t.hasher(key, 0) // see issue 23734
}
return unsafe.Pointer(&zeroVal[0])
}

// ...
}

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

下面这段代码输出什么?

1
2
3
4
5
6
7
8
9
func hello(num ...int) {  
num[0] = 18
}

func main() {
i := []int{5, 6, 7}
hello(i...)
fmt.Println(i[0])
}
  • A. 18
  • B. 5
  • C. Compilation error

答案解析

答案及解析:A. 18。
可变参数是切片,切片是引用,所以func内赋值会带出来。

Go每日一题 (强类型)

下面这段代码输出什么?

1
2
3
4
5
func main() {  
a := 5
b := 8.1
fmt.Println(a + b)
}
  • A. 13.1
  • B. 13
  • C. compilation error

答案解析

答案及解析:C。
a 的类型是 int,b 的类型是 float,两个不同类型的数值不能相加,编译报错。
Go语言的类型机制更加严格,没有隐式类型转换,所以不同类型的数据不能直接参与同一个运算。

Go每日一题 (json slice)

以下代码输出什么?

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

import (
"encoding/json"
"fmt"
)

type AutoGenerated struct {
Age int `json:"age"`
Name string `json:"name"`
Child []int `json:"child"`
}

func main() {
jsonStr1 := `{"age": 14,"name": "potter", "child":[1,2,3]}`
a := AutoGenerated{}
json.Unmarshal([]byte(jsonStr1), &a)
aa := a.Child
fmt.Println(aa)
jsonStr2 := `{"age": 12,"name": "potter", "child":[3,4,5,7,8,9]}`
json.Unmarshal([]byte(jsonStr2), &a)
fmt.Println(aa)
}
  • A: [1 2 3] [1 2 3] ;
  • B: [1 2 3] [3 4 5];
  • C: [1 2 3] [3 4 5 6 7 8 9];
  • D: [1 2 3] [3 4 5 0 0 0]

答案解析

结果为什么是 [1 2 3] [3 4 5] 呢?

这道题涉及到两个知识点:

  • json解析
  • slice

第一次解析,aa.Child 是:[1 2 3],cap = 4。第二次解析,aa.Child 先被重置,之后将 3,4,5,7,8,9 一个个 append,
最后 aa.Child 是:[3 4 5 6 7 8 9], cap = 6。

aa在第一次赋值时,就已经确定了长度和容量;第二次反序列化json时,aa的长度和容量是没有变化的,
但是底层数组却发生了变化(底层数组的数据内容,长度,容量都改变了),aa是从底层数组上取数据的

Go每日一题 (数据竞争 main goroutine 和其他 goroutine 调度问题 局部变量问题)

以下代码有什么问题,怎么解决?

1
2
3
4
5
6
7
8
total, sum := 0, 0
for i := 1; i <= 10; i++ {
sum += i
go func() {
total += i
}()
}
fmt.Printf("total:%d sum %d", total, sum)

答案解析

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
total, sum := 0, 0
for i := 1; i <= 10; i++ {
sum += i
go func(i int) {
fmt.Println(i)
total += i
}(i)
}

time.Sleep(1e9)
fmt.Printf("total:%d sum %d", total, sum)
}

解决一下问题:

  • i 使用的问题
  • data race。因为存在多 goroutine 同时写 total 变量的问题,所以有数据竞争。可以加上 -race 参数验证:
  • main 函数先退出了,开启的 goroutine 根本没有机会执行。

Go每日一题 (数据类型)

下面这段代码输出什么?

1
2
3
4
5
6
7
8
9
func main() {
a := [2]int{5, 6}
b := [3]int{5, 6}
if a == b {
fmt.Println("equal")
} else {
fmt.Println("not equal")
}
}
  • A. compilation error
  • B. equal
  • C. not equal

答案解析

Go 中的数组是值类型,可比较,另外一方面,
数组的长度也是数组类型的组成部分,所以 a 和 b 是不同的类型,是不能比较的,所以编译错误。

Go每日一题 (cap)

以下哪种类型可以使用 cap() 函数?

  • A. array
  • B. slice
  • C. map
  • D. channel

答案解析

答案及解析:ABD。
知识点:cap(),cap() 函数不适用 map。

Go每日一题 (interface 零值)

下面这段代码输出什么?

1
2
3
4
5
6
7
8
func main() {
var i interface{}
if i == nil {
fmt.Println("nil")
return
}
fmt.Println("not nil")
}
  • A. nil
  • B. not nil
  • C. compilation error

答案解析

答案及解析:A。
当且仅当接口的动态值和动态类型都为 nil 时,接口类型值才为 nil。

Go每日一题 (map 零值)

下面这段代码输出什么?

1
2
3
4
5
func main() {
s := make(map[string]int)
delete(s, "h")
fmt.Println(s["h"])
}
  • A. runtime panic
  • B. 0
  • C. compilation error

答案解析

答案及解析:B。
删除 map 不存在的键值对时,不会报错,相当于没有任何作用;获取不存在的减值对时,返回值类型对应的零值,所以返回 0。

Go每日一题 (符号输出)

下面这段代码输出什么?

1
2
3
4
5
func main() {  
i := -5
j := +5
fmt.Printf("%+d %+d", i, j)
}
  • A. -5 +5
  • B. +5 +5
  • C. 0 0

答案解析

答案及解析:A。
%d表示输出十进制数字,+表示输出数值的符号。这里不表示取反。

Go每日一题 (接口 内嵌 组合)

下面这段代码输出什么?

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

答案解析

答案及解析:teacher showB。

知识点:结构体嵌套。
在嵌套结构体中,People 称为内部类型,Teacher 称为外部类型;
通过嵌套,内部类型的属性、方法,可以为外部类型所有,就好像是外部类型自己的一样。
此外,外部类型还可以定义自己的属性和方法,甚至可以定义与内部相同的方法,这样内部类型的方法就会被“屏蔽”。
这个例子中的 ShowB() 就是同名方法。

Powered by Hexo and Hexo-theme-hiker

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

访客数 : | 访问量 :