开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第30天,点击查看活动详情
今天来学习下Go常见的习题问题(七),也是面试中可能会遇到的,让我们来一起学习吧~
赋值与声明
观察下列代码中,x已经声明,y没有声明,判断每条语句是否成立?
- x, _ := f()
- x, _ = f()
- x, y := f()
- x, y = f()
参考答案:第一个是错误的,因为x已经声明过,不能使用:=进行赋值,第二个是正确的,第三个也是正确的,因为在多个变量进行赋值的时候,:=左边的变量无论是否进行声明都可以进行赋值,第四个是错误的,变量y没有进行声明不能进行赋值操作
defer与return
观察下列代码,输出结果是什么?
func increaseA() int {
var i int
defer func() {
i++
}()
return i
}
func increaseB() (r int) {
defer func() {
r++
}()
return r
}
func main() {
fmt.Println(increaseA())
fmt.Println(increaseB())
}
参考答案:输出0 1,这里先引入下defer和return的基本概念,便于后续进行解释
- go语言中的return操作不是原子操作,而是被分解成两个步骤,第一个是赋值返回值,第二个才将值return,而defer是插在最后一个return之前执行,因此可以在defer语句中修改返回值。
- 多个defer语句的执行顺序,是类似栈的执行顺序先进后出
- return负责将结果写入返回值中,接着执行defer进行一些收尾操作,最后将携带当前的返回值退出
1. 返回值 = xxx
2. 调用 defer 函数
3. 调用 return
那么在上述代码中,由于increaseA()方法是匿名返回值的方法,所以再遇到return的时候,会将结果赋值给一个临时创建的变量(rep = r),此时defer修改的是变量r的值,而不是rep的值。那么在increaseB()方法是命名返回值函数,由于返回值在方法的时候就已经定义好了,所以就不会再创建临时变量rep,那么defer的操作也会随之生效直接返回
接着再看下面四个函数,各自的返回结果是什么?
func f1() (r int) {
defer func() {
r++
}()
return 0
}
func f2() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}
func f3() (r int) {
defer func(r int) {
r = r + 5
}(r)
return 1
}
func f4() (r int) {
t := 5
defer func() {
r = t + 5
}()
return t
}
func main(){
fmt.Println(f1())
fmt.Println(f2())
fmt.Println(f3())
fmt.Println(f4())
}
参考答案:1 5 1 10
- 函数一:就如同第一个例子一样,由于是命名返回值函数,所以在defer中修改的值会相应的跟着变换,所以返回的r也就会加一
- 函数二:因为返回值变量是r,而defer中修改的是变量t,所以不会改变r的值,那么在执行
return t语句时候,相当于将t赋值给变量r也就是5,之后就不会被改动,所以返回的结果就为5 - 函数三:同样执行到
return 1语句的时候,会给变量r进行赋值操作r = 1,上一步执行defer语句传入的r值为0,而内部虽然修改了变量r但那个是局部变量,不会修改外部的变量r,所以返回的值也只有一开始赋值的1 - 函数四:则是在函数三的基础上,将局部变量换掉,所以在defer中修改的r值就会作用到外部变量中,也就返回的是
r = t + 5,即返回10
结构体与defer
观察下列代码,判断输出结果是什么?
type Person struct {
age int
}
func main() {
person := &Person{28}
// 1.
defer fmt.Println(person.age)
// 2.
defer func(p *Person) {
fmt.Println(p.age)
}(person)
// 3.
defer func() {
fmt.Println(person.age)
}()
person.age = 29
}
参考答案:29 29 28
1.person.age此时为28作为defer的函数参数,会把 28 缓存在栈中,等到最后执行该 defer 语句的时候取出,即输出 28;
2.defer 缓存的是结构体 Person{28} 的地址,最终Person{28}的 age 被重新赋值为 29,所以 defer 语句最后执行的时候,依靠缓存的地址取出的 age 便是 29,即输出 29;
3.闭包引用,外部的person变量age已经改为29,即输出 29;
因为 defer 的执行顺序为先进后出,即 3 2 1,所以输出 29 29 28
在上述代码基础上修改了一处地方,再判断输出结果是什么?
func main() {
person := &Person{28}
// 1.
defer fmt.Println(person.age)
// 2.
defer func(p *Person) {
fmt.Println(p.age)
}(person)
// 3.
defer func() {
fmt.Println(person.age)
}()
person = &Person{29}
}
参考答案:29 28 28
这里第一处和之前的原理一样就不做过多解释,主要看第二处的代码,第二处defer缓存的是结构体 Person{28} 的地址,这个地址保存的结构体内容没有改变还是28,所以最后取出的值仍是28,而第三处的闭包引用,因为外部的person值已经被改变,指向的是结构体 Person{29} 的地址,所以输出的就是29
总结
今天浅谈了Go的习题(七),主要介绍了GO面试中会出现的问题,接下来会继续分享其他的习题的相关知识,对于一个刚入门的我来说,还有许多地方需要学习,有错误的地方欢迎大家指出,共同进步!!