Go语言圣经 - 第4章 复合数据类型 - 4.2 slice

第四章 复合数据类型

基础数据类型是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]
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值