Go语言中数组、字符串和切片三者是密切相关的数据结构。这3种数据类型,在底层原始数据有着相同的内存结构,在上层,由于语法的限制而有着不一样的行为表现。数组
Go语言的数据是一种值类型,虽然数组的元素能够被修改,可是数组自己的赋值和函数传参都是以总体复制的方式处理的。数据结构
Go语言字符串底层数据也是对应的字节数组,可是字符串的只读属性禁止了在程序中对底层字节数组的元素的修改。字符串赋值只是复制了数据地址和对应的长度,而不会导师底层数据的复制。app
切片的底层数据虽然也是对应数据类型的数组,可是每一个切片还有独立的长度和容量信息,切片赋值和函数的传参时也是将切片头信息部分按传值方式处理。由于切片头含有底层数据的指针,因此它的赋值也不会致使底层数据的复制。ide
1、数组函数
数组是一个由固定长度的特定类型元素组成的序列,一个数组能够由零个或多个元素组成。数组的长度是数组类型的组成部分。由于数组长度是数组的一部分,不一样长度或不一样类型的数据组成的都是不一样的类型,因此在Go语言中不多直接使用数组。和数组对应的类型是切片,切片是能够动态增加和收缩的序列,切片的功能也更灵活。性能
数组定义方式:编码
var a [3]int //定义长度为3的int型数组,元素所有为0 var b = [...]int{1, 2, 3} //定义长度为3的int型数组,元素为1,2,3 var c = [...]int{2: 3, 1: 2} //定义长度为3的int型数组,元素为0,2,3 var d = [...]int{1,2,4:5,6} //定义长度为6的int型数组,元素为1,2,0,0,5,6
第一种方式是定义一个数组变量的最基本的方式,数组长度明确指定,数组中的每一个元素都以0值初始化spa
第二种方式是定义数组,能够在定义的时候顺序指定所有元素的初始化值,数组的长度根据初始化元素的数目自动计算指针
第三种方式是以索引的方式来初始化数组的元素,所以元素的初始化值出现顺序比较随意。这种初始化方式和map[int]Type类型的初始化语法相似。数组的长度以出现的最大索引为准,没有明确初始化的元素依然用0值初始化code
第四种方式是混合了第二种和第三种的初始化方式,前面两个元素采用顺序初始化,第三个和第四个元素以0值初始化,第五个元素经过索引初始化,最后一个元素跟在前面的第五个元素以后采用顺序初始化。
Go语言数组是值定义。一个数组变量即表示整个数组,它并不隐式的指向第一个元素的指针,而是一个完整的值。
var a = [...]int{1, 2, 3} //a是一个数组 var b = &a //b是指向数组的指针 fmt.Println(a[0], a[1]) //打印数组的前两个元素 fmt.Println(b[0], b[1]) //打印数组指针访问数组元素的方式和经过数组相似 for i, v := range b { //经过数组指针迭代数组的元素 fmt.Println(i, v) }
其中b是指向数组a的指针,可是经过b访问数组中元素的写法和a是相似的。还能够经过for range 来迭代数组指针指向的数组元素。
能够将数组看作一个特殊的机构体,结构的字段名对应数组的索引,同时结构体成员的数目是固定的。内置函数len()能够用于计算数组的长度,cap()函数用于计算数组的容量。
数组不只能够定义数值数组,还能够定义字符串数组、结构体数组、函数数组、接口数组、通道数组等
2、字符串
一个字符串是一个不可改变的字节序列,和数组不一样的是,字符串的元素不可修改,是一个只读的字节数组。字符串的结构有两个信息组成:第一个是字符串指向的底层字节数组;第二个是字符串的字节长度。如下是go语言源码中对string类型的说明:
// string is the set of all strings of 8-bit bytes, conventionally but not // necessarily representing UTF-8-encoded text. A string may be empty, but // not nil. Values of string type are immutable. type string string
字符串是8位字节字符的集合,默认以utf-8编码,能够是空的可是不能是nil,字符串是只读的。
字符串虽然不是切片,可是支持切片操做,不一样位置的切片底层访问的是同一块内存数据
s := "hello, world" hello := s[:5] world := s[7:]
字符串和数组相似,内置的len()函数返回字符串的长度
3、切片
切片(slice)是一种简化版的动态数组。
切片的定义方式:
var (
a [] int //nil切片,和nil相等,通常用来表示不存在的切片
b = []int{} //空切片,和nil不相等,通常用来表示一个空的集合
c = []int{1, 2, 3} //有3个元素的切片,len和cap都为3
d = c[:2] //有2个元素的切片,len为2,cap为3
e = c[0:2:cap(c)] //有2个元素的切片,len为2,cap为3
f = make([]int,3) //有3个元素的切片,len和cap都为3
)
和数组同样,内置的len()函数返回切片中有效元素的长度,内置的cap()函数返回切片容量的大小,容量必须大于或等于切片的长度。
添加切片元素
内置的泛型函数append()能够在切片的尾部追加N个元素:
var a []int a = append(a, 1) //追加一个元素 a = append(a, 1, 2, 3) //追加多个元素,手写解包方式 a = append(a, []int{1, 2, 3}...) //追加一个切片,切片须要解包
注意,在容量不足的状况下,append()操做会致使从新分配内幕才能,可能致使巨大的内存分配和复制数据的代价。及时容量足够,依然须要用append()函数的返回值来更新切片自己,由于新切片的长度已经发生了变化。
除了在切片尾部追加,还能够在切片的开头添加元素:
var a = []int{1, 2, 3} a = append([]int{0}, a...) a = append([]int{-3, -2, -1}, a...)
在开头通常会致使内存的从新分配,并且会致使已有的元素所有复制一次,所以,从切片开头添加元素的性能通常要比从尾部追加元素的性能差不少。
因为append()函数返回新的切片,也就是它支持链式操做,所以咱们能够将多个append()操做组合起来,实如今切片中间插入元素:
var a []int a = append(a[:i], append([]int{x}, a[i:]...)...) //在第i个位置插入x a = append(a[:i], append([]int{1, 2, 3}, a[i:]...)...) //在第一个位置插入切片
用copy()和append()组合能够避免建立中间的临时切片,一样完成添加元素的操做:
a = append(a, 0) //切片扩展一个空间 copy(a[i+1], a[i:]) //a[i:]向后移动一个位置 a[i] = x //设置新添加的元素
操做语句虽然冗余了一些,可是相比以前的方法能够减小中间建立的临时切片。
2.删除切片元素
根据要删除的元素的位置,有从开头位置删除、从中间位置删除和从尾部删除3种状况,其中删除切片尾部的元素最快:
a = []int{1, 2, 3} a = a[:len(a) - 1] //删除尾部1个元素 a = a[:len(a) - n] //删除尾部n个元素
删除开头的元素能够直接移动数据指针:
a = []int{1, 2, 3} a = a[1:] //删除开头1个元素 a = a[N:] //删除开头N个元素
删除中间的元素,须要对剩余的元素进行一次总体挪动,一样能够用append()或copy()原地完成
a = []int{1, 2, 3, ...} a = append(a[:i], a[i+1:]...) //删除中间1个元素 a = append(a[:i], a[i+N:]...) //删除中间N个元素 a = a[:i+copy(a[i:], a[i+1:])] //删除中间1个元素 a = a[:i+copy(a[i:], a[i+N:])] //删除中间N个元素