前言
最近在为公司出笔试题,准备面试未来应聘的 Golang 工程师。以下为个人精选的几道
先来一波数组
1 2 3 4 5 6 7 8 9 10 |
func main() { var a=[4]int{1,2,3,3} var b=[4]int{1,2,3,4} if a==b { fmt.Println(true) }else { fmt.Println(false) } // 编译会报错吗?结果是什么? } |
1 |
答案:不会,false,这里着重考察数组是否是可以比较的类型,请注意切片不可以比较的 |
1 2 3 4 5 6 7 8 9 10 |
func main() { var a=[5]int{1,2,3,4} var b=[4]int{1,2,3,4} if a==b { fmt.Println(true) }else { fmt.Println(false) } // 编译会报错吗?结果是什么? } |
1 |
答案:编译报错,数组长度不同,导致类型不同,无法比较,因此编译报错 |
1 2 3 4 5 6 7 |
func main() { a := [2]int{1, 2} b := [...]int{1, 2} c := [2]int{1, 3} // 编译会报错吗?结果是什么? fmt.Println(a == b, a == c, b == c) } |
1 |
答案:不会,"true false false" |
1 2 3 4 5 6 |
func main() { a := [...]int{1, 2} b := [3]int{1, 2} // 编译会报错吗?结果是什么? fmt.Println(a==b) } |
1 |
// 答案:编译报错,长度 2 和 3 的数组不是一个类型 |
再来一波数组与切片结合的题
1 2 3 4 5 6 7 |
func main() { a := [3]int{10, 20, 30} slice := a[:] // 将数组转换为切片 slice[0] = 40 fmt.Println(a) // 结果是什么? fmt.Println(slice) // 结果是什么? } |
1 2 3 |
结果:a [40 20 30] 结果:slice [40 20 30] 考点:这里主要考察面试者对切片底层引用数组指针的概念是否清晰 |
1 2 3 4 5 6 7 8 |
func main() { a := [3]int{10, 20, 30} slice := a[:] slice = append(slice, 40) slice[0] = 100 fmt.Println(a) // 结果是什么? fmt.Println(slice) // 结果是什么? } |
1 2 3 |
结果: a [10 20 30] 结果: slice [100 20 30 40] 考点:这里主要考察面试者是否知晓当切片扩容时,将开辟另一个数组进行值拷贝,而不再引用之前的数组,因此切片改变值后,不再影响之前的数组 |
defer 机制题
第一部分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main import "fmt" func function(index int, value int) int { fmt.Println(index) return index } func main() { // 请问会输出什么? defer function(1, function(3, 0)) defer function(2, function(4, 0)) } |
1 2 3 4 |
3 4 2 1 |
解析:
这里,有4个函数,他们的index序号分别为1,2,3,4。
那么这4个函数的先后执行顺序是什么呢?这里面有两个defer, 所以defer一共会压栈两次,先进栈1,后进栈2。 那么在压栈function1的时候,需要连同函数地址、函数形参一同进栈,那么为了得到function1的第二个参数的结果,所以就需要先执行function3将第二个参数算出,那么function3就被第一个执行。同理压栈function2,就需要执行function4算出function2第二个参数的值。然后函数结束,先出栈fuction2、再出栈function1.
所以顺序如下:
- defer压栈function1,压栈函数地址、形参1、形参2(调用function3) --> 打印3
- defer压栈function2,压栈函数地址、形参1、形参2(调用function4) --> 打印4
- defer出栈function2, 调用function2 --> 打印2
- defer出栈function1, 调用function1--> 打印1
我们平日总是顺着思想,都认为defer是最后才执行的,其实这是错的,不信你仔细思考下面的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package main import "fmt" func function(index int, value int) int { fmt.Println(index) return index } func main() { defer function(1, function(3, 0)) fmt.Println("我被夹在两个 defer 之间") defer function(2, function(4, 0)) } |
1 2 3 4 5 |
3 我被夹在两个 defer 之间 4 2 1 |
第二部分
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 42 |
package main import "fmt" func DeferFunc1(i int) int { var t = i defer func() { t = t * 10 }() return t } func DeferFunc2(i int) (t int) { t = i defer func() { t = t * 10 }() return t } func DeferFunc3(i int) (result int) { t := i defer func() { t += 3 }() return t } func DeferFunc4() (t int) { defer func(i int) { fmt.Println(i) fmt.Println(t) }(t) t = 1 return 2 } func main() { // 结果是什么? fmt.Println(DeferFunc1(1)) fmt.Println(DeferFunc2(1)) fmt.Println(DeferFunc3(1)) DeferFunc4() } |
1 2 3 4 5 6 |
答案: 1 10 1 0 2 |
解析:
1 2 3 4 5 6 7 |
func DeferFunc1(i int) int { t := i defer func() { t += 3 }() return t } |
- 创建变量t并赋值为1
- 执行return语句,注意这里是将t赋值给返回值,此时返回值为1(这个返回值并不是t)
- 执行defer方法,将t + 3 = 4
- 函数返回返回值1
1 2 3 4 5 6 7 |
func DeferFunc2(i int) (t int) { t = i defer func() { t = t * 10 }() return t } |
- 将返回值t赋值为传入的i,此时t为1
- 执行return语句将t赋值给t(等于啥也没做)
- 执行defer方法,将t + 3 = 4
- 函数返回 4 ,因为t的作用域为整个函数所以修改有效。
1 2 3 4 5 6 7 |
func DeferFunc3(i int) (result int) { t := i defer func() { t += 3 }() return t } |
上面的代码return的时候相当于将t赋值给了result,当defer修改了t的值之后,对result是不会造成影响的。
1 2 3 4 5 6 7 8 |
func DeferFunc4() (t int) { defer func(i int) { fmt.Println(i) fmt.Println(t) }(t) t = 1 return 2 } |
- 初始化返回值 t 为零值 0
- 首先执行 defer 的第一步,赋值 defer 中的 func 入参 t 为 0
- 执行 defer 的第二步,将 defer 压栈
- 将 t 赋值为 1
- 执行 return 语句,将返回值 t 赋值为 2
- 执行defer的第三步,出栈并执行
- 因为在入栈时defer执行的func的入参已经赋值了,此时它作为的是一个形式参数,所以打印为0;相对应的因为最后已经将t的值修改为2,所以再打印一个2
结语
这只能作为部分笔试题,不过如果能将上述笔试题全做对也代表着该开发者基础确实挺扎实的。但不包含理论面试,比如GC、runtime 调度 、逃逸分析、内存对齐、GPM 调度模型等。
有缘看到这篇文章的工程师们,你们答对了多少呢?
© 著作权归作者所有
文章评论(0)