[TOC]
1. Arrays
golang数组与基本静态语言的数组一致,由于golang是强类型的语言,所以数组元素必须类型相同。数组的基本声明方式如下:
1.1 数组声明
定长数组
使用var arr [n]T
进行声明长度为n, 类型为T的数组对象,采用这种方式进行声明,每个元素根据数组类型赋予相应的初始值,例如int初值为0
也可以在声明时向数组赋予初始值,a := [3]int{1, 3, 6}
,可以不全赋值,其余位置会被默认值填充
Copy // 初始值测试
package main
import (
"fmt"
"reflect"
)
func main() {
var a [3]int //int array with length 3
var (
b [3]float32
c [3]float64
d [3]string
e [3]bool
f [3]byte
g [3]rune
h [3]complex64
i [3]int8
)
fmt.Println(reflect.ValueOf(a[0]).Kind(), a,
reflect.ValueOf(b[0]).Kind(), b,
reflect.ValueOf(c[0]).Kind(), c,
reflect.ValueOf(d[0]).Kind(), d,
reflect.ValueOf(e[0]).Kind(), e,
reflect.ValueOf(f[0]).Kind(), f,
reflect.ValueOf(g[0]).Kind(), g,
reflect.ValueOf(h[0]).Kind(), h,
reflect.ValueOf(i[0]).Kind(), i)
}
int [0 0 0] float32 [0 0 0] float64 [0 0 0] string [ ] bool [false false false] uint8 [0 0 0] int32 [0 0 0] complex64 [(0+0i) (0+0i) (0+0i)] int8 [0 0 0]
自定长度数组
可以使用[...]T{.., .., }
进行数组的声明,编译器会自动根据给定元素给数组确定长度
但是后面必须跟上数组元素的声明,不可以不指定元素
Copy func main() {
a := [...]int{1, 2, 1e3, 2e4} // ... makes the compiler determine the length
fmt.Println(a)
}
注意数组长度也是数组类型的一部分 ,对于强类型语言来说,不同长度的数组不属于同一类型,所以将不同长度的数组进行赋值是不被允许的
1.2. 数组以值传递
golang中的数组是value type
而不是reference type
,也就是说,进行数组间的相互赋值时,使用的不是同一个内存空间,开辟了另一个内存空间用以存放新的数组,所以改变新的数组中元素并不会改变原数组的值,也就是说,使用的是深复制。
所以数组作为参数进行传递时,也是按值传递,不会改变原数组的元素。
Copy func changeLocal(num [5]int) {
num[0] = 55
fmt.Println("inside function ", num)
}
func main() {
num := [...]int{5, 6, 7, 8, 8}
fmt.Println("before passing to function ", num)
changeLocal(num) //num is passed by value
fmt.Println("after passing to function ", num)
}
关于数组的长度可以使用len()
函数求取
2. 可迭代对象的遍历
对于数组等可迭代对象,可以使用基本的for循环进行,也可以使用range
函数,进行迭代,每次迭代包含index, value
两个值进行使用,如果需要忽略index可以使用空白操作符,忽略value可以只进行index的赋值
Copy for i, v := range list {
fmt.Println(i, v)
}
3. 切片
数组虽然已经足够灵活,但是必须是确定长度,不支持变长操作。切片类型是基于数组的一个顶层wrapper
,切片本身并不存放任何数据,他们只是某些已经存在的数组的引用
3.1 切片声明
Copy a := [5]int{1, 2, 67, 76, 88}
var b []int = a[1:4]
// 1, 2, 67
a := [5]int{76, 77, 78, 79, 80}
var b = a[1:4] //creates a slice from a[1] to a[3]
fmt.Println(b[1:4], b) // 注意此处slice的len为3,cap为4,不可以用[]访问,但是可以使用[:]格式进行访问,
切片的类型用不需要长度的[]int
表示,对于数组的[:]
操作,与python用法一致,但是不允许负值操作,而且不允许倒序操作,而且最多只接受两个参数,同样,包含下界不包含上界。
使用 make()
函数声明切片
Copy func make([]T, len, cap int) []T
用于生成一个切片,传入的是类型,容量以及长度,值采用默认值填充。
3.2 切片机制
切片不拥有任何数据,只是作为底层数组的引用,所以基于切片的数据修改,会直接反映在底层数组对象上。所以公用同一个底层数组的切片,如果对其进行更改,会映射到每一个切片上去。
Copy numa := [3]int{78, 79 ,80}
nums1 := numa[:] //creates a slice which contains all elements of the array
nums2 := numa[:]
fmt.Println("array before change 1",numa)
nums1[0] = 100
fmt.Println("array after modification to slice nums1", numa)
nums2[1] = 101
fmt.Println("array after modification to slice nums2", numa)
长度与容量:
切片的长度使用len()
函数,表示的是切片可表示的元素的数量;切片的容量是从切片开始位置到数组最后一个元素的长度。
一个切片可以被重新切片,并且增长到其容量,如果超出范围会抛出运行时错误。
Copy fruitarray := [...]string{"apple", "orange", "grape", "mango", "water melon", "pine apple", "chikoo"}
fruitslice := fruitarray[1:3]
fmt.Printf("length of slice %d capacity %d\n", len(fruitslice), cap(fruitslice)) //length of is 2 and capacity is 6
fruitslice = fruitslice[:cap(fruitslice)] //re-slicing furitslice till its capacity
fmt.Println("After re-slicing length is",len(fruitslice), "and capacity is",cap(fruitslice)) // 6, 6
结构特征
slices可以被表示为一个结构体
Copy type slice struct {
Length int
Capacity int
ZerothElement *byte // 头指针
}
一个切片包含了长度,容量,以及指向数组第一个元素的指针,但一个切片传作为参数传递时,虽然是按值传递,但是指针变量仍然会指向同一个底层数组。所以使用切片进行参数传递时,内部的改变会影响到函数外部.
3.3 基本操作
Append()
Copy func append([]T, x ...T) []T
// 用于动态扩充一个切片,参数x ...T表示接受多个类型为T的值作为参数,可变长参数
// 当一个切片进行扩充时,如果长度与容量相等,表示空间已经用完
// 此时建立一个底层新的数组,容量为原来2倍并且把原数组的数据copy到新的数组中去,形成新的底层数组
// 并且把新数组的引用赋给切片
Copy cars := []string{"Ferrari", "Honda", "Ford"}
fmt.Println("cars:", cars, "has old length", len(cars), "and capacity",cap(cars)) //capacity of cars is 3
cars = append(cars, "Toyota")
fmt.Println("cars:", cars, "has new length", len(cars), "and capacity",cap(cars)) //capacity of cars is doubled to 6
append不仅可以追加单个或者多个元素,而且可以追加另外一个slice, 使用...
操作符即可
Copy fruits := []string{"oranges", "apples"}
veg := []string{"potatoes"}
food := append(veg, fruits...)
copy
Copy func copy(dst, src []T) int
将切片的容量扩充2倍,可以使用
Copy t := make([]byte, len(s), (cap(s)+1)*2) // cap可能为0
copy(t, s)
s = t
// s = append(s, x ...T)
3.4 空值nil与多维slice
一个slice 的length , capacity均为0,则该切片为空nil
,对于一个控切片,可以使用append()
进行扩充。
使用多维slice只需要在声明是将其指定为多维即可
Copy pls := [][][]string{
{
{"My", "programming"},
{"Language"},
},
{
{"c", "c++"},
{"Python", "JavaScript"},
{"Go", "Rust"},
},
}
注意使用多维数组时,只允许最高纬度使用...
运算符,表示可以不确定其维度,但是其他维度必须标识,如果不标识则内部存放的是slice
Copy var te [...][][]string
var te [...][2][3]int
4. 切片的优化
正如前面所说,切片操作并不会复制底层的数组。整个数组将被保存在内存中,直到它不再被引用。 有时候可能会因为一个小的内存引用导致保存所有的数据。
例如, FindDigits
函数加载整个文件到内存,然后搜索第一个连续的数字,最后结果以切片方式返回。
Copy var digitRegexp = regexp.MustCompile("[0-9]+")
func FindDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
return digitRegexp.Find(b)
}
因为切片引用了原始的数组, 导致 GC 不能释放数组的空间;只用到少数几个字节却导致整个文件的内容都一直保存在内存里。
改进方法是将该切片复制到一个新的切片中,释放原内存。
Copy func CopyDigits(filename string) []byte {
b, _ := ioutil.ReadFile(filename)
b = digitRegexp.Find(b)
c := make([]byte, len(b))
copy(c, b)
return c
}
// c := make([]byte, 0)
// c = append(c, b...)