【Go学习笔记4】切片

282 阅读4分钟

切片

go语言中的切片,类似于动态数组的概念。因为数组的长度不可变,那么对于可变长度的数据,就需要使用切片来存储。需要注意的是,切片的底层任然是使用数组来存储的,只不过切片当中有一个变量,是指向底层数组的指针(学过C语言的同学应该知道指针的概念)。

切片的数据结构

|-----|-----|-----|

| 地址指针 |长度 | 容量 |

包括3个属性,第一个是指向底层存储数据的数组的指针,第二个是当前切片的长度,第三个是切片最大可以容纳的元素个数。

切片声明

使用make函数创建切片:

slice := make([]string,5)

上面的代码表示创建一个字符串切片,切片的长度和容量都是5个元素。

  • 长度就是当前切片中拥有的元素个数
  • 容量表示切片最大容许拥有的元素个数

可以分别指定长度和容量大小

slice := make([]string,5,10)

上面的代码表示,创建一个大小为5,容量为10的字符串切片。

注意:长度不能大于容量,否则会在编译的时候报错。

切片字面量

另一种声明切片的方式是使用切片字面量:

slice := []string{"one","two","three"}

上面的代码表示,创建一个长度和容量都为3的字符串切片。这种方式和数组的唯一区别就是[]中没有数字,如果里面指定了数字的话,创建的就是数组,而不是切片。

slice := []bool{false,true,false}

上面的代码表示,创建一个长度和容量都为3的boolean切片。

空切片和nil切片

package main

import (
	"fmt"
)

func main(){
	var slice []string

	fmt.Println(slice == nil)
}
//true

上面的代码表示创建一个nil切片,此时slice的值为nil,表示变量声明了但是没有初始化。

package main

import (
	"fmt"
)

func main(){
	slice := []string{}

	fmt.Println(slice == nil)
}
//false

上面的代码表示创建了一个空切片,空切片的长度和容量都是0,但是slice的值

package main

import (
	"fmt"
)

func main(){
	slice := []string{}
	slice = append(slice,"one")
	fmt.Println(slice)
}
//[one]
package main

import (
	"fmt"
)

func main(){
	var slice []string
	slice = append(slice,"one")
	fmt.Println(slice)
}
//[one]

看上面的两段代码,奇怪的是,nil切片和空切片都可以使用append函数对切片添加元素,但是在上面的代码中nil的切片slice=nil,而空切片slice!=nil。这通常可以理解为nil切片表示一个不存在的切片,而空切片表示的是切片存在,但是切片不包含任何元素。然而对于两者的append操作,效果却是一样的。

使用切片

slice := []string{"one","two"}
slice[0] = "newOne"

上面的代码表示修改切片中第一个元素的值为"newOne"。会发现切片修改和数组的修改形式是一样的。更加体现了切片的底层是数组的这个设计。

slice[3] = "three"
//runtime error: index out of range

尝试将上面的切片修改第4个值,会出错。因为上面的切片长度和容量都是2。

package main

import (
	"fmt"
)

func main(){
	slice := []string{"one","two"}
	slice[0] = "newOne"

	//使用append增加元素
	slice = append(slice,"three")

	fmt.Println(slice)
}
//[one two three]

上面的代码表示向slice中添加一个元素。此时切片的长度=3,容量=4。

注意:在向切片中追加元素的时候,如果切片容量不够,会自动扩充切片的容量,当切片的容量小于1000的时候,总是会成倍的增加容量,如果超过1000,则会在原来容量的基础上乘以1.25。

package main

import (
	"fmt"
)

func main(){
	slice1 := []int{1,2,3,4}
	slice2 := []int{5,6,7,8}
  //将切片slice2中的所有元素追加到slice1中
	slice1 = append(slice1,slice2...)

	fmt.Println(slice1)
}
//[1 2 3 4 5 6 7 8]

看到上面的代码,有没有发现和js的数组的concat操作是一样的效果?

从切片创建切片

package main

import (
	"fmt"
)

func main(){
	slice := []int{1,2,3,4,5}

	//从slice中创建一个新的切片
	slice2 := slice[0:2:3]

	fmt.Println(slice2)
}
//[1 2]

上面的代码表示从切片中创建切片,slice[0:2:3]第一个0表示起始位置,第二个2表示从新切片中包含的元素个数,第三个3表示新切片的容量。第三个值也可以不指定,如果不指定,新切片的容量=slice切片的容量-起始位置(这里是0)

切片遍历

package main

import (
	"fmt"
)

func main(){
	slice1 := []int{1,2,3,4}
	slice2 := []int{5,6,7,8}
	slice1 = append(slice1,slice2...)

	for index , value := range slice1{
		fmt.Println("index=",index,",value=",value)
	}
}
// index= 0 ,value= 1
// index= 1 ,value= 2
// index= 2 ,value= 3
// index= 3 ,value= 4
// index= 4 ,value= 5
// index= 5 ,value= 6
// index= 6 ,value= 7
// index= 7 ,value= 8

上面的代码使用range关键字来遍历切片,range在每一次的迭代中返回两个变量,第一个表示当前操作的元素的位置索引,第二个表示当前的元素值。

package main

import (
	"fmt"
)

func main(){
	slice1 := []int{1,2,3,4}
	slice2 := []int{5,6,7,8}
	slice1 = append(slice1,slice2...)

	for index :=0 ; index < len(slice1) ; index++{
		fmt.Println("index=",index,",value=",slice1[index])
	}
}
// index= 0 ,value= 1
// index= 1 ,value= 2
// index= 2 ,value= 3
// index= 3 ,value= 4
// index= 4 ,value= 5
// index= 5 ,value= 6
// index= 6 ,value= 7
// index= 7 ,value= 8

上面的代码使用传统的for循环来遍历切片。