以下10个问题都是非常常见的,很容易犯错。其中涉及到一些golang的知识点,在此也记录一下
1. defer的理解
下面内容输出什么
1 |
|
答案是:1
2
3
4答应后
答应中
答应前
触发异常
解释:
考察对defer的理解,defer函数属延迟执行,延迟到调用者函数执行 return 命令前被执行。多个defer之间按LIFO先进后出顺序执行。
需要注意的是,函数的return value 不是原子操作.而是在编译器中分解为两部分:返回值赋值 和 return 。而defer刚好被插入到末尾的return前执行。故可以在derfer函数中修改返回值
1 | package main |
解释:
利用defer的”先进后执行”的特性,所以score = source*2会先执行,由于defer执行的函数使用score不是参数,而是外部变量,是会受外部修改影响的。
2. for遍历的理解
下面代码会输出什么
1 | package main |
答案:
1 | key=zhou,value=&{wang,22} |
解释:
由于for遍历时,stu是一个变量,每一次遍历都是这一个变量,只是每一趟遍历都拷贝了一遍struct的值给stu,而每一次遍历取&stu都是同一个变量的地址,所以m接收到的值是一样的地址。
当最后一次遍历,会把stu的值修改成{wang,22},所以m中所有的值都会是{wang,22}
如何修正?
1 | for i, _ := range stus { |
3. 匿名函数的参数问题
下面代码会输出什么
1 | package main |
答案:
1 | 第一个遍历都会输出10,第二个遍历会输出:1,2,3,4,5,6,7,8,9,10 |
解释:
由于第一个遍历里面的go func是没有参数的,i是外部变量,在主协程中的for i => 10很快就执行完了,所以go func输出都是遍历完之后的i=10
而第二个遍历中的i做为参数传入go func中,所以输出的值是顺序的
4. golang有组合但没有继承
下面代码输出什么
1 | package main |
答案
1 | showA |
解释:
Teacher组合了People,当调用t.ShowA()的时候,事实上是把People升级为Teacher的方法,但是实际上调用者是People,所以ShowA()调用的ShowB仍然属于People,故输出showB,而并非Teacher的showB()方法输出的teacher showB
另外,如果想要调用teacher的showB(),必须使用t.showB(),注意,调用者一定是Teacher。如果想要在外部调用People的showB,那么可以指定People这个组合成员
1 | t.People.showB() |
5. select的随机性
下面代码会触发异常吗?
1 | func main() { |
答案: 可能会,是随机事件
解释:
单个chan如果无缓冲时,将会阻塞。但结合 select可以在多个chan间等待执行。有三点原则:
- select 中只要有一个case能return,则立刻执行。
- 当如果同一时间有多个case均能return则伪随机方式抽取任意一个执行。
- 如果没有一个case能return则可以执行”default”块。
此考题中的两个case中的两个chan均能return,则会随机执行某个case块。故在执行程序时,有可能执行第二个case,触发异常。
6. defer的先进后执行的特性(栈)
下面代码输出什么?
1 | package main |
答案:1
2
3
410 1 2 3
20 0 2 2
2 0 2 2
1 1 2 3
解释:
在解题前需要明确两个概念:
- defer是在函数末尾的return前执行,先进后执行,具体见问题1。
- 函数调用时 int 参数发生值拷贝。
1 | 1. 首先,当执行第3行时,defer calc("1", 1, calc("10", 1, 2))应该先入栈,第三个参数是函数调用calc("10", a, b),先执行 |
7. 切片的初始化
下面代码输出什么?
1 | func main() { |
答案:1
[0,0,0,0,0,1,2,3]
解释:
由于s := make([]int, 5),会默认创建一个匿名的数组[0,0,0,0,0],当append时,由于数组只有5个元素,不够空间,则s = append(s,1,2,3)时,会重新创建一个数组,容量是原来的2倍,就是[0,0,0,0,0,0,0,0,0,0],然后把原来的匿名数组[0,0,0,0,0]拷贝过来,随后把1,2,3填入,最终:[0,0,0,0,0,1,2,3]
8. 注意map的并发访问的问题
下面代码有什么问题?
1 | type UserAges struct { |
答案:
1 | 在执行Get方法的时候可能会panic |
解释:
虽然有使用sync.Mutex做写锁,但是map是并发读写不安全的。map属于引用类型,并发读写时多个协程见是通过指针访问同一个地址,即访问共享变量,此时同时读写资源存在竞争关系。会报错误信息:“fatal error: concurrent map read and map write”。
可以在在线运行中执行,复现该问题。那么如何改善呢? 当然Go1.9新版本中将提供并发安全的map。首先需要了解两种锁的不同:
sync.Mutex互斥锁
sync.RWMutex读写锁,基于互斥锁的实现,可以加多个读锁或者一个写锁。
利用读写锁可实现对map的安全访问,利用RWutex进行读锁。
参考改进版本:
1 | type UserAges struct { |
9. 关于有缓冲的chan和无缓冲的chan的区别
下面代码会有什么问题?
1 | func (set *threadSafeSet) Iter() <-chan interface{} { |
答案:内部迭代出现阻塞。默认初始化时无缓冲区,需要等待接收者读取后才能继续写入。
解释:
1 | chan在使用make初始化时可附带一个可选参数来设置缓冲区。 |
值得注意的是,make(chan int, 1) 与 make(chan int) 是有区别的
1 | 无缓冲的 不仅仅是只能向 ch 通道放 一个值 而是一直要有人接收,那么ch <- elem才会继续下去,要不然就一直阻塞着,也就是说有接收者才去放,没有接收者就阻塞。 |
再举个例子:1
2
3
4
5
6
7
8
9package main
import "fmt"
fun main() {
a := make(chan int) // 一个无缓冲区
a <- 1
fmt.Println("hello", <-a)
}
此代码会报异常:fatal error: all goroutines are asleep - deadlock!
这是由于在主协程中,检到有无缓冲channel在被写入,但此前不存有人接受(即使后面有fmt.Println(“hello”, <-a)),所以出现阻塞,go运行时判断为deadlock
如果代码改为:1
2
3
4
5
6
7
8
9
10
11
12
13
14package main
import "fmt"
fun main() {
a := make(chan int)
go func() {
a <- 1
}()
go func() {
fmt.Println(<-a)
}()
fmt.Println("hello")
}
此时有另一个协程负责接受,代码就正常了
10. 实现接口的问题
下面代码能编译通过吗?
1 | package main |
答案:不能, 值类型 Student{} 未实现接口People的方法,不能定义为 People类型
解释:
为什么没有实现接口People呢?
func (stu Stduent) Speak(think string) (talk string) 是表示结构类型Student的指针有提供该方法,但该方法并不属于结构类型Student的方法。因为struct是值类型。
修改方法:
定义为指针 go var peo People = &Stduent{}
方法定义在值类型上,指针类型本身是包含值类型的方法。 go func (stu Stduent) Speak(think string) (talk string) { //... }