0%

《Golang》常见错误陷阱

for range循环问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package main
import "fmt"
type Test struct {
A string
}
func main() {
var a []Test = []Test{
Test{A: "123"},
Test{A: "1234"},
}
var b []*Test
for _, a1 := range a {
b = append(b, &a1) // 这里把a1的指针多次push到了b

// a11 := a1 // 这里会重新分配地址给a11
// b = append(b, &a11) // 新地址push进去,这里可以避免这个问题
}
for _, b1 := range b {
fmt.Printf(`%#v`, b1) // 所以b的所有元素都是 Test{A: "1234"}
}
}
1
2
3
for v1, v2 := range a {  // v1, v2并不是两个for作用域内的变量,不会每次for循环都重新初始化
// ...
}

上面的range会被编译器翻译成下面的代码,上面的问题引刃而解

1
2
3
4
5
6
7
8
9
ha := a
hv1 := 0
hn := len(ha)
v1 := hv1
v2 := ha[hv1]
for ; hv1 < hn; hv1++ {
v1, v2 = hv1, ha[hv1]
// ...
}

数组是值传递,切片make后才是引用传递

1
2
3
4
5
6
7
8
9
10
func main() {
x := [3]int{1, 2, 3}

func(arr [3]int) {
arr[0] = 7
fmt.Println(arr)
}(x)

fmt.Println(x)
}

必要时需要使用切片

map遍历时顺序不固定

1
2
3
4
5
6
7
8
9
10
11
func main() {
m := map[string]string{
"1": "1",
"2": "2",
"3": "3",
}

for k, v := range m {
println(k, v)
}
}

闭包发生时不会进行拷贝,而是直接引用堆中变量,变量更新会受影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import "time"
func main() {
for i := 0; i < 5; i++ {
go func() {
println("go: ", i)
}()
}

for i := 0; i < 5; i++ {
defer func() {
println("defer: ", i)
}()
}

time.Sleep(1 * time.Second)
}

输出全部是5。因为主协程中的for循环都在其他协程或者defer前面执行,i早已发生改变

只有在没有任何指针指向切片的时候,底层的数组内存才会被释放

1
2
3
4
5
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
c := b[:1] // 指针传递
return c
}

上面函数运行完,b占用的内存不会被释放,因为c还指向了切片第一个元素

改进

1
2
3
4
5
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
c := append([]byte{}, b[:1]...) // 硬拷贝
return c
}

因为没有指针传递,b指针会释放掉,切片的内存也跟着释放

如果chan close了,还是可以取出元素(取出的是对应类型的零值),但for range会被打断

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
package main

import (
"fmt"
"time"
)

func main() {
a := make(chan struct{})

go func() {
close(a)
//a = nil // 如果是chan设置成nil,则 <-a 会阻塞,就会一直打印wait
time.Sleep(100 * time.Second)
}()

for {
select {
case result := <-a:
fmt.Println("a: ", result) // 打印wait之后一直打印{}
default:
fmt.Println("wait")
time.Sleep(3 * time.Second)
}
}
}

如果想要做到 取完元素就自动退出for循环,可以使用for range <chan>for range语法自带break

interface其实是一个struct

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
package main

import (
"fmt"
)

type People interface {
Show()
}

type Student struct{
A int64
}

func (stu *Student) Show() {
fmt.Println(stu.A) // 报错空指针异常
}

func live() People {
var stu *Student
return stu
}

func main() {
fmt.Println(live() == nil) // false
live().Show() // 报错空指针异常
}

下面是接口的定义:

1
2
3
4
type iface struct {
tab *itab
data unsafe.Pointer
}

接口包含了一个类型以及一个值。上面例子中虽然赋予接口的值是nil,但是类型是有的,接口不会是nil。因为接口中的值是nil,那么访问接口中的值的A属性当然空指针异常

map中的元素不可寻址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
)

type Test struct {
A string
}

func main() {
a := map[string]Test{
"a": {A:"a"},
}
a["a"].A = "fgsdgd" // 这里编译报错,a["a"]不可寻址,导致a["a"].A是非法的
fmt.Println(a)
}

要对一个struct中的字段更新,首先必须保证这个struct可以寻址,没有地址就找不到它的字段

以下情况都是不可寻址的:

  1. 字符串中的字节(因为字符串是不可变的)
  2. map对象中的元素
  3. 接口对象的动态值(通过type assertions获得)
  4. 常数(如果可以寻址的话,我们可以通过指针修改常数的值,破坏了常数的定义)
  5. literal值(非composite literal)
  6. package 级别的函数
  7. 方法method (用作函数值)

map、chan、slice并非真正的指针类型

他们底层就是struct类型,属于值类型

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 main() {
var m map[int]int
testMap(m) // 值拷贝传递
fmt.Println(m) // map[],m还是原来的m,没有分配空间

m1 := make(map[int]int) // make编译成runtime.makemap_small方法,分配数据区域空间。m1的指针有指向了
testMap1(m1) // 值拷贝传递,但他们指向同一个数据区域
fmt.Println(m1) // map[1:1],数据区域发生改变,m1也改变

var s []int
testSlice(s) // 值拷贝传递,s未分配底层数组,指针没有指向
fmt.Println(s) // []

s1 := make([]int, 3) // 值拷贝传递。s1指针有指向了,指向底层数组
testSlice1(s1) // 值拷贝传递
fmt.Println(s1) // [1 0 0],受影响
}

func testMap(m map[int]int) {
m = map[int]int{
1: 1,
}
m[2] = 2
}

func testMap1(m map[int]int) {
m[1] = 1
}

func testSlice(s []int) {
s = []int{1,2}
s[0] = 3
}

func testSlice1(s []int) {
s[0] = 1
}

再看一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
)

type Test []int

func (t Test) T() {
t = t[0:2] // 左边的t是值拷贝过来的局部变量,更改它只会在这个函数中生效
// t[0] = 123 // 如果这里进行更改了,则会改动底层数组,对外面的t有影响
}

func (t *Test) T1() {
*t = (*t)[0:2] // 这样才可以改变实例
}

func main() {
t := Test([]int{1,23,4,5})
t.T()
fmt.Println(t)
}



微信关注我,及时接收最新技术文章