一、说明

切片(slice)是golang里面的可变长元素类型,不是数组或数组指针,本质是一个结构体。

slice的声明如下(位于安装目录下的src/runtime/slice.go ):

type slice struct {
    array unsafe.Pointer //array是一个指针,指向实际的数据地址。
    len   int //slice的长度
    cap   int //slice的容量
}

值得注意的是,切片除了有一个len 属性表明当前切片长度以外,还有一个容量标识cap ,表示切片的最大容量,len 小于等于cap 。查看长度和容量的方法很简单,直接使用len()cap() 即可。

二、创建方式

1.直接创建

s := []int{1, 2, 3, 4, 5} //len(s)=5,cap(s)=5

也可以使用索引号指定

s := []int{1, 2, 3, 8:999} //len(s))=9, cap(s)=9, s:[1 2 3 0 0 0 0 0 999]

2.基于数组创建

通过给定数组的下标范围来创建切片,创建的时候,切片长度等于我们实际赋值的长度,容量默认等于数组第一个被赋值的元素最后一个元素 的距离:

data := [...]int{0, 1, 8, 3, 4, 5, 6, 7}
s_data := data[1:5] //len(s_data)=4, cap(s_data)=7, s_data:[1, 8, 3, 4]

这个距离也可以显式指定:

data := [...]int{0, 1, 8, 3, 4, 5, 6, 7}
s_data := data[1:5:5] //len(s_data)=4, cap(s_data)=4, s_data:[1, 8, 3, 4]

s_data := data[x:y:z] 的含义如下:

  • x表示赋值的起始索引,从x开始赋值。
  • y表示赋值的结束索引,长度就是x-y的距离。
  • z表示容量最大可以到的索引,x-z的距离是容量。

通过数组创建的切片和数组共享同一块内存区域,修改切片的时候数组也会随之改变:

arr := [...]int{1, 2, 3, 4, 5}
s := arr[:4]
fmt.Println(s)  // [1 2 3 4]
s[1] = 99
fmt.Println(s[1], arr[1])  //99 99

3.使用make创建

语法形式是make([]Type, Len, Cap), Type是想要创建的切片类型,Len 是切片长度,Cap 是切片容量。

s := make([]int, 6, 8)
fmt.Println(len(s), cap(s)) //6, 8

cap不是必需的,可以省略不写,默认等于len:

s := make([]int, 6)
fmt.Println(len(s), cap(s)) //6, 6

三、使用示例

package main
import "fmt"
func main(){
    s := []int{1, 2, 3, 4, 5, 6} //len(s))=9, cap(s)=9
    fmt.Println(s[0], s[1]) //直接使用索引
    fmt.Println(s[2:3]) //索引范围[2, 3)
    fmt.Println(s[4:]) //[4, last]
    fmt.Println(s[:6]) //[start, 6)
    s2 := s[1:5]
    fmt.Println(s2)
}

输出:

1 2
[3]
[5 6]
[1 2 3 4 5 6]
[2 3 4 5]

四、和数组定义的区别

1.数组的定义:

s := [3]int{1, 2, 3}

s := [...]int{1, 2, 3}

2.切片的定义:

s := []int{1, 2, 3}

s := make([]s, 3)

五、注意事项

切片本质是一个结构体,使用时拷贝传递。在使用切片做函数形参时,会创建一个临时变量,但内部的数据还是不变。

package main
import "fmt"
func f(a []int){
    fmt.Printf("%p %p %p", &a, &a[0], &a[1])
}
func main(){
    s := []int{1, 2, 3, 4, 5, 6}
    fmt.Printf("%p %p %p", &s, &s[0], &s[1])
    f(s)
}

输出:

0xc04203e400 0xc04203bf50 0xc04203bf58
0xc04203e460 0xc04203bf50 0xc04203bf58

可以看到s的地址变了,但是内部s[0] , s[1] 的地址还是一样的,说明在创建副本的时候只是简单拷贝了切片的值,并没有把内部的内存空间重新分配。所以当我们使用range 或者把切片作为函数形参时在代码块内部直接就会修改切片的值。

但是在要注意的是在把切片作为函数形参时,在函数内部使用append 就不会对切片产生影响:

package main
import (
    "fmt"
)
func f(a []string){
    a = append(a, "hello")
}
func main(){
    a := make([]string, 0, 4)
    f(a)
    fmt.Println(a) //输出[],而不是[hello]
}

原理很简单,因为append 会返回一个新的slice给a ,而as 的一个副本,所以这里对s 就不会有任何影响,此时要想修改s 可以把切片改成指针类型:

package main
import (
    "fmt"
)
func f(a *[]string){
    *a = append(*a, "hello")
}
func main(){
    a := make([]string, 0, 4)
    f(&a)
    fmt.Println(a) //[hello]
}
最后修改:2017 年 08 月 26 日
如果觉得我的文章对你有用,请随意赞赏