从问题中发现问题, 从提问中提升自己
Go每日一题 (闭包)
以下代码输出什么?
1 | package main |
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 | package main |
答案解析
答案及解析:29 29 28。变量 person 是一个指针变量。
person.age
此时是将 28 当做 defer 函数的参数,会把 28 缓存在栈中,等到最后执行该 defer 语句的时候取出,即输出 28;- defer 缓存的是结构体 Person{28} 的地址,最终 Person{28} 的 age 被重新赋值为 29,所以 defer 语句最后执行的时候,依靠缓存的地址取出的 age 便是 29,即输出 29;
- 很简单,闭包引用,输出 29; 又由于 defer 的执行顺序为先进后出,即 3 2 1,所以输出 29 29 28。
Go每日一题 (结构体 指针)
下面 A、B 两处应该填入什么代码,才能确保顺利打印出结果?
1 | package main |
答案解析
A. &S{"foo"}
B. *f()
或者 f()
f() 函数返回参数是指针类型,所以可以用 & 取结构体的指针;
B 处,如果填 *f(),则 p 是 S 类型;如果填 f(),则 p 是 *S 类型,不过都可以使用 p.m 取得结构体的成员。
Go每日一题 (常量表达式)
以下代码输出什么?
1 | package main |
A:不能编译;B:45;C:45.2;D:45.0
答案解析
常量表达式是指仅包含常量操作数,且是在编译的时候进行计算的。
而常量,在 Go 语言中又可以分为无类型常量和有类型常量,也可以分为字面值常量和具名常量。说人话?!
通过代码看看:
1 | const a = 1 + 2 // a == 3,是无类型常量 |
Go每日一题 (字符串类型)
下面的代码有几处语法问题,各是什么?
1 | package main |
答案解析
2 处有语法问题。
golang 的字符串类型是不能赋值 nil 的,也不能跟 nil 比较。
Go每日一题 (defer)
return 之后的 defer 语句会执行吗,下面这段代码输出什么?
1 | import ( |
答案解析
2 1
defer 关键字后面的函数或者方法想要执行必须先注册,return 之后的 defer 是不能注册的, 也就不能执行后面的函数或方法。
Go每日一题 (切片)
下面这段代码输出什么?为什么?
1 | func main() { |
答案解析
[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 | func main() { |
A. 1 2
B. compilation error
答案解析
Go每日一题 (切片 len)
以下代码输出什么:
1 | package main |
- A:运行时 panic;
- B:32;
- C:编译错误;
- D:0
答案解析
正确答案:B。
注意这里不是定义一个结构体类型,而是定义一个结构体类型指针变量,即 x 是一个指针,指针类型是一个匿名结构体。
很显然,x 的值是 nil,因为没有初始化,可以打印证实这一点。
解析: https://polarisxu.studygolang.com/posts/go/action/weekly-question-104/
Go每日一题 (类型方法)
下面这段代码输出什么?为什么?
1 | func (i int) PrintInt () { |
- A. 1
- B. compilation error
答案解析
答案及解析:B。
基于类型创建的方法必须定义在同一个包内,上面的代码基于 int 类型创建了 PrintInt() 方法,
由于 int 类型和方法 PrintInt() 定义在不同的包内,所以编译出错。解决的办法可以定义一种新的类型:
1 | type Myint int |
Go每日一题 (类型方法)
下面这段代码输出什么?为什么?
1 | type People interface { |
答案解析
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 | const ( |
答案解析
答案及解析:0 1 1 2
iota 是 golang 语言的常量计数器,只能在常量的表达式中使用。
iota 在 const 关键字出现时将被重置为0,const中每新增一行常量声明将使 iota 计数一次。
Go每日一题 (动态类型)
下面这段代码输出什么?为什么?
1 | type People interface { |
答案解析
答案及解析:
s is nil
和p is not nil
这道题会不会有点诧异,我们分配给变量 p 的值明明是 nil,然而 p 却不是 nil。
记住一点,当且仅当动态值和动态类型都为 nil 时,接口类型值才为 nil。
上面的代码,给变量 p 赋值之后,p 的动态值是 nil,但是动态类型却是 *Student,是一个 nil 指针,所以相等条件不成立。
Go每日一题 (iota String方法)
下面这段代码输出什么?
1 | type Direction int |
答案解析
答案及解析:South
根据 iota 的用法推断出 South 的值是 2;另外,如果类型定义了 String() 方法,
当使用fmt.Printf()
、fmt.Print()
和fmt.Println()
会自动使用String()
方法,实现字符串的打印。
Go每日一题 (map不能寻址)
下面代码输出什么?
1 | type Math struct { |
- 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 | type Math struct { |
- 修改数据结构
1 | type Math struct { |
Go每日一题 (select死锁)
下面代码输出什么?
1 | package main |
- A:default
- B:panic
答案解析
答案及解析:B
Go每日一题 (切片不能比较)
下面的代码有什么问题?
1 | func main() { |
答案解析
有两处错误
go 中不同类型是不能比较的,而数组长度是数组类型的一部分,所以 […]int{1} 和 [2]int{1} 是两种不同的类型,不能比较;
切片是不能比较的;
Go每日一题 (数组 循环)
下面这段代码能否正常结束?
1 | func main() { |
答案解析
答案及解析:不会出现死循环,能正常结束
循环次数在循环开始前就已经确定,循环内改变切片的长度,不影响循环次数
Go每日一题 (切片 协程)
下面这段代码输出什么?为什么?
1 | func main() { |
答案解析
2 3
2 3
2 3
for range 使用短变量声明 (:=) 的形式迭代变量,需要注意的是,变量 k、v 在每次循环体中都会被重用,而不是重新声明。
各个 goroutine 中输出的 i、v 值都是 for range 循环结束后的 i、v 最终值,而不是各个 goroutine 启动时的 k, v值。可以理解为闭包引用,使用的是上下文环境的值。两种可行的 fix 方法:
- 使用函数传递
1 | for k, v := range m { |
- 使用临时变量保留当前值
1 | for k, v := range m { |
Go每日一题 (defer)
下面这段代码输出什么?
1 | func f(n int) (r int) { |
答案解析
答案及解析:7
defer
的7个隐性必备知识点。
defer
的执行顺序defer
与return
谁先谁后- 函数的返回值初始化与
defer
间接影响 - 有名函数返回值遇见
defer
情况 defer
遇见panic
defer
中包含panic
defer
下的函数参数包含子函数
结论:
- 多个defer出现的时候,它是一个“栈”的关系,也就是先进后出。
- return之后的语句先执行,defer后的语句后执行
- 只要声明函数的返回值变量名称,就会在函数初始化时候为之赋值为0,而且在函数体作用域可见
- 先
return
,再defer
,所以在执行完return
之后,还要再执行defer
里的语句,依然可以修改本应该返回的结果。
- 先
- 遇到
panic
时,遍历本协程的defer
链表,并执行defer
。在执行defer
过程中:遇到recover
则停止panic
,返回recover
处继续往下执行。 如果没有遇到recover
,遍历完本协程的defer链表后,向stderr抛出panic信息。 defer 最大的功能是 panic 后依然有效
- 遇到
panic
仅有最后一个可以被revover
捕获。
Go每日一题 (数组 循环)
下面这段代码输出什么?
1 | func main() { |
答案解析
r = [1 2 3 4 5]
a = [1 12 13 4 5]
range 表达式是副本参与循环,就是说例子中参与循环的是 a 的副本,
而不是真正的 a。就这个例子来说,假设 b 是 a 的副本,则 range 循环代码是这样的:
1 | for i, v := range b { |
Go每日一题 (可变函数 切片 make append)
下面这段代码输出什么?
1 | func change(s ...int) { |
答案解析
[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 | type Foo struct { |
输出:
{A} {B} {C}
&{A} &{B} &{C}
答案解析
答案及解析:s2 的输出结果错误。
s2 的输出是 &{C} &{C} &{C},在前面题目我们提到过,for range 使用短变量声明(:=)的形式迭代变量时,变量 i、value 在每次循环体中都会被重用,而不是重新声明。
所以 s2 每次填充的都是临时变量 value 的地址,而在最后一次循环中,value 被赋值为{c}。因此,s2 输出的时候显示出了三个 &{c}。
- 可行的解决办法如下:
1 | for i := range s1 { |