内容简介:最近在很多地方看到了考点:defer执行顺序解答:defer 是后进先出。panic 需要等defer 结束后才会向上传递。
最近在很多地方看到了 golang的面试题 ,看到了很多人对Golang的面试题心存恐惧,也是为了复习基础,我把解题的过程总结下来。
面试题
1. 写出下面代码输出内容。
package main import ( "fmt" ) func main() { defer_call() } func defer_call() { defer func() { fmt.Println("打印前") }() defer func() { fmt.Println("打印中") }() defer func() { fmt.Println("打印后") }() panic("触发异常") }
考点:defer执行顺序
解答:defer 是后进先出。panic 需要等defer 结束后才会向上传递。
出现panic恐慌时候,会先按照defer的后入先出的顺序执行,最后才会执行panic。
打印后 打印中 打印前 panic: 触发异常
近期有同学遇到多次执行的时候发现panic的执行顺序不定,那么是不是因为panic与defer没有先后关系呢?我们先来下面的例子:
func main() { defer_call() } func defer_call() { defer func() { if err := recover(); err != nil { fmt.Println("one=", err) } }() defer func() { fmt.Println("打印前") }() defer func() { fmt.Println("打印中") }() defer func() { fmt.Println("打印后") }() panic("触发异常") }
大家再多次执行,看看是否都是输出:
打印后 打印中 打印前 one= 触发异常
那为什么没有加recover()时候,panic执行顺序不定呢?
defer的执行顺序肯定是FILO的,但是没有被recover的panic协程(线程)可能争夺CPU的顺序比defer快,所以造成了这样的情况,也可能是写缓存问题,所以对panic进行recover将其加入到defer队列中。
2. 以下代码有什么问题,说明原因。
type student struct { Name string Age int } func pase_student() { m := make(map[string]*student) stus := []student{ {Name: "zhou", Age: 24}, {Name: "li", Age: 23}, {Name: "wang", Age: 22}, } for _, stu := range stus { m[stu.Name] = &stu } }
考点:foreach
解答:这样的写法初学者经常会遇到的,很危险!
与 Java 的foreach一样,都是使用副本的方式。所以m[stu.Name]=&stu实际上一致指向同一个指针, 最终该指针的值为遍历的最后一个struct的值拷贝。 就像想修改切片元素的属性:
for _, stu := range stus { stu.Age = stu.Age+10 }
也是不可行的。 大家可以试试打印出来:
func pase_student() { m := make(map[string]*student) stus := []student{ {Name: "zhou", Age: 24}, {Name: "li", Age: 23}, {Name: "wang", Age: 22}, } // 错误写法 for _, stu := range stus { m[stu.Name] = &stu } for k,v:=range m{ println(k,"=>",v.Name) } // 正确 for i:=0;i<len(stus);i++ { m[stus[i].Name] = &stus[i] } for k,v:=range m{ println(k,"=>",v.Name) } }
3. 下面的代码会输出什么,并说明原因
func main() { runtime.GOMAXPROCS(1) wg := sync.WaitGroup{} wg.Add(20) for i := 0; i < 10; i++ { go func() { fmt.Println("A: ", i) wg.Done() }() } for i := 0; i < 10; i++ { go func(i int) { fmt.Println("B: ", i) wg.Done() }(i) } wg.Wait() }
考点:go执行的随机性和闭包
解答:谁也不知道执行后打印的顺序是什么样的,所以只能说是随机数字。
但是A:均为输出10,B:从0~9输出(顺序不定)。 第一个go func中i是外部for的一个变量,地址不变化。遍历完成后,最终i=10。 故go func执行时,i的值始终是10。
第二个go func中i是函数参数,与外部for中的i完全是两个变量。 尾部(i)将发生值拷贝,go func内部指向值拷贝地址。
4. 下面代码会输出什么?
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.ShowA() }
考点:go的组合继承
解答:这是Golang的组合模式,可以实现OOP的继承。 被组合的类型People所包含的方法虽然升级成了外部类型Teacher这个组合类型的方法(一定要是匿名字段),但它们的方法(ShowA())调用时接受者并没有发生变化。 此时People类型并不知道自己会被什么类型组合,当然也就无法调用方法时去使用未知的组合者Teacher类型的功能。
showA showB
5. 下面代码会触发异常吗?请详细说明
func main() { runtime.GOMAXPROCS(1) int_chan := make(chan int, 1) string_chan := make(chan string, 1) int_chan <- 1 string_chan <- "hello" select { case value := <-int_chan: fmt.Println(value) case value := <-string_chan: panic(value) } }
考点:select随机性
解答:select会随机选择一个可用通用做收发操作。 所以代码是有肯触发异常,也有可能不会。
单个chan如果无缓冲时,将会阻塞。但结合 select可以在多个chan间等待执行。有三点原则:
- select 中只要有一个case能return,则立刻执行。
- 当如果同一时间有多个case均能return则伪随机方式抽取任意一个执行。
- 如果没有一个case能return则可以执行”default”块。
6. 下面代码输出什么?
func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } func main() { a := 1 b := 2 defer calc("1", a, calc("10", a, b)) a = 0 defer calc("2", a, calc("20", a, b)) b = 1 }
考点:defer执行顺序
解答:
这道题类似第1题 需要注意到defer执行顺序和值传递 index:1肯定是最后执行的,但是index:1的第三个参数是一个函数,所以最先被调用calc(“10”,1,2)==>10,1,2,3 执行index:2时,与之前一样,需要先调用calc(“20”,0,2)==>20,0,2,2 执行到b=1时候开始调用,index:2==>calc(“2”,0,2)==>2,0,2,2 最后执行index:1==>calc(“1”,1,3)==>1,1,3,4
7. 请写出以下输入内容
func main() { s := make([]int, 5) s = append(s, 1, 2, 3) fmt.Println(s) }
考点:make默认值和append
解答:make初始化是由默认值的哦,此处默认值为0
[0 0 0 0 0 1 2 3]
大家试试改为:
s := make([]int, 0) s = append(s, 1, 2, 3) fmt.Println(s)//[1 2 3]
8. 下面的代码有什么问题?
type UserAges struct { ages map[string]int sync.Mutex } func (ua *UserAges) Add(name string, age int) { ua.Lock() defer ua.Unlock() ua.ages[name] = age } func (ua *UserAges) Get(name string) int { if age, ok := ua.ages[name]; ok { return age } return -1 }
考点:map线程安全
解答:可能会出现fatal error: concurrent map read and map write. 修改一下看看效果
func (ua *UserAges) Get(name string) int { ua.Lock() defer ua.Unlock() if age, ok := ua.ages[name]; ok { return age } return -1 }
9. 下面的迭代会有什么问题?
func (set *threadSafeSet) Iter() <-chan interface{} { ch := make(chan interface{}) go func() { set.RLock() for elem := range set.s { ch <- elem } close(ch) set.RUnlock() }() return ch }
考点:chan缓存池
解答:看到这道题,我也在猜想出题者的意图在哪里。 chan?sync.RWMutex?go?chan缓存池?迭代? 所以只能再读一次题目,就从迭代入手看看。 既然是迭代就会要求set.s全部可以遍历一次。但是chan是为缓存的,那就代表这写入一次就会阻塞。 我们把代码恢复为可以运行的方式,看看效果
package main import ( "sync" "fmt" ) //下面的迭代会有什么问题? type threadSafeSet struct { sync.RWMutex s []interface{} } func (set *threadSafeSet) Iter() <-chan interface{} { // ch := make(chan interface{}) // 解除注释看看! ch := make(chan interface{},len(set.s)) go func() { set.RLock() for elem,value := range set.s { ch <- elem println("Iter:",elem,value) } close(ch) set.RUnlock() }() return ch } func main() { th:=threadSafeSet{ s:[]interface{}{"1","2"}, } v:=<-th.Iter() fmt.Sprintf("%s%v","ch",v) }
10. 以下代码能编译过去吗?为什么?
package main import ( "fmt" ) type People interface { Speak(string) string } type Stduent struct{} func (stu *Stduent) Speak(think string) (talk string) { if think == "bitch" { talk = "You are a good boy" } else { talk = "hi" } return } func main() { var peo People = Stduent{} think := "bitch" fmt.Println(peo.Speak(think)) }
考点:golang的方法集
解答:编译不通过! 做错了!?说明你对golang的方法集还有一些疑问。
一句话:golang的方法集仅仅影响接口实现和方法表达式转化,与通过实例或者指针调用方法无关。
11. 以下代码打印出来什么内容,说出为什么。
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") } }
考点:interface内部结构
解答:很经典的题! 这个考点是很多人忽略的interface内部结构。 go中的接口分为两种一种是空的接口类似这样:
var in interface{}
另一种如题目:
type People interface { Show() }
他们的底层结构如下:
type eface struct { //空接口 _type *_type //类型信息 data unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于 c语言 中的void*) } type iface struct { //带有方法的接口 tab *itab //存储type信息还有结构实现方法的集合 data unsafe.Pointer //指向数据的指针(go语言中特殊的指针类型unsafe.Pointer类似于c语言中的void*) } type _type struct { size uintptr //类型大小 ptrdata uintptr //前缀持有所有指针的内存大小 hash uint32 //数据hash值 tflag tflag align uint8 //对齐 fieldalign uint8 //嵌入结构体时的对齐 kind uint8 //kind 有些枚举值kind等于0是无效的 alg *typeAlg //函数指针数组,类型实现的所有方法 gcdata *byte str nameOff ptrToThis typeOff } type itab struct { inter *interfacetype //接口类型 _type *_type //结构类型 link *itab bad int32 inhash int32 fun [1]uintptr //可变大小 方法集合 }
可以看出iface比eface 中间多了一层itab结构。 itab 存储_type信息和[]fun方法集,从上面的结构我们就可得出,因为data指向了nil 并不代表interface 是nil, 所以返回值并不为空,这里的fun(方法集)定义了接口的接收规则,在编译的过程中需要验证是否实现接口 结果:
BBBBBBB
来源: https://my.oschina.net/u/553243/blog/1478739
添加小编微信:grey0805,加入知识分享小分队,别掉队哦!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Python面试经验总结,面试一时爽,一直面试一直爽!
- 算法面试:数组编码面试问题
- 【面试虐菜】—— JAVA面试题(1)
- 如何面试-作为面试官得到的经验
- PHP面试之网络协议面试题
- 如何克服面试紧张心理 ?(面试答题篇Ⅲ)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C++ Primer 中文版(第 4 版)
Stanley B.Lippman、Josée LaJoie、Barbara E.Moo / 李师贤、蒋爱军、梅晓勇、林瑛 / 人民邮电出版社 / 2006 / 99.00元
本书是久负盛名的C++经典教程,其内容是C++大师Stanley B. Lippman丰富的实践经验和C++标准委员会原负责人Josée Lajoie对C++标准深入理解的完美结合,已经帮助全球无数程序员学会了C++。本版对前一版进行了彻底的修订,内容经过了重新组织,更加入了C++ 先驱Barbara E. Moo在C++教学方面的真知灼见。既显著改善了可读性,又充分体现了C++语言的最新进展和当......一起来看看 《C++ Primer 中文版(第 4 版)》 这本书的介绍吧!