GO经典面试真题详解
本文所有内容都是来自于面试中被问到频率较多的问题,尤其是
slice
部分,就是面试官口中的:“你在使用切片时遇到过哪些坑呢?”,所以特整理这些内容,希望对大家工作和面试都能有帮助
Slice
slice 真正存储数据的地方,是一个数组。slice 的结构中存储的是指向所引用的数组指针地址。
同一数组多次append
func main() {
x := make([]int, 0, 10)
x = append(x, 1, 2, 3)
y := append(x, 4)
z := append(x, 5)
fmt.Println(x, len(x), cap(x))
fmt.Println(y, len(y), cap(y))
fmt.Println(z, len(z), cap(z))
}
那么这段代码输出多少呢?
[1 2 3] 3 10
[1 2 3 5] 4 10
[1 2 3 5] 4 10
解析:
在使用 append 向 slice 追加元素时,若 slice 空间不足则会发生扩容,扩容会重新分配一块更大的内存。若不发生扩容则操作依然是同一个底层数组。
疑问点❓
若操作的是同一个底层数组,那x为什么未被改变?x = append(x, 1, 2, 3)
,此时索引位0 值为1,索引位1 值为2,索引位2 值为3
y := append(x, 4)
,y基于x append,此时索引位3 值为4
z := append(x, 5)
,z基于x append,此时索引位3 值被覆盖成了5
打印数据时将从底层数组中找到对应长度的数据
由于x的长度为3,所以打印结果为1 2 3
y、z长度为4,但由于z append时将索引位3的值覆盖成了5,所以打印y、z结果为1 2 3 5
函数中的扩容
func append1(nums []int, a int) {
nums = append(nums, a)
}
func main() {
nums := []int{1, 2, 3, 4, 5}
append1(nums, 6)
fmt.Println(nums)
}
那么这段代码输出多少呢?
[1 2 3 4 5]
解析:
当切片存储容量不足时,则会把当前切片进行扩容并产生新的切片。新的切片具有新的地址,但是go是按值传递,所以新的切片地址只是局部变量,是不能够随着函数返回的。
若切片容量足够,切片作为函数传参扩容后返回的还是原来的切片内容。因为决定切片的还有len
。
闭包函数扩容
var f = func(s []int) {
s = append(s, 4, 5)
fmt.Println(s)
}
func main() {
nums := make([]int, 0, 3)
fmt.Println(nums, len(nums), cap(nums))
f(nums)
fmt.Println(nums, len(nums), cap(nums))
fmt.Println(nums[:3])
}
那么这段代码输出多少呢?
[] 0 3
[4 5]
[] 0 3
[4 5 0]
解析:
在 Go 语言中,只有值传递
要记住一个关键点:如果传过去的值是指向内存空间的地址,是可以对这块内存空间做修改的。反之,你也改不了
实质上在调用f(nums)
函数时,实际上修改了底层所指向的数组。
疑问点❓
为何fmt.Println(nums)
与fmt.Println(nums[:5])
打印的结果差别这么大?
- 在调用函数
f
时修改了底层数组,但未修改nums的长度(len(nums)
依然是0),相对的fmt.Println(nums)
因为该切片 len 值为 0,没有指定最大索引值,high 则取 len 值,导致输出结果为空。 nums[:5]
截取表达式s[low : high]
中的 high,最大的取值范围对应着切片的容量,不是单纯的长度。因此调用fmt.Println(nums[:5])
时可以输出容量范围内的值,不会出现越界。
截取
func main() {
nums := []int{1, 2, 3, 4, 5}
b := nums[2:4]
b[0] = 10
fmt.Println(nums)
fmt.Println(b)
}
那么这段代码输出多少呢?
[1 2 10 4 5]
[10 4]
解析:
GO 中对切片进行切割, 并不会切一个新的数组出来, 而是仍然使用原数组, 只是修改下数组的首地址和长度。
所以go 切片切割仍然使用的是原数组,所以改变b时也改变了a,而b索引位0 指向的是a 索引位2,所以a最终结果是1 2 10 4 5。
函数操作
func set(nums []int, v int) {
nums[3] = v
}
func main() {
nums := []int{1, 2, 3, 4, 5}
set(nums, 6)
fmt.Println(nums)
}
那么这段代码输出多少呢?
[1 2 3 6 5]
解析:
如果传过去的值是指向内存空间的地址,那么是可以对这块内存空间做修改的。
所以在函数set()
中修改nums索引位3的数据,外部的nums也会被修改。
疑问点❓
为什么 slice 类型可以直接修改源数据的值呢?
其实和输出的原理是一样的,在 Go 语言运行时,传递的也是相应 slice 类型的底层数组的指针,但需要注意,其使用的是指针的副本。严格意义是引用类型,依旧是值传递。
defer与return
想想下面这个输出多少?
func main() { fmt.Println("return:", testD1()) } func testD1() int { defer fmt.Println("defer1") i := 1 defer func() { i++ fmt.Println("defer-i", i) }() fmt.Println(i) return i }
输出结果
1 defer-i 2 defer1 return: 1
这个例子很简单,就是按照
defer
先进后出的执行顺序执行的。那么在想想这个例子呢?
func main() { fmt.Println("return:", testD2()) } func testD2() (i int) { defer fmt.Println("defer1") i = 1 defer func() { i++ fmt.Println("defer-i", i) }() fmt.Println(i) return }
输出结果
1 defer-i 2 defer1 return: 2
为什么第二次
return: 2
返回值变成2呢?
因为:return
语句本身并不是一条原子指令,它会先给返回值赋值,然后再是返回。- 在含
defer
表达式时,函数返回的过程是这样的:先给返回值赋值,然后调用defer
表达式,最后再是返回结果。 - 但对于匿名和有名返回值时,
return
会将返回值先保存起来,对于匿名返回值来说,保存在一个临时对象中,defer
是看不到这个临时对象的;而对于有名返回值来说,就保存在已命名的变量中。
Map
func test1(m map[string]interface{}) {
m["name"] = "张三"
}
func main() {
m := make(map[string]interface{})
m["age"] = 23
m["name"] = "李四"
test1(m)
fmt.Println(m)
}
那么这段代码输出多少呢?
map[age:23 name:张三]
为什么在调用 test1()
后 name
的值就变成了 「张三」了呢?
解释:
func makemap(t *maptype, hint int, h *hmap) *hmap {}
这是创建 map 类型的底层方法,其返回的是 *hmap
类型,是一个指针。
就是说当我们在调用 test1
方法时,其相当于是在传入一个指针参数test1(*hmap)
,从而修改了map中的name值。
总结
以上内容就是今天要分享的面试题了,再次强调的是 slice
部分,就是面试官口中的:“你在使用切片时遇到过哪些坑呢?”
希望这次分享能帮助大家拿到满意的offer!!!
END
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: