数组
在上文对基本类型做了介绍,如,现在需要10个int类型的变量,安装上文中介绍,我们可以声明10个变量,那么100个呢,这样是不有些麻烦?这时数组就出现了。可以声明一个长度为10的数组。数组是有长度和类型的集合,具有连续的存储空间
数组的声明
var aa [10]int // 标准声明
var bb [10]int = [10]int{} //声明并初始化
var cc = [10]int{1: 3, 5: 6} // 带索引的式初始化
var mm = [...]int{1, 3, 4, 5} // 类型推断,长度推断
dd := [10]int{1, 2} //类型推断式声明
ee := [...]int{1, 2}
数组的长度
获取数组的长度可以使用内置函数len取得, 声明数组时,数组的长度必须是一个常量,因为在编译完成后,就需要确定在内存中分配多大的空间进行存储。
数组的访问数组是一种高效的数据结构,它之前高效,是因为,数组在内存空间中的存储是连续,知道第一个元素的地址,通过公式可以快速的定位其要访问的存储单元的地址。公式为:索引对应内存地址=首地址+存储单元长度*索引值
通过查看内存的方式验证下这个结论
基于这种的内存结构,使得它的访问效率极高。在语言层面,数组的访问和赋值可以使用 aa[index] // 访问index的数组 aa[index] = 123 // 数组赋值
数组遍历使用go语言提供的for循环进行遍历
aa := [3]int{1,2,3}
for idx, val := range aa {
fmt.Printf("下标:%d 值:%d\n",idx, val)
}
for i :=0; i< len(aa); i++ {
fmt.Printf("下标:%d 值:%d\n",i, aa[i])
}
对数组的介绍在此先画个句号。数组是定长的。当需要使用变长的数组,怎么办?
func main() {
var aa = [3]int{}
num1 := (*int)(unsafe.Pointer(&aa))
*num1 = 1
num2 := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&aa)) + unsafe.Sizeof(int(0))*1))
*num2 = 10
num3 := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&aa)) + unsafe.Sizeof(int(0))*2))
*num3 = 4
fmt.Println(aa)
}
接着上文中提出的那个问题?怎么实现变长的数组?基于定长的数组,我们通过封装可以定义一个新的数据类型,实现不定长度的数组。这种数据类型被称为切片。
切片的声明切片的声明同数组声明一样,只是少了长度的指定 如:var aa []int 也可以通过类型推断的方式进行声明
初始化切片使用make进行初始化 aa := make([]int, 1,1) 通过字面量的方式初始化 aa := []int{1,2} 通过数组初始化 arr :=[5]int{1,2,3} aa := aa[0:3]
切片的追加切片内部是基于数组存储数据的,但存储的长度等于数组的最大长度时,再添加元素就会触发扩容,这个实现对外暴露出一个append的函数。如下:基于数组初始化的切片扩容 arr := [3]int{1,2,3} s1 := arr[0:2] // len(s1) < cap(s1) 不会发生扩容 s1 = append(s1, 1) // len(s1) = cap(s1) 触发扩容 s1 = append(s1, 1)
扩容机制:容量小于1024时,成倍的扩容,容量超过1024时,增长因子设为1.25,也就是说每次会增加25%的容量 看到很多书和资料都写的容量小于1024时,是二倍的扩容,但通过实验发现该描述并不准确,如:uint, int8切片在在首次扩容是8倍,uint16, int16是四倍的扩容
切片的迭代 切片的遍历和数组一样可以使用for range或for,此处不做展开