切片
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循环来遍历切片。