错题集
0227
- 下面这段代码输出什么?
1
2
3
4
5
6
7
8func main() {
var i interface{}
if i == nil {
fmt.Println("nil")
return
}
fmt.Println("not nil")
}
nil
解析:当且仅当接口的动态值和动态类型都为 nil 时,接口类型值才为 nil。
指针,函数,interface,slice,channel和map的零值都是nil
空接口赋值之后其值为nil, 但是其本身不是nil,所以打印的时候是nil, 判等的时候并不是nil。
- 下面代码下划线处可以填入哪个选项?
1
2
3
4
5
6
7
8
9func main() {
var s1 []int // nil切片
var s2 = []int{} // 空切片
if __ == nil {
fmt.Println("yes nil")
}else{
fmt.Println("no nil")
}
}
s1 s2 都可以
知识点:nil 切片和空切片。
nil 切片和 nil 相等,一般用来表示一个不存在的切片;
空切片和 nil 不相等,表示一个空的集合。
nil切片和空切片指向的地址不一样。nil空切片引用数组指针地址为0(无指向任何实际地址)
空切片的引用数组指针地址是有的,且固定为一个值。
nil切片和空切片最大的区别在于指向的数组引用地址是不一样的,所有的空切片指向的数组引用地址都是一样的。
通过var a []int创建的切片是一个nil切片
通过var s2 []int = []int{} 等价于 var s2 = []int{},声明并赋值一个空切片
通过b:=make([]int,0)创建的是一个空切片,(底层数组为空,但底层数组指针非空)
- 下面的代码有几处语法问题
1
2
3
4
5
6
7
8
9
10
11package main
import (
"fmt"
)
func main() {
var x string = nil
if x == nil {
x = "default"
}
fmt.Println(x)
}
2
解析:var x string = nil、x == nil两个地方有语法问题。golang 的字符串类型是不能赋值 nil 的,也不能跟 nil 比较。
- 下面代码输出什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19func 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
increaseA() 的返回参数是匿名,increaseB() 是具名。
- 下面代码输出什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22type 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 = &Person{29}
}
29 28 28
- 下面这段代码输出什么?
1
2
3
4func main() {
i := 65
fmt.Println(string(i))
}
A
UTF-8 编码中,十进制数字 65 对应的符号是 A。
- 下面代码段输出什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22type 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22type 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()
}
showA showB
- 切片 a、b、c 的长度和容量分别是多少?
1
2
3
4
5
6
7func main() {
s := [3]int{1, 2, 3}
a := s[:0]
b := s[:2]
c := s[1:2:cap(s)]
}
03 23 12
知识点:数组或切片的截取操作。
截取操作有带 2 个或者 3 个参数,形如:[i:j] 和 [i:j:k],假设截取对象的底层数组长度为 l。
操作符 [i:j] 中,如果 i 省略,默认 0,如果 j 省略,默认底层数组的长度,截取得到的切片长度和容量计算方法是 j-i、l-i。
操作符 [i:j:k],k 主要是用来限制切片的容量,但是不能大于数组的长度 l,截取得到的切片长度和容量计算方法是 j-i、k-i。
- 下面代码输出什么?
1
2
3
4
5func main() {
str := "hello"
str[0] = 'x'
fmt.Println(str)
}
编译错误
常量,Go 语言中的字符串是只读的
- 下面这段代码输出什么?
1
2
3
4
5func main() {
s := make(map[string]int)
delete(s, "h")
fmt.Println(s["h"])
}
0
- 下面代码输出什么?
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
27type A interface {
ShowA() int
}
type B interface {
ShowB() int
}
type Work struct {
i int
}
func (w Work) ShowA() int {
return w.i + 10
}
func (w Work) ShowB() int {
return w.i + 20
}
func main() {
c := Work{3}
var a A = c
var b B = c
fmt.Println(a.ShowB())
fmt.Println(b.ShowA())
}
编译错误
- 下面代码输出什么?
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
26type A interface {
ShowA() int
}
type B interface {
ShowB() int
}
type Work struct {
i int
}
func (w Work) ShowA() int {
return w.i + 10
}
func (w Work) ShowB() int {
return w.i + 20
}
func main() {
var a A = Work{3}
s := a.(Work)
fmt.Println(s.ShowA())
fmt.Println(s.ShowB())
}
13 23
- 下面这段代码输出什么?
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
27type A interface {
ShowA() int
}
type B interface {
ShowB() int
}
type Work struct {
i int
}
func (w Work) ShowA() int {
return w.i + 10
}
func (w Work) ShowB() int {
return w.i + 20
}
func main() {
c := Work{3}
var a A = c
var b B = c
fmt.Println(a.ShowA())
fmt.Println(b.ShowB())
}
13 23
接口。一种类型实现多个接口,结构体 Work 分别实现了接口 A、B,所以接口变量 a、b 调用各自的方法 ShowA() 和 ShowB(),输出 13、23。
关于 cap() 函数的适用类型
channel slice array1、2、3、4 哪些选项有语法错误?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17type S struct {
}
func f(x interface{}) {
}
func g(x *interface{}) {
}
func main() {
s := S{}
p := &s
f(s) //1
g(s) //2
f(p) //3
g(p) //4
}
2 4
函数参数为 interface{} 时可以接收任何类型的参数,包括用户自定义类型等,即使是接收指针类型也用 interface{},而不是使用 *interface{}。
永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。
0228
- 下面这段代码输出什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14func main() {
var a = []int{1, 2, 3, 4, 5}
var r [5]int
for i, v := range a {
if i == 0 {
a[1] = 12
a[2] = 13
}
r[i] = v
}
fmt.Println("r = ", r)
fmt.Println("a = ", a)
}
r = [1 12 13 4 5]
a = [1 12 13 4 5]
这的 a 是一个切片,那切片是怎么实现的呢?切片在 go 的内部结构有一个指向底层数组的指针,当 range 表达式发生复制时,副本的指针依旧指向原底层数组,所以对切片的修改都会反应到底层数组上,所以通过 v 可以获得修改后的数组元素。
- 下面这段代码输出什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const (
a = iota
b = iota
)
const (
name = "name"
c = iota
d = iota
)
func main() {
fmt.Println(a)
fmt.Println(b)
fmt.Println(c)
fmt.Println(d)
}
0 1 1 2。知识点:iota 的用法。
iota 是 golang 语言的常量计数器,只能在常量的表达式中使用。
iota 在 const 关键字出现时将被重置为0,const中每新增一行常量声明将使 iota 计数一次。
- 下面这段代码输出什么?
1
2
3
4
5
6
7
8
9func main() {
s1 := []int{1, 2, 3}
s2 := s1[1:]
s2[1] = 4
fmt.Println(s1)
s2 = append(s2, 5, 6, 7)
fmt.Println(s1)
}
[1 2 4]
[1 2 4]
我们知道,golang 中切片底层的数据结构是数组。当使用 s1[1:] 获得切片 s2,和 s1 共享同一个底层数组,这会导致 s2[1] = 4 语句影响 s1。
而 append 操作会导致底层数组扩容,生成新的数组,因此追加数据后的 s2 不会影响 s1。
但是为什么对 s2 赋值后影响的却是 s1 的第三个元素呢?这是因为切片 s2 是从数组的第二个元素开始,s2 索引为 1 的元素对应的是 s1 索引为 2 的元素。
- 下面这段代码输出什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21var p *int
func foo() (*int, error) {
var i int = 5
return &i, nil
}
func bar() {
//use p
fmt.Println(*p)
}
func main() {
p, err := foo()
if err != nil {
fmt.Println(err)
return
}
bar()
fmt.Println(*p)
}
知识点:变量作用域。问题出在操作符:=,对于使用:=定义的变量,如果新变量与同名已定义的变量不在同一个作用域中,那么 Go 会新定义这个变量。对于本例来说,main() 函数里的 p 是新定义的变量,会遮住全局变量 p,导致执行到bar()时程序,全局变量 p 依然还是 nil,程序随即 Crash。
var err error
p, err = foo()
- 下面这段代码输出什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18func f(n int) (r int) {
defer func() {
r += n
recover()
}()
var f func()
defer f()
f = func() {
r += 2
}
return n + 1
}
func main() {
fmt.Println(f(3))
}
7
- 下面这段代码输出什么?
1
2
3
4
5
6
7
8
9
10
11
12
13func change(s ...int) {
s = append(s,3)
}
func main() {
slice := make([]int,5,5)
slice[0] = 1
slice[1] = 2
change(slice...)
fmt.Println(slice)
change(slice[0:2]...)
fmt.Println(slice)
}
[1 2 0 0 0]
[1 2 3 0 0]
要关注初始化容量
知识点:可变函数、append()操作。Go 提供的语法糖…,可以将 slice 传进可变函数,不会创建新的切片。
第一次调用 change() 时,append() 操作使切片底层数组发生了扩容,原 slice 的底层数组不会改变;
第二次调用change() 函数时,使用了操作符[i,j]获得一个新的切片,假定为 slice1,它的底层数组和原切片底层数组是重合的,不过 slice1 的长度、容量分别是 2、5,所以在 change() 函数中对 slice1 底层数组的修改会影响到原切片。
- 下面这段代码输出什么?
type Direction int
const (
North Direction = iota
East
South
West
)
func (d Direction) String() string {
return […]string{“North”, “East”, “South”, “West”}[d]
}
func main() {
fmt.Println(South)
}
知识点:iota 的用法、类型的 String() 方法。
根据 iota 的用法推断出 South 的值是 2;另外,如果类型定义了 String() 方法,当使用 fmt.Printf()、fmt.Print() 和 fmt.Println() 会自动使用 String() 方法,实现字符串的打印。
- 下面这段代码输出结果正确吗?
for range 的坑1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19type Foo struct {
bar string
}
func main() {
s1 := []Foo{
{"A"},
{"B"},
{"C"},
}
s2 := make([]*Foo, len(s1))
for i, value := range s1 {
s2[i] = &value
}
fmt.Println(s1[0], s1[1], s1[2])
fmt.Println(s2[0], s2[1], s2[2])
}
输出:
{A} {B} {C}
&{A} &{B} &{C}
s2 的输出结果错误。s2 的输出是 &{C} &{C} &{C},for range 使用短变量声明(:=)的形式迭代变量时,变量 i、value 在每次循环体中都会被重用,而不是重新声明。所以 s2 每次填充的都是临时变量 value 的地址,而在最后一次循环中,value 被赋值为{c}。因此,s2 输出的时候显示出了三个 &{c}。
可行的解决办法如下:
for i := range s1 {
s2[i] = &s1[i]
}
- 下面这段代码输出什么?思考为什么。
1
2
3
4
5
6
7
8
9
10
11
12func main() {
var m = [...]int{1, 2, 3}
for i, v := range m {
go func() {
fmt.Println(i, v)
}()
}
time.Sleep(time.Second * 3)
}
2 3
2 3
2 3
for range 使用短变量声明(:=)的形式迭代变量,需要注意的是,变量 i、v 在每次循环体中都会被重用,而不是重新声明。
各个 goroutine 中输出的 i、v 值都是 for range 循环结束后的 i、v 最终值,而不是各个goroutine启动时的i, v值。可以理解为闭包引用,使用的是上下文环境的值。
两种可行的 fix 方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
171.使用函数传递
for i, v := range m {
go func(i,v int) {
fmt.Println(i, v)
}(i,v)
}
2.使用临时变量保留当前值
for i, v := range m {
i := i
// 这里的 := 会重新声明变量,而不是重用
v := v
go func() {
fmt.Println(i, v)
}()
}
0228
0328
常量。常量组中如不指定类型和初始化值,则与上一行非空常量右值相同
调用 foo() 函数时虽然是传值,但 foo() 函数中,字段 ls 依旧可以看成是指向底层数组的指针
类型断言语法:i.(Type),其中 i 是接口,Type 是类型或接口。编译时会自动检测 i 的动态类型与 Type 是否一致。但是,如果动态类型不存在,则断言总是失败
输出[1 0 2 3],字面量初始化切片时候,可以指定索引,没有指定索引的元素会在前一个索引基础之上加一,所以输出[1 0 2 3],而不是[1 3 2]。
var x1 = []int{2: 2}
fmt.Println(x1) // 0 0 2
var x1 = []int{2: 2, 3}
fmt.Println(x1) // 0 0 2 3
运行时错误。如果类型实现 String() 方法,当格式化输出时会自动使用 String() 方法。上面这段代码是在该类型的 String() 方法内使用格式化输出,导致递归调用,最后抛错。
err 没有用到
常量。常量是一个简单值的标识符,在程序运行时,不会被修改的量。不像变量,常量未使用是能编译通过的。
不能对 nil 的 map 直接赋值,需要使用 make() 初始化。但可以使用 append() 函数对为 nil 的 slice 增加元素。1
2
3
4
5func main() {
var m map[string]int
m = make(map[string]int)
m["one"] = 1
}