文章目录
- 1. 概念
- 2. 切片的基本操作
- 1) 切片生成方式
- a. 从数组或者切片上切的生成方式
- b. 直接声明切片 : var name []Type
- 2)make()函数构造切片
- 3)切片扩容append ()方法
- 5) 删除元素
- 4)切片遍历
- 3. 关于数据类型的补充
- 1)值类型
- 2)引用类型
- 3) 切片是个什么类型
- 4. 关于切片的原理
- 总结
1. 概念
切片是一个动态数组,因为数组的长度是固定的,所以操作起来很不方便,比如一个names数组,我想增加一个学生姓名都没有办法,十分不灵活。所以在开发中数组并不常用,切片类型才是大量使用的。
切片可以动态的扩容,切片的类型和长度无关。
相当于java中的 ArrayList
需要注意的是: 切片是一个引用类型,不存在具体的值。
a := [3]int{1,2,3}
这是个数组,一共3个元素,不能在添加元素了。 而切片可以添加元素。
2. 切片的基本操作
1) 切片生成方式
切片的生成方式有两种:
a. 从数组或者切片上切
b. 直接声明切片 : var name []Type
ame:表示变量名
T:表示切片中的元素类型,
举例:
package main
import "fmt"
func main() {
var a []string
var b = []int{}
var c = []bool{false, true}
var d = []bool{false, true}
fmt.Println(a, b, c) //[] [] [false true]
fmt.Println(a == nil) //true
fmt.Println(b == nil) //false
fmt.Println(c == nil) //false
/// fmt.Println(c == d) 这行报错,切片是引用类型,不支持直接比较,只能和nil比较
}
切片的长度和容量
切片拥有自己的长度和容量,我们可以通过使用内置的len()函数求长度,使用内置的cap()函数求切片的容量。
a. 从数组或者切片上切的生成方式
package main
import (
“fmt”
“reflect”
)func main() {
var arr = [6]int{10, 11, 12, 13, 14, 15}
var s1 = arr[1:4] // 4-1有三个元素
fmt.Println(s1, reflect.TypeOf(s1))
}
执行结果:
[ ] int 这种叫切片
[4]int 这种叫数组
这里有个问题:
当我把一个数据改了之后,其他数据会变吗?
package main
import (
"fmt"
)
func main() {
var arr = [6]int{10, 11, 12, 13, 14, 15}
var s1 = arr[1:4] // 4-1有三个元素
fmt.Println(s1)
var s2 = arr[2:4]
fmt.Println(s2)
}
这个时候打印出来:
修改其中的一个元素,来看看s1和s2会变化吗?
package main
import (
"fmt"
)
func main() {
var arr = [6]int{10, 11, 12, 13, 14, 15}
var s1 = arr[1:4] // 4-1有三个元素
fmt.Println(s1)
var s2 = arr[2:4]
fmt.Println(s2)
arr[2] = 1200
fmt.Println(s1, s2) //这两个值会变吗
}
发现s1和s2也变化了
为什么呢? 我们放在最后说这个原理的问题
b. 直接声明切片 : var name []Type
[] 中不加数字就是切片
import (
"fmt"
"reflect"
)
func main() {
var s = []int{10, 11, 12, 13, 14, 15}
fmt.Println(s, reflect.TypeOf(s))
}
问题: 切片还可以切片吗?
答案是可以, 因为数组的切片依旧可以切,现在我们直接是切片了,一样可以切
2)make()函数构造切片
如果需要动态的创建一个切片,我们就需要使用内置的make()函数
格式如下
make([]T, size, cap)
T:切片的元素类型
size:切片中元素的数量
cap:切片的容量
package main
import "fmt"
func main() {
var s4 =make([]int,3,5) //构建切片类型,s4只占3个长度,所以是0 0 0 ,这里的3是长度,5是容量
fmt.Println(s4)
fmt.Println(len(s4))
fmt.Println(cap(s4)
}
package main
import "fmt"
func main() {
var s4 = make([]int, 3, 5) //构建切片类型,s4只占3个长度,所以是0 0 0 ,这里的3是长度,5是容量
fmt.Println(s4)
s4[0] = 100
fmt.Println(s4)
fmt.Println(len(s4), cap(s4))
}
执行结果:
3)切片扩容append ()方法
上面我们已经讲过,切片作为一个动态数组是可以添加元素的,添加方式为内建方法append。
package main
import "fmt"
func main() {
var s4 = make([]int, 3, 5) //构建切片类型,s4只占3个长度,所以是0 0 0 ,这里的3是长度,5是容量
s1 := append(s4, 1)
fmt.Println(s1)
}
另外一种方式:
package main
import "fmt"
func main() {
var s []int
s1 := append(s, 1, 2, 3, 4, 5, 6)
fmt.Println(s1)
}
结果:
追加一个切片进去,需要用到… (三个点)
package main
import "fmt"
func main() {
var s []int
s1 := append(s,[]int{1,2,3,4}...)
fmt.Println(s1)
}
不加…会报错,因为我们本身定义的是int类型
总结:
var citySlice []string
// 追加一个元素
citySlice = append(citySlice, "北京")
// 追加多个元素
citySlice = append(citySlice, "上海", "广州", "深圳")
// 追加切片
a := []string{"成都", "重庆"}
citySlice = append(citySlice, a...)
fmt.Println(citySlice) //[北京 上海 广州 深圳 成都 重庆]
5) 删除元素
go语言中并没有删除切片元素的专用方法,但是我们可以用切片本身的特性来删除元素。
package main
import "fmt"
func main() {
s := []int{1, 3, 4, 5, 6}
fmt.Println(s[:2])
fmt.Println(s[3:])
s = append(s[:2], s[3:]...)
fmt.Println(s)
}
执行结果:
4)切片遍历
方法一:
package main
import "fmt"
func main() {
s := []int{1, 3, 4}
for i := 0; i < len(s); i++ {
fmt.Println(i, s[i])
}
}
方法二: 用range
package main
import "fmt"
func main() {
s := []int{1, 3, 4}
for i, v := range s {
fmt.Println(i, v)
}
}
3. 关于数据类型的补充
数据类型从存储方式分为两类:值类型和引用类型!
1)值类型
基本数据类型(int,float,bool,string)以及数组和struct都属于值类型。
特点:变量直接存储值,内存通常在栈中分配,栈在函数调用完会被释放。值类型变量声明后,不管是否已经赋值,编译器为其分配内存,此时该值存储于栈上。
当使用等号=将一个变量的值赋给另一个变量时,如 j = i ,实际上是在内存中将 i 的值进行了拷贝,可以通过 &i 获取变量 i 的内存地址。此时如果修改某个变量的值,不会影响另一个。
2)引用类型
切片是对某一个存储空间的引用,而不会赋值
指针,slice,map,chan,interface等都是引用类型。
特点:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配,通过GC回收。
变量直接存放的就是一个内存地址值,这个地址值指向的空间存的才是值。所以修改其中一个,另外一个也会修改(同一个内存地址)。
引用类型必须申请内存才可以使用,make()是给引用类型申请内存空间。
一个切片包含三个部分: 引用地址,长度,容量
3) 切片是个什么类型
切片是第一个引用类型
在不赋值的时候,他的值 默认为空
package main
import (
"fmt"
)
func main() {
var s3 []int
fmt.Println(s3)
}
可以看到切片类型没有默认值, 这个是与值类型最大的不同。
值类型默认是有值的。
当引用类型,只声明,未赋值,在内存中并不开辟空间
所以当你赋值的时候会报错。
package main
import (
"fmt"
)
func main() {
var s3 []int
fmt.Println(s3)
//s3[0]=1 这种赋值会报错,因为默认值为空
}
如果想赋值怎么办呢?
答案是使用make()函数,这个我们前面讲过了,可以再回去回顾一下。
4. 关于切片的原理
package main
import "fmt"
func main() {
s := [...]int{1, 3, 4, 5, 6, 7}
slice := s[1:3]
fmt.Println(s) //[1 3 4 5 6,7]
fmt.Println(slice) //[3 4]
fmt.Println(cap(slice)) // 切片的容量是可以动态变化的,可以理解为底层数组容量个数-1
}
接下来我们看看切片和数组之间的内存地址有什么规律
package main
import "fmt"
func main() {
s := [...]int{1, 3, 4, 5, 6, 7}
slice := s[1:3]
fmt.Println(s) //[1 3 4 5 6,7]
fmt.Println(slice) //[3 4]
fmt.Println(cap(slice)) // 切片的容量是可以动态变化的,可以理解为底层数组容量个数-1
fmt.Printf("切片为0的内存地址:%p,数组索引为1的内存地址:%p,", &slice[0], &s[1])
/*
可以看到两个地址相同 切片为0的内存地址:0xc00000a368,数组索引为1的内存地址:0xc00000a368,
*/
//如果我们该切片的值,数组的值会变化吗?答案是会
slice[0] = 100
fmt.Println(s) //,[1 100 4 5 6 7]
fmt.Println(slice) //[100 4]
}
总结
- cap是一个内置函数,用于统计切片的容量,即 最大可以存放多少个元素。
- 切片定义完之后,还不能使用,因为本身是空,需要让其引用到一个数组,或者make一个空间供切片使用
- 切片也是可以继续切片的
- 切片中append函数实际上就是对数组扩容,go会在底层创建一个新的数组,然后把原来的元素拷贝过去,在指向新的数组。当然这个新的数组对程序员不可见。
- 切片的拷贝是通过内置函数实现的,copy函数
- 切片的内置函数汇总
slice表示切片
len 获取slice的长度
cap 获取slice的最大容量
append 向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice
copy 函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数