# 数组

数组是值类型,且大小固定,声明方式为:[n]T

func main() {
	arr := [4]int{1, 2, 3, 4}
	arr2 := arr
	arr2[1] = 5
	log.Println(arr, arr2)
	changeArr(arr)
	changeArr(arr2)
	log.Println(arr, arr2)
}
func changeArr(arr [4]int) {
	for i := 0; i < len(arr); i++ {
		arr[i] = 0
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 切片

切片是引用类型,引用的是指向的数组的一段数据,大小不固定,声明方式为:[]T

func main() {
	arr := []int{1, 2, 3, 4}
	changeArr(arr)
	log.Println(arr)
}
func changeArr(arr []int) {
	for i := 0; i < len(arr); i++ {
		arr[i] = 0
	}
}
1
2
3
4
5
6
7
8
9
10
11

make创建切片的原理

# 第一个例子,创建一个指定长度为5,指定容量为10的切片
arr:=make([]int,5,10) 
// 等价与下面的代码
arrTemp:=[10]int{0,0,0,0,0,0,0,0,0,0}
arr:=arrTemp[:5]
# 第二个例子,创建一个指定长度为0,指定容量为10的切片
arr:=make([]int,0,10) 
// 等价与下面的代码
arrTemp:=[10]int{0,0,0,0,0,0,0,0,0,0}
arr:=arrTemp[:0]
1
2
3
4
5
6
7
8
9
10
11

注意,总结

  1. 切片不会存储任何数据
  2. 切片永远指向数组中的一段引用
  3. 使用make创建切片时,实际是创建了一个对应容量的数组,然后返回引用了数组指定长度的切片
  4. 当用appent添加元素,容量不够时则将旧数组元素复制到一个新创建的比原来容量大一倍的数组,并返回引用了新数组指定长度的切片

数组/切片与垃圾回收

我们应该尽量使用make来创建一个预定义容量的切片,最好不指定大小(方便使用append),当向切片中添加元素时超过了切片的实际容量则会导致创建新的数组,从而产生gc垃圾

# 接口interface

接口是一组方法签名,所有实现了该签名的子类都可以赋值给这个接口变量。

go中有两种接口的使用场景:1. 用作类型签名,2. 空接口(无方法签名)

用作类型签名

type Abser interface {
	Abs() float64
}
type Vertex struct {
	X, Y float64
}
func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
	var a Abser
	v := Vertex{3, 4}
	a = &v // a *Vertex 实现了 Abser
	// 下面一行,v 是一个 Vertex(而不是 *Vertex)
	//v.Abs()调用时实际被转成了(&v).Abs()
	// 所以没有实现 Abser
	// 下面为错误代码
	a = v
	fmt.Println(a.Abs())
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

空接口

空接口就是没有任何方法签名的接口,它可以接收任意类型的值

func main() {
	var i interface{}
	i = 1
	i = 1.1
	i = "1"
	i = map[string]interface{}{}
	i = []int{}
	i = true
}
1
2
3
4
5
6
7
8
9

interface与nil

接口类似于下面这样一个结构体,一个接口变量记录了它实际指向的值和这个值的类型

type interface struct{
	data interface{}
	type Type
}
1
2
3
4

下面这个例子,给一个接口类型赋值一个bool值,实际上接口内部存储了两个值,一个是具体的值,一个是类型

var i interface{}
i = true
fmt.Printf("%v %T",i,i) // true bool
1
2
3

不要判断interface是否为nil

func main() {
	var i interface{}
	if i == nil {
		log.Println("is nil") 		// is nil
	}
	var ipeople *IPeople
	i = ipeople
	log.Printf("%v %T", i, i)		// nil nil
	var people *People
	i = people
	if people == nil {
		log.Println("people is nil")  // people is nil
	}
	if i == nil {
		log.Println("i is nil")
	} else {
		log.Println("i is not nil")   // i is not nil
		log.Printf("%v %T", i, i)     // nil *main.People
	}
	var people2 People
	log.Printf("%v %T", people2, people2) //{} main.People
}
type People struct {
}
type IPeople interface{}
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

使用断言判断接口是否为nil

语法:v,ok:=i.(type)

func main() {
	var i interface{}
	i = 1
	v, ok := i.(int)
	log.Println(v, ok) // 1 true
	var people *People
	i = people
	people, ok = i.(*People)
	log.Println(people, ok) // <nil> true
	var ipeople *IPeople
	i = ipeople
	ipeople, ok = i.(*IPeople)
	log.Println(ipeople, ok) // <nil> true
	var people1 People
	var ipeople1 IPeople
	i = people1
	people1, ok = i.(People)
	log.Println(ipeople, ok) // <nil> true
	i = ipeople1
	ipeople1, ok = i.(IPeople)
	log.Println(ipeople1, ok) // <nil> false
	if ok && ipeople==nil{
        // do...
    }
}
type People struct{}
type IPeople interface{}
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

# defer栈的执行顺序

defer栈保证函数退出的时候总会执行一些代码,defer是按后进先出的顺序调用的,跟栈的行为一致

func main() {
	fmt.Println("counting")
	for i := 0; i < 10; i++ {
		defer fmt.Println(i)
	}
	fmt.Println("done")
}
// 9
// 8
// ...
// 0
1
2
3
4
5
6
7
8
9
10
11
12
13

# new与make的区别

new返回的是引用,make返回的是值

new返回的需要是*才能使用,make则不需要

new可以作用与除map,arr,chan以外的类型,make则只支持这三种类型

new([]byte)等价与&[]byte{}
new(Struct)等价于&Struct{}
1
2

注意

不要或尽量不使用new来创建对象,需要new的改为&T{}这样创建

函数间传递数据要使用*T这样通过引用地址传递,避免频繁分配不必要的内存,除非传递的数据明确在函数内修改后不能影响外部对它的应用