TalkGo Night项目Golang面试题深度解析(二)
前言
本文将对TalkGo Night项目中精选的Golang面试题进行深入解析,这些题目涵盖了Go语言中容易混淆和出错的知识点,包括类型断言、函数返回值、defer机制、切片操作、结构体比较等多个方面。通过详细解析这些题目,帮助读者深入理解Go语言的特性。
1. 类型断言与接口类型检查
func main() {
i := GetValue()
switch i.(type) {
case int:
println("int")
case string:
println("string")
case interface{}:
println("interface")
default:
println("unknown")
}
}
func GetValue() int {
return 1
}
解析: 这个题目考察的是Go语言的类型断言机制。在Go中,.(type)
语法只能用于接口类型(interface{})的变量上进行类型判断。题目中GetValue()
返回的是具体类型int
,而不是接口类型,因此编译会失败。
正确做法: 如果需要使用类型断言,应该先将返回值转换为接口类型:
var i interface{} = GetValue()
switch i.(type) {
// ...
}
2. 函数返回值的命名规则
func funcMui(x,y int)(sum int,error){
return x+y,nil
}
解析: 这道题考察函数返回值的命名规则。在Go语言中:
- 如果函数有多个返回值,当其中一个返回值有命名时,其他返回值也必须命名
- 多个返回值必须用括号括起来
- 即使只有一个命名返回值,也需要用括号括起来
正确写法:
func funcMui(x, y int) (sum int, err error) {
return x + y, nil
}
3. defer与函数返回值的关系
func DeferFunc1(i int) (t int) {
t = i
defer func() {
t += 3
}()
return t
}
func DeferFunc2(i int) int {
t := i
defer func() {
t += 3
}()
return t
}
func DeferFunc3(i int) (t int) {
defer func() {
t += i
}()
return 2
}
解析: 这道题考察defer语句对函数返回值的影响,需要理解几个关键点:
- 命名返回值的作用域是整个函数
- defer语句在return之后执行,但在函数返回前执行
- 对于命名返回值,defer可以修改返回值
- 对于非命名返回值,return的值会被复制到一个临时变量,defer修改原变量不影响返回值
执行结果:
- DeferFunc1(1) 返回4(命名返回值t被defer修改)
- DeferFunc2(1) 返回1(非命名返回值不受defer影响)
- DeferFunc3(1) 返回3(return 2后,defer将t += 1)
4. new与make的区别
func main() {
list := new([]int)
list = append(list, 1)
fmt.Println(list)
}
解析: 这道题考察new
和make
的区别:
new
只分配内存并返回指针,不初始化内存make
用于slice、map和channel的初始化,返回已初始化的对象new([]int)
返回的是指向nil slice的指针,不能直接用于append
正确做法:
list := make([]int, 0)
list = append(list, 1)
5. 切片追加的正确用法
func main() {
s1 := []int{1, 2, 3}
s2 := []int{4, 5}
s1 = append(s1, s2)
fmt.Println(s1)
}
解析: 这道题考察切片的append操作。append一个切片到另一个切片时,需要使用...
展开操作符。
正确写法:
s1 = append(s1, s2...)
6. 结构体的比较规则
sn1 := struct {
age int
name string
}{age: 11, name: "qq"}
sm1 := struct {
age int
m map[string]string
}{age: 11, m: map[string]string{"a": "1"}}
解析: 这道题考察结构体的比较规则:
- 只有相同类型的结构体才能比较(字段名、类型、顺序都必须相同)
- 结构体中不能包含不可比较的类型(如map、slice、function)
- 可以使用
reflect.DeepEqual
进行深度比较
关键点:
- sn1和相同类型的结构体可以比较
- sm1包含map字段,不能直接比较
7. 接口与nil的判断
func Foo(x interface{}) {
if x == nil {
fmt.Println("empty interface")
return
}
fmt.Println("non-empty interface")
}
func main() {
var x *int = nil
Foo(x)
}
解析: 这道题考察接口与nil的关系。接口变量包含类型和值两部分,只有当两者都为nil时,接口才等于nil。
输出结果: non-empty interface
,因为x是*int
类型的nil值,接口变量包含类型信息。
8. 函数返回值的类型一致性
func GetValue(m map[int]string, id int) (string, bool) {
if _, exist := m[id]; exist {
return "存在数据", true
}
return nil, false
}
解析: 这道题考察函数返回值的类型一致性。nil不能直接作为string类型的返回值,因为nil只能用于特定类型(interface、pointer、map、slice等)。
正确写法:
return "", false
9. iota常量生成器的使用
const (
x = iota
y
z = "zz"
k
p = iota
)
解析: 这道题考察iota的用法:
- iota在const声明块中从0开始递增
- 每次const关键字出现时重置为0
- 省略值会沿用上一个表达式,但iota仍然递增
- 显式赋值会中断iota递增,但iota计数器继续增加
输出结果: 0 1 zz zz 4
总结
本文通过9个典型面试题的解析,深入探讨了Go语言中的多个重要概念。理解这些知识点对于编写正确、高效的Go代码至关重要。建议读者在理解这些题目后,可以自己尝试编写类似的测试代码,加深对Go语言特性的理解。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考