Go基础之切片 | 青训营

71 阅读6分钟

切片

相对于数组,切片(slice)是一种更方便和强大的数据结构,它同样表示多个同类型元素的连续集合,但是切片本身并不存储任何元素,而只是对现有数组的引用。
切片结构包括:地址、长度和容量。
◇ 地址:切片的地址一般指切片中第一个元素所指向的内存地址,用十六进制表示。
◇ 长度:切片中实际存在元素的个数。
◇ 容量:从切片的起始元素开始到其底层数组中的最后一个元素的个数。
切片的长度和容量都是不固定的,可以通过追加元素使切片的长度和容量增大。
切片主要有三种生成方式:
1.从数组生成一个新的切片;
2.从切片生成一个新的切片;
3.直接生成一个新的切片。

从数组生成一个新的切片

格式:

slice [开始位置:结束位置]

从数组str生成一个新的切片sli,再使用len()函数来获得当前切片长度,cap()函数来获得当前切片容量。

package main
import "fmt"

func main(){
    str := [...]string{"Tom","Ben","Peter"}
    sli := str[0:2]
    fmt.Println("数组str: ",str," len(str) = ",len(str)," 内存地址: ",&str[0])
    fmt.Println("切片sli: ",sli," len(sli) = ",len(sli)," 内存地址: ",&sli[0]," 容量: ",cap(sli))
}

执行结果:

数组str:  [Tom Ben Peter]  len(str) =  3  内存地址:  0xc000116510
切片sli:  [Tom Ben]  len(sli) =  2  内存地址:  0xc000116510  容量:  3

从数组或切片生成新的切片拥有如下特性:

  • 取出的元素数量为:结束位置 - 开始位置;
  • 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取;
  • 当缺省开始位置时,表示从连续区域开头到结束位置;
  • 当缺省结束位置时,表示从开始位置到整个连续区域末尾;
  • 两者同时缺省时,与切片本身等效;
  • 两者同时为 0 时,等效于空切片,一般用于切片复位。
  • 新生成的切片是对现有数组或切片的引用,其地址与截取的数组或切片开始位置对应的元素地址相同。
  • 新生成的切片容量指从切片的起始元素开始到其底层数组中的最后一个元素的个数。

根据索引位置取切片 slice 元素值时,取值范围是(0~len(slice)-1),超界会报运行时错误,生成切片时,结束位置可以填写 len(slice) 但不会报错。

从切片生成一个新的切片

借助str数组先生成一个新切片sli,再从此切片生成一个新的切片newSli

package main
import "fmt"

func main(){
    str := [...]string{"Tom","Ben","Peter"}
    sli := str[0:2]
    newSli := sli[0:1]
    fmt.Println("数组str: ",str," len(str) = ",len(str)," 内存地址: ",&str[0])
    fmt.Println("切片sli: ",sli," len(sli) = ",len(sli)," 内存地址: ",&sli[0]," 容量: ",cap(sli))
    fmt.Println("切片newSli: ",newSli," len(newSli) = ",newSli," 内存地址: ",&newSli[0]," 容量: ",cap(newSli))
}

执行结果:

数组str:  [Tom Ben Peter]  len(str) =  3  内存地址:  0xc000116510
切片sli:  [Tom Ben]  len(sli) =  2  内存地址:  0xc000116510  容量:  3
切片newSli:  [Tom]  len(newSli) =  1  内存地址:  0xc000116510  容量:  3

直接生成一个新的切片

1.声明格式:

var 切片变量名 []元素类型

切片不需要说明长度。
或直接使用make()函数来创建切片:

var sli []type = make([]type, len)
//也可以简写为
sli := make([]type, len)
package main
import "fmt"

func main(){
    var sli []int
    fmt.Println("sli:",sli,"len(sli) =",len(sli),"容量 :",cap(sli),"sli == nil ?",(sli == nil))
}

执行结果:

sli: [] len(sli) = 0 容量 : 0 sli == nil ? true

2.切片初始化

声明同时初始化
package main
import "fmt"

func main(){
    var sli = []int{1,2,3,4}
    fmt.Println("sli:",sli,"len(sli) =",len(sli),"容量 :",cap(sli),"sli == nil ?",(sli == nil))
}

执行结果:

sli: [1 2 3 4] len(sli) = 4 容量 : 4 sli == nil ? false
使用make()函数初始化

声明完切片后,可以通过内建函数make()来初始化切片,格式:

切片名 := make([]元素类型,切片长度,切片容量)
sli := make([]T,len,capacity)
package main
import "fmt"

func main(){
    var sli = make([]int,4,6)
    //sli := make([]int,4,6)
    fmt.Println("sli:",sli,"len(sli) =",len(sli),"容量 :",cap(sli),"sli == nil ?",(sli == nil))
}

执行结果:

sli: [0 0 0 0] len(sli) = 4 容量 : 6 sli == nil ? false

我们可以发现sli切片在初始化后,就会自动填充0值且不再为空。

添加元素

Go语言中,我们可以使用append()函数来对切片进行元素的添加。当切片不能再容纳其他元素时(即当前切片长度值等于容量值),下一次使用append()函数对切片进行元素添加,容量会按2倍数进行扩充。

package main
import "fmt"

func main(){
    sli := make([]int,1)
    for i := 1;i < 6;i++ {
        sli = append(sli, i)
        fmt.Println("sli:",sli,"len(sli) =",len(sli),"容量: ",cap(sli))
    }
}

执行结果:

sli: [0 1] len(sli) = 2 容量:  2
sli: [0 1 2] len(sli) = 3 容量:  4
sli: [0 1 2 3] len(sli) = 4 容量:  4
sli: [0 1 2 3 4] len(sli) = 5 容量:  8
sli: [0 1 2 3 4 5] len(sli) = 6 容量:  8

修改程序

package main
import "fmt"

func main(){
    var str = [...]string{"Tom","Ben","Peter"}
    var sli = str[0:1]//从str数组生成切片sli
    fmt.Println("str数组:",str)
    fmt.Println("sli切片:",sli,"切片容量为:",cap(sli))
    sli = append(sli,"Danny")//对student1切片的元素添加,会覆盖引用数组对应的元素
    fmt.Println("扩充Danny后的sli切片:",sli,",切片长度为:",len(sli),",切片容量为:",cap(sli))
    fmt.Println("扩充Danny后的str数组:",str)
}

执行结果:

str数组: [Tom Ben Peter]
sli切片: [Tom] 切片容量为: 3
扩充Danny后的sli切片: [Tom Danny] ,切片长度为: 2 ,切片容量为: 3
扩充Danny后的str数组: [Tom Danny Peter]

由于sli切片是从str数组生成(即对str数组的引用),为sli添加元素会覆盖str数组中对应的元素。
所以,如果切片是从其他数组或切片生成,新切片的元素添加需要考虑对原有数组或切片中数据的影响。

删除元素

由于Go语言没有为删除切片元素提供方法,所以需要我们手动将删除点前后的元素连接起来,从而实现对切片中元素的删除。

package main
import "fmt"

func main(){
    var sli = []string{"Tom","Ben","Peter","Danny"}
    fmt.Println("sli切片长度:",len(sli))
    sli = append(sli[0:1],sli[2:]...)
    fmt.Println("sli切片:",sli)
    fmt.Println("sli切片长度:",len(sli))
    fmt.Println("sli切片容量:",cap(sli))
}

执行结果:

sli切片长度: 4
sli切片: [Tom Peter Danny]
sli切片长度: 3
sli切片容量: 4

其中append()函数中传入的省略号代表按sli切片展开,该行代码等价于:

sli = append(sli[0:1],sli[2],sli[3])

如果需要清空切片中的所有元素,可以把切片的开始下标和结束下标都设为0来实现:

package main
import "fmt"

func main(){
    var sli = []string{"Tom","Ben","Peter","Danny"}
    fmt.Println("sli切片长度:",len(sli))
    sli = sli[0:0]
    fmt.Println("sli切片:",sli)
    fmt.Println("sli切片长度:",len(sli))
    fmt.Println("sli切片容量:",cap(sli))
}

执行结果:

sli切片长度: 4
sli切片: []
sli切片长度: 0
sli切片容量: 4

遍历

切片的遍历和数组类似,可以通过切片下标来进行遍历。切片下标同样从0开始,第一个元素的数组下标为0,第二个元素的数组下标为1,以此类推。

package main
import "fmt"

func main(){
    var sli = []string{"Tom","Ben","Peter","Danny"}
    for index,value := range sli {
	fmt.Println("index:",index,"-","value:",value)
    }    
}

执行结果:

index: 0 - value: Tom
index: 1 - value: Ben
index: 2 - value: Peter
index: 3 - value: Danny