Golang 每日一题 202301篇

书读百遍其义自见, 多看多问多巩固基础
下面是对https://studygolang.com/每日一题的合集; 如有侵权,请联系我删除!

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每日一题 (线程 协程)

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

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

答案解析

参考答案及解析:AD。

Go每日一题 (for 循环)

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

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

答案解析

参考答案及解析:CD。

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。

知识点:强制类型转化。

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函数定义正确的是:

  • A.
1
2
3
4
type Integer int
func (a Integer) Add(b Integer) Integer {
return a + b
}
  • B.
1
2
3
4
type Integer int
func (a Integer) Add(b *Integer) Integer {
return a + *b
}
  • C.
1
2
3
4
type Integer int
func (a *Integer) Add(b Integer) Integer {
return *a + b
}
  • D.
1
2
3
4
type Integer int
func (a *Integer) Add(b *Integer) Integer {
return *a + *b
}

答案解析

参考答案及解析:AC。

知识点:类型断言、方法集。

go 中有些的变量不可以寻址,指的是不能通过&获得其地址。
所以 func(*A) 只能接收 *A, func(A) 可以接收 A 或者 *A ,通过指针一定能得到变量的值 *A -> A
func(A) 可以接收 *A 和 A,func(*A) 只能接收 A,因为有些变量不可寻址(&获取地址)

Go每日一题 (循环 defer)

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

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)
}
}
  • 扩展题目
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
type Test struct {
name string
}

func (this *Test) Point(){
fmt.Println(this.name)
}

func main() {
ts := []Test{
{"a"},
{"b"},
{"c"},
}

for _, t := range ts {
//fmt.Println(reflect.TypeOf(t))
defer t.Point()
}
}

答案解析

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

解析:这是新手常会犯的错误写法,for range 循环的时候会创建每个元素的副本,而不是元素的引用,所以 m[key] = &val 取的都是变量 val 的地址,所以最后 map 中的所有元素的值都是变量 val 的地址,因为最后 val 被赋值为3,所有输出都是3.

正确的写法:

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

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

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

Go每日一题 (异常 defer)

下面这段代码输出的内容

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

import (
"fmt"
)

func main() {
defer_call()
}

func defer_call() {
defer func() { fmt.Println("打印前") }()
defer func() { fmt.Println("打印中") }()
defer func() { fmt.Println("打印后") }()

panic("触发异常")
}

答案解析

打印后
打印中
打印前
panic: 触发异常

解析:defer 的执行顺序是后进先出。当出现 panic 语句的时候,会先按照 defer 的后进先出的顺序执行,最后才会执行panic。

Go每日一题 (map)

map 的 key 为什么是无序的?

在遍历 map 的时候,我们会发现,输出的 key 是无序的。为什么?

答案解析

map 在扩容后,会发生 key 的搬迁,原来落在同一个 bucket 中的 key,搬迁后,有些 key 就要远走高飞了(bucket 序号加上了 2^B)。
而遍历的过程,就是按顺序遍历 bucket,同时按顺序遍历 bucket 中的 key。搬迁后,key 的位置发生了重大的变化,有些 key 飞上高枝,有些 key 则原地不动。
这样,遍历 map 的结果就不可能按原来的顺序了。

当然,如果我就一个 hard code 的 map,我也不会向 map 进行插入删除的操作,按理说每次遍历这样的 map 都会返回一个固定顺序的 key/value 序列吧。的确是这样,但是 Go 杜绝了这种做法,因为这样会给新手程序员带来误解,以为这是一定会发生的事情,在某些情况下,可能会酿成大错。

当然,Go 做得更绝,当我们在遍历 map 时,并不是固定地从 0 号 bucket 开始遍历,每次都是从一个随机值序号的 bucket 开始遍历,并且是从这个 bucket 的一个随机序号的 cell 开始遍历。这样,即使你是一个写死的 map,仅仅只是遍历它,也不太可能会返回一个固定序列的 key/value 对了。

多说一句,“迭代 map 的结果是无序的”这个特性是从 go 1.0 开始加入的。

Go每日一题 (slice append)

下面两段代码输出什么。

1
2
3
4
5
6
7
8
9
10
11
12
   // 1.
func main() {
s := make([]int, 5)
s = append(s, 1, 2, 3)
fmt.Println(s)
}

// 2.
func main() {
s := make([]int,0)
s = append(s,1,2,3,4)
}

答案解析

代码 1 输出:[0 0 0 0 0 1 2 3]
代码 2 输出:[1 2 3 4]

参考解析:这道题考的是使用 appendslice 添加元素,第一段代码常见的错误是 [1 2 3],需要注意。

Go每日一题 (map 线程安全)

Go 的 map 可以边遍历边删除吗?

答案解析

map 并不是一个线程安全的数据结构。同时读写一个 map 是未定义的行为,如果被检测到,会直接 panic

上面说的是发生在多个协程同时读写同一个 map 的情况下。 如果在同一个协程内边遍历边删除,并不会检测到同时读写,理论上是可以这样做的。但是,遍历的结果就可能不会是相同的了,有可能结果遍历结果集中包含了删除的 key,也有可能不包含,这取决于删除 key 的时间:是在遍历到 key 所在的 bucket 时刻前或者后。

一般而言,这可以通过读写锁来解决:sync.RWMutex

读之前调用 RLock() 函数,读完之后调用 RUnlock() 函数解锁;写之前调用 Lock() 函数,写完之后,调用 Unlock() 解锁。

另外,sync.Map 是线程安全的 map,也可以使用。

Go每日一题 (func 多返回值)

下面这段代码有什么缺陷:

1
2
3
func sum(x, y int)(total int, error) {
return x+y, nil
}

答案解析

第二个返回值没有命名。

在函数有多个返回值时,只要有一个返回值有命名,其他的也必须命名。
如果有多个返回值必须加上括号();如果只有一个返回值且命名也必须加上括号()
这里的第一个返回值有命名 total,第二个没有命名,所以错误。

Go每日一题 (map 取地址)

以下代码是否能编译通过?

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
m := make(map[string]int)

fmt.Println(&m["qcrao"])
}

答案解析

这个问题,相当于问:可以对 map 的元素直接取地址吗?

以上代码编译报错:

./main.go:8:15: invalid operation: cannot take address of m[“qcrao”] (map index expression of type int)

即无法对 mapkeyvalue 进行取址。

如果通过其他 hack 的方式,例如 unsafe.Pointer 等获取到了 key 或 value 的地址,也不能长期持有,因为一旦发生扩容,key 和 value 的位置就会改变,之前保存的地址也就失效了。

Go每日一题 (map 相等判断)

如何确认两个 map 是否相等?

答案解析

map 深度相等的条件:

  • 都为 nil
  • 非空、长度相等,指向同一个 map 实体对象
  • 相应的 key 指向的 value “深度”相等

直接将使用 map1 == map2 是错误的。这种写法只能比较 map 是否为 nil。

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

import "fmt"

func main() {
var m map[string]int
var n map[string]int

fmt.Println(m == nil)
fmt.Println(n == nil)

// 不能通过编译
//fmt.Println(m == n)
}

输出结果:

true
true

因此只能是遍历 map 的每个元素,比较元素是否都是深度相等。

Go每日一题 (struct)

空 struct{} 占多少空间?有什么用途?

答案解析

使用空结构体 struct{} 可以节省内存,一般作为占位符使用,表明这里并不需要一个值。

1
fmt.Println(unsafe.Sizeof(struct{}{})) // 0

比如使用 map 表示集合时,只关注 key,value 可以使用 struct{} 作为占位符。如果使用其他类型作为占位符,例如 int,bool,不仅浪费了内存,而且容易引起歧义。

1
2
3
4
5
6
7
8
9
10
11
12
13
type Set map[string]struct{}

func main() {
set := make(Set)

for _, item := range []string{"A", "A", "B", "C"} {
set[item] = struct{}{}
}
fmt.Println(len(set)) // 3
if _, ok := set["A"]; ok {
fmt.Println("A exists") // A exists
}
}

再比如,使用信道(channel)控制并发时,我们只是需要一个信号,但并不需要传递值,这个时候,也可以使用 struct{} 代替。

1
2
3
4
5
6
7
8
9
func main() {
ch := make(chan struct{}, 1)
go func() {
<-ch
// do something
}()
ch <- struct{}{}
// ...
}

再比如,声明只包含方法的结构体。

1
2
3
4
5
6
7
8
9
type Lamp struct{}

func (l Lamp) On() {
println("On")

}
func (l Lamp) Off() {
println("Off")
}

Go每日一题 (init)

init() 函数是什么时候执行的?

答案解析

init() 函数是 Go 程序初始化的一部分。
Go 程序初始化先于 main 函数,由 runtime 初始化每个导入的包,初始化顺序不是按照从上到下的导入顺序,而是按照解析的依赖关系,没有依赖的包最先初始化。

每个包首先初始化包作用域的常量和变量(常量优先于变量),然后执行包的 init() 函数。
同一个包,甚至是同一个源文件可以有多个 init() 函数。init() 函数没有入参和返回值,不能被其他函数调用,同一个包内多个 init() 函数的执行顺序不作保证。

一句话总结: import –> const –> var –> init() –> main()

  • 示例:
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 init() {
fmt.Println("init1:", a)
}

func init() {
fmt.Println("init2:", a)
}

var a = 10
const b = 100

func main() {
fmt.Println("main:", a)
}
// 执行结果
// init1: 10
// init2: 10
// main: 10

Go每日一题 (new make)

new()make() 的区别

答案解析

new(T)make(T,args) 是 Go 语言内建函数,用来分配内存,但适用的类型不同。

new(T) 会为 T 类型的新值分配已置零的内存空间,并返回地址(指针),即类型为 *T 的值。
换句话说就是,返回一个指针,该指针指向新分配的、类型为 T 的零值。
new() 适用于值类型,如数组、结构体等。

make(T,args) 返回初始化之后的 T 类型的值,这个值并不是 T 类型的零值,也不是指针 *T,是经过初始化之后的 T 的引用。
make() 只适用于 slice、map 和 channel。

Go每日一题 (new make)

下面这段代码能否通过编译,不能的话原因是什么;如果通过,输出什么。

1
2
3
4
5
func main() {
list := new([]int)
list = append(list, 1)
fmt.Println(list)
}

答案解析

不能通过编译,new([]int) 之后的 list 是一个 *[]int 类型的指针,不能对指针执行 append 操作。可以使用 make() 初始化之后再用。
同样的,map 和 channel 建议使用 make() 或字面量的方式初始化,不要用 new()

Go每日一题 (slice append)

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

1
2
3
4
5
6
func main() {
s1 := []int{1, 2, 3}
s2 := []int{4, 5}
s1 = append(s1, s2)
fmt.Println(s1)
}

答案解析

不能通过编译。
append() 的第二个参数不能直接使用 slice,需使用 … 操作符,
将一个切片追加到另一个切片上:append(s1,s2…)。或者直接跟上元素,形如:append(s1,1,2,3)。

Go每日一题 (bit)

以下代码输出什么?

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

import (
"fmt"
)

func main() {
var a, b float64 = 1.0, 4.0
fmt.Println(a | b)
}
  • A:5
  • B:+Inf
  • C:panic
  • D:不能编译

答案解析

正确答案:D

| 操作是按位或操作符,它的操作数只能是整数,而上面这道题的操作数是 float64,因此编译不通过。

Go每日一题 (接口 内存分配)

Go 1.15 中 var i interface{} = a 会有额外堆内存分配吗?

具体代码是:

1
2
3
var a  int = 3
// 以下有额外内存分配吗?
var i interface{} = a

答案解析

在 Go 中,接口被实现为一对指针

1
2
3
4
type iface struct {
tab *itab
data unsafe.Pointer
}

其中 tab 是指向类型信息的指针;data 是指向值的指针。因此,一般来说接口意味着必须在堆中动态分配该值。

然而,Go 1.15 发行说明在 runtime 部分中提到了一个有趣的改进:

Converting a small integer value into an interface value no longer causes allocation.

意思是说,将小整数转换为接口值不再需要进行内存分配小整数是指 0 到 255 之间的数。

Go每日一题 (常量 len 位移运算)

以下程序输出什么?

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

const s = "Go101.org"

// len(s) == 9
// 1 << 9 == 512
// 512 / 128 == 4

var a byte = 1 << len(s) / 128
var b byte = 1 << len(s[:]) / 128

func main() {
println(a, b)
}
  • A: 0 0
  • B: 0 4
  • C: 4 0
  • D: 4 4

答案解析

4 0(即选 C)

len 是一个内置函数。在官方标准库文档关于 len 函数有这么一句:

For some arguments, such as a string literal or a simple array expression, the result can be a constant. See the Go language specification’s “Length and capacity” section for details.

明确支持,当参数是字符串字面量和简单 array 表达式,len 函数返回值是常量,这很重要。

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
29
30
31
32
33
34
35
36
37
38
39
40
41
package main

import "fmt"

func main() {
sn1 := struct {
age int
name string
}{
age: 11, name: "qq",
}

sn2 := struct {
age int
name string
}{
age: 11, name: "qq",
}

if sn1 == sn2 {
fmt.Println("sn1 == sn2")
}

sm1 := struct {
age int
m map[string]string
}{
age: 11, m: map[string]string{"a": "1"},
}

sm2 := struct {
age int
m map[string]string
}{
age: 11, m: map[string]string{"a": "1"},
}

if sm1 == sm2 {
fmt.Println("sm1 == sm2")
}
}

答案解析

编译不通过。
./main.go:39:5: invalid operation: sm1 == sm2 (struct containing map[string]string cannot be compared)

只有相同类型的结构体才可以比较,结构体是否相同不但与属性类型个数有关,还与属性顺序相关。

Go每日一题 (常量)

下面代码有什么问题?

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

const cl = 100

var bl = 123

func main() {
println(&bl, bl)
println(&cl, cl)
}

答案解析

./main.go:9:11: invalid operation: cannot take address of cl (untyped int constant 100)

常量不同于变量的在运行期分配内存,常量通常会被编译器在预处理阶段直接展开,作为指令数据使用,

在golang中,常量是无法取出地址的,因为字面量符号并没有地址而言。

Go每日一题 (slice 初始化 append)

下面这段代码能否通过编译,不能的话原因是什么;如果通过,输出什么。

1
2
3
4
5
func main() {
list := new([]int)
list = append(list, 1)
fmt.Println(list)
}

答案解析

不能通过编译
new([]int) 之后的 list 是一个 *[]int 类型的指针,不能对指针执行 append 操作。可以使用 make() 初始化之后再用。
同样的,map 和 channel 建议使用 make() 或字面量的方式初始化,不要用 new() 。

Go每日一题 (slice append)

写出程序运行的结果:

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

import (
"fmt"
)

func main(){
s := make([]int, 10)
s = append(s, 1, 2, 3)
fmt.Println(s)
}

答案解析

[0 0 0 0 0 0 0 0 0 0 1 2 3]
切片追加, make 初始化均为 0

Go每日一题 (变量声明)

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

1
2
3
4
5
6
7
8
var(
size := 1024
max_size = size*2
)

func main() {
fmt.Println(size,max_size)
}

答案解析

不能通过编译。
这道题的主要知识点是变量声明的简短模式,形如:x := 100. 但这种声明方式有限制:

  • 必须使用显示初始化;
  • 不能提供数据类型,编译器会自动推导;
  • 只能在函数内部使用简短模式;

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

通过指针变量 p 访问其成员变量 name,有哪几种方式?(多选)

  • A. p.name
  • B. (&p).name
  • C. (*p).name
  • D. p->name

答案解析

答案:AC
解析:& 取址运算符,* 指针解引用。

Go每日一题 (类型别名)

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

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

import "fmt"

type MyInt1 int
type MyInt2 = int

func main() {
var i int =0
var i1 MyInt1 = i
var i2 MyInt2 = i
fmt.Println(i1,i2)
}

答案解析

编译不通过,cannot use i (type int) as type MyInt1 in assignment

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

答案解析

参考答案及解析:A。

./main.go:8:10: invalid operation: a == b (mismatched types [2]int and [3]int)

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

Powered by Hexo and Hexo-theme-hiker

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

访客数 : | 访问量 :