Listening to the Words

Golang学习之切片(slice)

今天上午买了一根很长的火腿肠(sausage),大概有30cm那么长一次吃不完,只能切来吃(slice).
从左面切,从右面切还是从中间切?
这是个问题?
slice from the left or right ?
this is the question

数组局限性

在go中因为数组的长度是固定的并且使用时要计算其长度,因此它有很多局限性。

func arraySum(x [5]int) int{
    sum := 0
    for _, v := range x{
        sum = sum + v
    }
    return sum
}

这个求和函数只能接受[5]int类型数组,其他都不支持

 arr:=[5]{1,2,3,4,5}

数组arr中已经有5个元素了,就不能再向数组中添加新的元素了

这….充满了局限性,相比于PHP的数组显得有点难以满足使用需求

于是就有了 slice

切片 slice

切片本质上是对数组的一层封装,是的数组拥有可变长度序列,灵活和支持自动扩容

切片的定义

声明切片:

func main() {
    var slice = []int{1, 2, 3, 4}
    city := []string{"北京", "东京", "南京", "西京"}
    fmt.Println(slice)
    fmt.Println(city)
    }

切片的长度和容量

切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。

基于数组定义切片

func main() {
    var slice = []int{1, 2, 3, 4}
    city := []string{"北京", "东京", "南京", "西京"}
    fmt.Println(slice)
    fmt.Println(city)
    //长度和容量
    fmt.Println(len(slice), len(city))
    fmt.Println(cap(slice), cap(city))

    a1 := [...]int{1, 3, 5, 7, 9, 11, 13}

    s1 := a1[0:4]   //从0切到4但不包含4 (左闭右开)
    fmt.Println(s1) //[1 3 5 7]

    s2 := a1[:6]    // 从0 切到 6 不包含6
    fmt.Println(s2) //[1 3 5 7 9 11]

    s3 := a1[3:]    // 从0 切到 6 不包含6
    fmt.Println(s3) //[7 9 11 13]

    //切片的长度是切完的数组长度,容量是底层数组的长度(php中array_slice())

}

切片再切片

    //切片再切片
    a := [...]string{"北京", "上海", "广州", "深圳", "成都", "重庆"}
    fmt.Printf("a:%v type:%T len:%d  cap:%d\n", a, a, len(a), cap(a))
    b := a[1:3]
    fmt.Printf("b:%v type:%T len:%d  cap:%d\n", b, b, len(b), cap(b))
    c := b[1:5]
    fmt.Printf("c:%v type:%T len:%d  cap:%d\n", c, c, len(c), cap(c))

输出:

    a:[北京 上海 广州 深圳 成都 重庆] type:[6]string len:6  cap:6
    b:[上海 广州] type:[]string len:2  cap:5
    c:[广州 深圳 成都 重庆] type:[]string len:4  cap:4

注意: 对切片进行再切片时,索引不能超过原数组的长度,否则会出现索引越界的错误。

使用make()函数构造一个切片

上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的make()函数,格式如下:

    make([]T,size,cap)
  • T切片的元素类型
  • size切片中元素的数量
  • cap切片的容量
 a:=make([]string, 2,10)
 fmt.Println(a)
 fmt.Println(len(a))
 fmt.Println(cap(a))

上面代码中a的内部存储空间已经分配了10个,但实际上只用了2个。 容量并不会影响当前元素的个数,所以len(a)返回2,cap(a)则返回该切片的容量。

切片的本质

切片的本质就是底层元素数组的封装,包含了三个信息: 底层数组的指针,切片的长度(len),切片的容量(cap)

扩展学习链接: 切片的本质

切片的判断

要检查切片是否为空,请始终使用len(s) == 0来判断,而不应该使用s == nil来判断。

判断切片的长度

切片不能直接比较

切片不能直接比较,唯一可以比较的操作时和nil比较

var s1 []int         //len(s1)=0;cap(s1)=0;s1==nil
s2 := []int{}        //len(s2)=0;cap(s2)=0;s2!=nil
s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil

切片的复制拷贝

下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

func main() {
    s1 := make([]int, 3) //[0 0 0]
    s2 := s1             //将s1直接赋值给s2,s1和s2共用一个底层数组
    s2[0] = 100
    fmt.Println(s1) //[100 0 0]
    fmt.Println(s2) //[100 0 0]
}

切片的遍历

和数组相同

append()方法为切片添加元素

Go语言的内建函数append()可以为切片动态添加元素。 可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素(后面加…)。

func main(){
    var s []string //直接使用,无需初始化
    s = append(s, "新垣结衣")               
    s = append(s, "长泽雅美", "石原里美", "佐佐木希") 
    s2 := []string{"广末凉子", "铃木杏", "中岛美嘉"}
    s = append(s, s2...) // 支持追加多个元素
    fmt.Println(s) //[新垣结衣 长泽雅美 石原里美 佐佐木希 广末凉子 铃木杏 中岛美嘉]
}

注意:通过var声明的零值切片可以在append()函数直接使用,无需初始化
并且支持追加多个元素

s := []int{}  // 没有必要初始化
s = append(s, 1, 2, 3)
var s = make([]int)  // 没有必要初始化
s = append(s, 1, 2, 3)

非引用切片(copy)

在php中数组是赋值类型:

$a=[1,2,3]
$b=$a;
$b[]=4;
print_r($a); //[1,2,3]
print_r($b)//[1,2,3,4]

go中切片是引用类型,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。

Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:

copy(destSlice, srcSlice []T)

其中:

  1. srcSlice: 数据来源切片
  2. destSlice: 目标切片
func main() {
    // copy()复制切片
    a := []int{1, 2, 3, 4, 5}
    c := make([]int, 5, 5)
    copy(c, a)     //使用copy()函数将切片a中的元素复制到切片c
    fmt.Println(a) //[1 2 3 4 5]
    fmt.Println(c) //[1 2 3 4 5]
    c[0] = 1000
    fmt.Println(a) //[1 2 3 4 5]
    fmt.Println(c) //[1000 2 3 4 5]
}

从切片中删除元素

Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:

func main() {
    // 从切片中删除元素
    a := []int{30, 31, 32, 33, 34, 35, 36, 37}
    // 要删除索引为2的元素
    a = append(a[:2], a[3:]...)
    fmt.Println(a) //[30 31 33 34 35 36 37]
}

总结一下就是:要从切片a中删除索引为index的元素,操作方法是a = append(a[:index], a[index+1:]...)

点赞