第四章 复合数据类型
基础数据类型是Go语言世界的原子
复合数据类型包括四种:slice、map、 struct、array
数组和结构体是聚合类型,它们的值由许多元素或成员构成,数组和结构体都是固定内存大小的数据结构,,相比之下,slice和map则是动态的数据结构,它们将根据动态增长
4.2 slice
slice首先和数组很像,slice元素的类型都必须相同,slice提供了访问数组子序列全部元素的功能,其底层确实引用了一个数组对象。一个slice由三个部分组成,指针、长度和容量,其中指针指向slice中第一个元素对应的底层数组元素的地址(slice的第一个元素不一定就是数组的第一个元素)。长度对应slice对应slice中元素的数目,长度不超过容量。容量一般指开始的位置到底层数据的结尾位置。内置的len和cap函数可以返回slice的长度和容量
多个slice可以共享同一个底层数据,并且引用部分可以重叠,slice虽然和数组声明非常相似,但是也有区别,它在声明时不用明确产长度,这会隐式的创建一个合适大小的数组
months := [...]string{1:"January",2:"February",3:"March",4:"April",5:"May",6:"June",7:"July",8:"August",9:"September",
10:"October",11:"November",12:"December"}
fmt.Println(months) //[ January February March April May June July August September October November December]
Q2 :=months[4:7]
Summar := months[6:9]
fmt.Println(Q2)//[April May June]
fmt.Println(Summar)//[June July August]
切片操作用s[i:j],范围包左不包右,s[1:]代表从索引值位1的元素一直到末尾,s[:5]表示从索引值为0开始到索引为4,s[:]表示整个数组。
我们可以使用一个测试来验证它们中是否有重叠元素
for _,s :=range Summar{
for _,q := range Q2{
if s == q {
fmt.Printf("%s appears in both\n",s)
}
}
}
切片操作超出cap(s)上限将导致错误,但是超出len(s)将扩展
字符串的切片和字节类型的切片[]byte操作是一样的
slice指针
因为slice值包含指向第一个slice元素的指针,因此向函数传递slice将允许修改底层数组,换句话说,新建了一个slice只是意味着新复制了一个slice别名。我们来看一个reverse函数
func main() {
a := []int{0,1,2,3,4,5}
reverse(a[:])
fmt.Println(a)///[5 4 3 2 1 0]
}
func reverse (s []int){
for i,j:=0, len(s)-1;i<j;i,j= i+1,j-1{
s[i],s[j] = s[j],s[i]
}
}
我们再来尝试反转局部元素
reverse(a[:3])
fmt.Println(a)// [2 1 0 3 4 5]
reverse(a[3:])
fmt.Println(a)// [0 1 2 5 4 3]
slice之间不能比较
虽然无法用 == 直接来比较两个slice,但是我们可以使用bytes.Equal 函数来判断两个字节型slice是否相等([]byte),而对于其它类型的slice,我们必须先展开,然后再比较
func equal (x,y string) bool{
if len(x)!=len(y) {
return false
}
for i := range x{
if x[i] != y[i] {
return false
}
}
return true
}
但是slice可以和nil比较,但是我们如果想测试一个slice是否为空,应该使用len(s) == 0 来判断,而不是和nil比较
if summer == nil{/*...*/}
make函数
利用make 函数也可以创建 slice
a := make([]int,6)
fmt.Println(a)//[0 0 0 0 0 0]
4.2.1 append 函数
内置的append函数可以用于向slice追加元素
func main() {
var runes []rune
for _,r:= range "hello, 世界"{
runes = append(runes,r)
}
fmt.Printf("%q\n",runes)//['h' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']
}
append函数对于理解底层slice是如何工作的十分重要,那append函数到底是发生了什么
1.每次调动append函数,函数会先检查底层数组是否有足够的容量来保存新添加的元素,如果空间足够的话,直接扩展到底层数组上,底层数组未变
2.如果没有足够的增长空间的话,append函数则会先分配一个足够大的slice用于保存新的结果,先将原有的数据复制到新的slice上,然后再添加数据,底层数组发生变化
要正确的使用slice,要明白尽管底层数组的元素是间接访问的,但是slice对应结构体本身的指针、长度和容量部分是直接访问的,更新这些信息需要显式赋值操作,从这个角度,slice并不是一个纯粹的引用类型,它实际上是一个类似于下面结构体的聚合类型
type Intslice struct {
ptr *int
len,cap int
}
内置的append函数可以添加一个或者多个元素、甚至一个切片至slice
4.2.2 slice内存技巧
在某个slice上做了变换(如reverse操作)返回了一个新slice,但是这两个slice共享同一个底层数组
还有,我们可以使用定义的nonempty函数去掉那些数组中包含了空格
func main() {
data := []string{"one","","three"}
fmt.Printf("%q\n",nonempty(data))//["one" "three"]
}
func nonempty(strings []string) []string {
i := 0
for _, s := range strings {
if s != "" {
strings[i] = s
i++
}
}
return strings[:i]
}
一个slice还可以类比一个栈,使用append函数将元素压入栈(stack),栈的顶部对应最后一个元素,并且通过收缩可以弹出栈顶元素
要删除某个栈中间某个元素,但是不想改变元素顺序,可以使用copy函数
func remove(slice []int, i int) []int {
copy(slice[i:], slice[i+1:])
return slice[:len(slice)-1]
}
func main() {
s := []int{5, 6, 7, 8, 9}
fmt.Println(remove(s, 2)) // "[5 6 8 9]"
}
如果不想要原来的顺序,直接用最后一个元素覆盖即可
func remove(slice []int, i int) []int {
slice[i] = slice[len(slice)-1]
return slice[:len(slice)-1]
}
func main() {
s := []int{5, 6, 7, 8, 9}
fmt.Println(remove(s, 2)) // "[5 6 9 8]
}