数组申明方式:
var ip4 [7]uint8 = [7]uint8{1, 2, 3, 4, 5}
for i, value := range ip4 {
fmt.Printf("index=%d,value=%d ", i, value)
}
数组和切片slice的区别:
Slice切片是对底层数组Array的封装,在内存中的存储本质就是数组,体现为连续的内存块,Go语言中的数组定义之后,长度就已经固定了,在使用过程中并不能改变其长度,而Slice就可以看做一个长度可变的数组进行使用,最为关键的,是数组在使用的过程中都是值传递,将一个数组赋值给一个新变量或作为方法参数传递时,是将源数组在内存中完全复制了一份,而不是引用源数组在内存中的地址,为了满足内存空间的复用和数组元素的值的一致性的应用需求,Slice出现了,每个Slice都是都源数组在内存中的地址的一个引用,源数组可以衍生出多个Slice,Slice也可以继续衍生Slice,而内存中,始终只有源数组,当然,也有例外。
Slice的定义:
Slice可以通过两种方式定义,一种是从源数组中衍生,一种是通过make函数定义,本质上来说都一样,都是在内存中通过数组的初始化的方式开辟一块内存,将其划分为若干个小块用来存储数组元素,然后Slice就去引用整个或者局部数组元素。
比如s:=[]int{1,2,3}
注意,这与初始化数组有一点点区别,可能有人认为这个写法是定义和初始化一个数组,事实上这个写法是现在内存中构建一个包括有3个元素的数组,然后将这个数组的应用赋值给s这个Slice,通过以下数组的定义进行区别:
a := [3]int{1, 2, 3}
b := [...]int{1, 2, 3}
c := []int{1, 2, 3}
fmt.Println(cap(a), cap(b), cap(c))
a = append(a, 4)//Error:first argument to append must be slice; have
[3]int
b = append(b, 4)//Errot:first argument to append must be slice; have
[3]int
c = append(c, 4)//正常,说明变量c是Slice类型
可以看出,强调了数组定义的规则:长度和类型必须指定,若是根据实际元素个数自动计算数组长度,需要使用[...]定义,而不能只使用[]。
从数组切片中创建slice:
a := [10]int{1, 2, 3, 4, 5, 6, 7, 8,
9, 0}
s := a[2:8]
fmt.Println(s) //输出:[3 4 5 6 7 8]
通过make函数创建一个slice
s := make([]int, 10, 20)
fmt.Println(s) //输出:[0 0 0 0 0 0 0 0 0 0]
make函数第一个参数表示构建的数组的类型,第二个参数为数组的长度,第三个参数可选,是slice的容量,默认为第二个参数值。
Slice的长度和容量
Slice有两个比较混淆的概念,就是长度和容量,何谓长度?这个长度跟数组的长度是一个概念,即在内存中进行了初始化实际存在的元素的个数。何谓容量?如果通过make函数创建Slice的时候指定了容量参数,那内存管理器会根据指定的容量的值先划分一块内存空间,然后才在其中存放有数组元素,多余部分处于空闲状态,在Slice上追加元素的时候,首先会放到这块空闲的内存中,如果添加的参数个数超过了容量值,内存管理器会重新划分一块容量值为原容量值*2大小的内存空间,依次类推。这个机制的好处在能够提升运算性能,因为内存的重新划分会降低性能。
Slice是引用类型
上边已经提到过,Slice是对源数组的一个引用,改变Slice中的元素的值,实质上就是改变源数组的元素的值
a := [10]int{1,
2, 3, 4, 5, 6, 7, 8, 9, 0}
sa := a[2:7]
sa = append(sa, 100)
sb := sa[3:8]
sb[0] = 99
fmt.Println(a) //输出:[1 2 3 4 5 99 7
100 9 0]
fmt.Println(sa) //输出:[3 4 5 99 7
100]
fmt.Println(sb) //输出:[99 7 100 9 0]
可以看到,不管是append操作,还是赋值操作,都影响了源数组或者其他引用同一数组的Slice的元素。Slice进行数组引用的时候,其实是将指针指向了内存中具体元素的地址,如数组的内存地址,事实上是数组中第一个元素的内存地址,Slice也是如此。
a := [10]int{1,
2, 3, 4, 5, 6, 7, 8, 9, 0}
sa := a[2:7]
sb := sa[3:8]
fmt.Printf("%p
", sa) //输出:0xc084004290
fmt.Println(&a[2], &sa[0]) //输出:0xc084004290 0xc084004290
fmt.Printf("%p
", sb) //输出:0xc0840042a8
fmt.Println(&a[5], &sb[0]) //输出:0xc0840042a8 0xc0840042a8
Slice引用传递发生意外:
上边我们一直在说,Slice是引用类型,指向的都是内存中的同一块内存,不过在实际应用中,有的时候却会发生“意外”,这种情况只有在像切片append元素的时候出现,Slice的处理机制是这样的,当Slice的容量还有空闲的时候,append进来的元素会直接使用空闲的容量空间,但是一旦append进来的元素个数超过了原来指定容量值的时候,内存管理器就是重新开辟一个更大的内存空间,用于存储多出来的元素,并且会将原来的元素复制一份,放到这块新开辟的内存空间。
a :=
[]int{1, 2, 3, 4}
sa := a[1:3]
fmt.Printf("%p
", sa) //输出:0xc0840046e0
sa = append(sa, 11, 22, 33)
fmt.Printf("%p
", sa) //输出:0xc084003200
字典:有如下几种申明方式。
方法一:直接make创建一个字典,map[string]int代表键值类型为string,value类型为int,字典长度为4个。
var ipswitch = make(map[string]int, 4)
ipswitch["abc"] = 1
方法二:直接申明map创建一个字典,注意后面需要带{},然后进行赋值
var ipswitch_update = map[string]int{}
ipswitch_update["abc"] = 2
ipswitch_update["country"] = 3
ipswitch_update["color"] = 4
ipswitch_update["city"] = 5
ipswitch_update["house"] = 6
方法三:在申明的同时进行字典的初始化
var ipswitch_new = map[string]int{"country": 1, "city": 2, "school": 3}
字典的遍历
for key := range ipswitch_update {
fmt.Println(key, " value=", ipswitch_update[key])
}
for key := range ipswitch_new {
fmt.Println(key, " value=", ipswitch_new[key])
}