Golang 每日一题 202212篇

从问题中发现问题, 从提问中提升自己

Go每日一题 (闭包)

以下代码输出什么?

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 "fmt"

func app() func(string) string {
t := "Hi"
c := func(b string) string {
t = t + " " + b
return t
}
return c
}

func main() {
a := app()
fmt.Printf("a: %p\n", &a)
b := app()
fmt.Printf("b: %p\n", &b)
a("go")
fmt.Println(b("All"))
//fmt.Println(a("All"))
}

A:Hi All;B:Hi go All;C:Hi;D:go All

答案解析

如果最后再加一行代码:fmt.Println(a(“All”)), 它输出什么?

  • 自测结果:

a: 0x1400000e028
b: 0x1400000e038
Hi All
Hi go All

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

import (
"fmt"
)

type Person struct {
age int
}

func main() {
person := &Person{28}

// 1.
defer fmt.Println(person.age)

// 2.
defer func(p *Person) {
fmt.Println(p.age)
}(person)

// 3.
defer func() {
fmt.Println(person.age)
}()

person.age = 29
}

答案解析

答案及解析:29 29 28。变量 person 是一个指针变量。

  1. person.age 此时是将 28 当做 defer 函数的参数,会把 28 缓存在栈中,等到最后执行该 defer 语句的时候取出,即输出 28;
  2. defer 缓存的是结构体 Person{28} 的地址,最终 Person{28} 的 age 被重新赋值为 29,所以 defer 语句最后执行的时候,依靠缓存的地址取出的 age 便是 29,即输出 29;
  3. 很简单,闭包引用,输出 29; 又由于 defer 的执行顺序为先进后出,即 3 2 1,所以输出 29 29 28。

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

下面 A、B 两处应该填入什么代码,才能确保顺利打印出结果?

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

import (
"fmt"
)

type S struct {
m string
}

func f() *S {
return __ //A
}

func main() {
p := __ //B
fmt.Println(p.m) //print "foo"
}

答案解析

A. &S{"foo"} B. *f() 或者 f()

f() 函数返回参数是指针类型,所以可以用 & 取结构体的指针;
B 处,如果填 *f(),则 p 是 S 类型;如果填 f(),则 p 是 *S 类型,不过都可以使用 p.m 取得结构体的成员。

Go每日一题 (常量表达式)

以下代码输出什么?

1
2
3
4
5
6
7
8
9
10
package main

import (
"fmt"
)

func main() {
var ans float64 = 15 + 25 + 5.2
fmt.Println(ans)
}

A:不能编译;B:45;C:45.2;D:45.0

答案解析

常量表达式是指仅包含常量操作数,且是在编译的时候进行计算的。

而常量,在 Go 语言中又可以分为无类型常量和有类型常量,也可以分为字面值常量和具名常量。说人话?!

通过代码看看:

1
2
3
4
5
const a = 1 + 2      // a == 3,是无类型常量
const b int8 = 1 + 2 // b == 3,是有类型常量,类型是 int8

// 而 1、2 这样的就是字面值常量
// a、b 这样的就是具名常量

Go每日一题 (字符串类型)

下面的代码有几处语法问题,各是什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
)

func main() {
var x string = nil
if x == nil {
x = "default"
}
fmt.Println(x)
}

答案解析

2 处有语法问题。
golang 的字符串类型是不能赋值 nil 的,也不能跟 nil 比较。

Go每日一题 (defer)

return 之后的 defer 语句会执行吗,下面这段代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import (
"fmt"
)

var a bool = true

func main() {
defer func() {
fmt.Println("1")
}()
if a == true {
fmt.Println("2")
return
}
defer func() {
fmt.Println("3")
}()
}

答案解析

2 1
defer 关键字后面的函数或者方法想要执行必须先注册,return 之后的 defer 是不能注册的, 也就不能执行后面的函数或方法。

Go每日一题 (切片)

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

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]

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

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

但是为什么对 s2 赋值后影响的却是 s1 的第三个元素呢?
这是因为切片 s2 是从数组的第二个元素开始,s2 索引为 1 的元素对应的是 s1 索引为 2 的元素。

Go每日一题 (代码块 作用域)

下面选项正确的是?

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每日一题 (切片 len)

以下代码输出什么:

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,因为没有初始化,可以打印证实这一点。

解析: https://polarisxu.studygolang.com/posts/go/action/weekly-question-104/

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

答案解析

答案及解析:B。
基于类型创建的方法必须定义在同一个包内,上面的代码基于 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))
}

答案解析

B.
编译错误: ./main.go:21:19: cannot use Student{} (value of type Student) as type People in variable declaration: Student does not implement People (Speak method has pointer receiver)
值类型 Student 没有实现接口的 Speak() 方法,而是指针类型 *Student 实现改方法。

Go每日一题 (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每日一题 (动态类型)

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

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
type People interface {
Show()
}

type Student struct{}

func (stu *Student) Show() {

}

func main() {

var s *Student
if s == nil {
fmt.Println("s is nil")
} else {
fmt.Println("s is not nil")
}
var p People = s
if p == nil {
fmt.Println("p is nil")
} else {
fmt.Println("p is not nil")
}
}

答案解析

答案及解析: s is nilp is not nil
这道题会不会有点诧异,我们分配给变量 p 的值明明是 nil,然而 p 却不是 nil。
记住一点,当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。
上面的代码,给变量 p 赋值之后,p 的动态值是 nil,但是动态类型却是 *Student,是一个 nil 指针,所以相等条件不成立。

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() 方法,实现字符串的打印。

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
./main.go:14:2: cannot assign to struct field m[“foo”].x in map
错误原因:对于类似 X = Y的赋值操作,必须知道 X 的地址,才能够将 Y 的值赋给 X,
但 go 中的 map 的 value 本身是不可寻址的。

  • 使用临时变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Math struct {
x, y int
}

var m = map[string]Math{
"foo": Math{2, 3},
}

func main() {
tmp := m["foo"]
tmp.x = 4
m["foo"] = tmp
fmt.Println(m["foo"].x)
}
  • 修改数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
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)
fmt.Printf("%#v", m["foo"]) // %#v 格式化输出详细信息
}

Go每日一题 (select死锁)

下面代码输出什么?

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

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
func main() {
v := []int{1, 2, 3}
for i := range v {
v = append(v, i)
}
}

答案解析

答案及解析:不会出现死循环,能正常结束

循环次数在循环开始前就已经确定,循环内改变切片的长度,不影响循环次数

Go每日一题 (切片 协程)

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

1
2
3
4
5
6
7
8
9
10
11
12
func main() {

var m = [...]int{1, 2, 3}

for k, v := range m {
go func() {
fmt.Println(k, v)
}()
}

time.Sleep(time.Second * 3)
}

答案解析

2 3
2 3
2 3

for range 使用短变量声明 (:=) 的形式迭代变量,需要注意的是,变量 k、v 在每次循环体中都会被重用,而不是重新声明。

各个 goroutine 中输出的 i、v 值都是 for range 循环结束后的 i、v 最终值,而不是各个 goroutine 启动时的 k, v值。可以理解为闭包引用,使用的是上下文环境的值。两种可行的 fix 方法:

  • 使用函数传递
1
2
3
4
5
for k, v := range m {
go func(k, v int) {
fmt.Println(k, v)
}(k, v)
}
  • 使用临时变量保留当前值
1
2
3
4
5
6
7
for k, v := range m {
k := k // 这里的 := 会重新声明变量,而不是重用
v := v
go func() {
fmt.Println(k, v)
}()
}

Go每日一题 (defer)

下面这段代码输出什么?

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

defer的7个隐性必备知识点。

  1. defer的执行顺序
  2. deferreturn谁先谁后
  3. 函数的返回值初始化与defer间接影响
  4. 有名函数返回值遇见defer情况
  5. defer遇见panic
  6. defer中包含panic
  7. defer下的函数参数包含子函数
  • Golang中的Defer必掌握的7知识点

  • 结论:

    1. 多个defer出现的时候,它是一个“栈”的关系,也就是先进后出
    1. return之后的语句先执行,defer后的语句后执行
    1. 只要声明函数的返回值变量名称,就会在函数初始化时候为之赋值为0,而且在函数体作用域可见
    1. return,再defer,所以在执行完return之后,还要再执行defer里的语句,依然可以修改本应该返回的结果。
    1. 遇到panic时,遍历本协程的defer链表,并执行defer。在执行defer过程中:遇到recover则停止panic,返回recover处继续往下执行。 如果没有遇到recover,遍历完本协程的defer链表后,向stderr抛出panic信息。 defer 最大的功能是 panic 后依然有效
    1. panic仅有最后一个可以被revover捕获。

Go每日一题 (数组 循环)

下面这段代码输出什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 循环代码是这样的:

1
2
3
4
5
6
7
for i, v := range b {
if i == 0 {
a[1] = 12
a[2] = 13
}
r[i] = v
}

Go每日一题 (可变函数 切片 make 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每日一题 (短赋值 循环)

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

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

  • 可行的解决办法如下:
1
2
3
for i := range s1 {
s2[i] = &s1[i]
}

Powered by Hexo and Hexo-theme-hiker

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

访客数 : | 访问量 :