Golang学习记录10——切片(slice)(注意append)

切片的引入

  1. 切片(slice)是golang中一种特有的数据类型
  2. 数组有特定的用处,但是却有一些呆板(数组长度固定不可变当然可以使用函数添加),所以在Go语言的代码里并不是特别常见。相对的切片却是随处可见的,切片是一种建立在数组类型之上的抽象,它构建在数组之上并且提供更强大的能力和便捷。
  3. 切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。
ackage main

import "fmt"

func main()  {
	//首先定义数组
	arr := [...]int{1,2,3,4,5,6}

	//切片:够建在数组之上
	var slice1, slice2 []int          //切片长度可变
	slice1 = arr[1:3]   //切出一个片段,索引从1开始,3位终止索引,不包含
	slice3 := arr[1:4] 
	slice2 = arr[:]    //整个数组
	fmt.Println(slice1)
	fmt.Println(slice2)
	fmt.Println(slice3)
	//类型
	fmt.Printf("arr类型:%T\n",arr)
	fmt.Printf("slice1类型:%T\n",slice1)
	fmt.Printf("slice2类型:%T\n",slice2)
	fmt.Printf("slice3类型:%T\n",slice3)
	//获取切片容量——可以动态变化
	fmt.Println("slice1容量:",cap(slice1))
	fmt.Println("slice2容量:",cap(slice2))
	fmt.Println("slice3容量:",cap(slice3))

}
[2 3]
[1 2 3 4 5 6]
[2 3 4]
arr类型:[6]int
slice1类型:[]int
slice2类型:[]int
slice3类型:[]int
slice1容量: 5
slice2容量: 6
slice3容量: 5
  • 首先对切片的赋值是建立在数组上的,通过指定索引,如果不指定默人开头和结尾(包含最后元素),但不管是整个数组还是片段,都需要使用[:]格式
  • 通过输出我门可以看见,数组类型为[长度]数据类型,切片为[]数据类型,所以不指定长度
  • 切片的容量大小是动态变化的,且不一定等于元素的大小。

内存分析

切片再底层由3个字段的数据结构:指向底层数组的指针、切片长度、切片容量
在这里插入图片描述

	//地址关系
	fmt.Printf("数组1的地址:%p\n",&arr[1])
	fmt.Printf("切片0位置的地址:%p\n",&slice1[0])
	fmt.Printf("切片变量位置的地址:%p\n",&slice1)

	//切片改变数组值
	arr[1] = 11
	slice1[1] = 10
	fmt.Println(arr)
	fmt.Println(slice1)
数组1的地址:0x14000118008
切片0位置的地址:0x14000118008
切片0位置的地址:0x1400011a000
[1 11 10 4 5 6]
[11 10]

由程序可以看出:

  • 切片变量结构体实际上是一个单独的变量,该结构体变量由上面的3个部分组成
  • 切片的指针指向引用数组的元素地址上
  • 切片这个结构体中指针指向的是引用的数组地址,但不是说切片就是一个指针,他是一个结构题
  • 切片为引用数据类型,通过改变切片元素的值,可以改变数组的值,当然,作为底层数据,改变数组的值,切片引用的是数组空间存储的值,那么切片值也会变。

切片的定义

  1. 定义一个切片,然后让切片去引用一个已经创建好的数组。
//方式1:通过引用数组定义
	arr := [...]int {1,2,3,4,5,6,7}
	//引用全部
	slice1 := arr[:]
	//引用片段
    slice2 := arr[1:3]
	fmt.Println(slice1)
	fmt.Println(slice2)
[1 2 3 4 5 6 7]
[2 3]
  1. 通过make内置函数来创建切片。make()底层创建一个数组,对外不可见,所以不能直接操作这个数组,需要使用slice去间接访问各元素。基本语法:make(数据类型, 长度 , [容量])
    请添加图片描述
//方式2:通过make内置函数创建-可不指定容量(只会默认长度容量)
	slice3 := make([]int, 4)
	slice4 := make([]int, 6, 8)
	fmt.Println(cap(slice3))
	fmt.Println(cap(slice4))
	fmt.Println(slice3)
	fmt.Println(slice4)
4
8
[0 0 0 0]
[0 0 0 0 0 0]
  1. 定一个切片,直接就指定具体数组,使用原理类似make的方式。
//方式3:定义一个切片,直接就指定一个具体的数组——和方式2一样,不能直接操作着这个数组
	slice5 := []int {1,2,3}
	fmt.Println(slice5 )
	fmt.Println(cap(slice5))
[1 2 3]
3

所以,不管是什么方式,都是对数组的引用,使用make是创建一个数组空间,方式3是直接创建一个具体的数组。后两种方式只能使用slice去维护/操作数组,第一种方式可以通过数组直接操作。

切片相较于数组,主要区别在于亮点:1.数组需要固定长度,切片则不需要指定。2.数组为值类型,切片为引用类型

切片的遍历

  • 普通for循环
  • for-range循环
package main

import "fmt"

func main()  {
	//slice traceral

	//slice definition
	slice := []int{1,2,3,4,5}

	//方式1:for
	for i := 0; i < len(slice); i++ {
		fmt.Printf("alice[%v]=%v\t",i,slice[i])
	}

	//方式2:for-range
	fmt.Println("\n-------------------for-range-------------------")
	for i, v := range slice {
		fmt.Printf("alice[%v]=%v\t",i,v)
	}
}
alice[0]=1      alice[1]=2      alice[2]=3      alice[3]=4      alice[4]=5
-------------------for-range-------------------
alice[0]=1      alice[1]=2      alice[2]=3      alice[3]=4      alice[4]=5   

切片注意事项

  1. 切片在定义之后不能直接使用,需要让其引用到一个数组
var slice []int
fmt.Println(slice)
[]
  1. 切片使用不能越界
var slice []int
	slice = []int{1,2,3,4}
	fmt.Println(slice[3])
	fmt.Println(slice[4]) //越界:index out of range [4] with length 4
4
panic: runtime error: index out of range [4] with length 4

goroutine 1 [running]:
main.main()
        /Users/dearfriend/golang/code/chapter8_slice/demo4/main.go:8 +0x88
exit status 2
  1. 切片简写使用:arr[:]索引省略
  2. 切片可以继续切片(对切片进行切片)切片都是指向数组,所以改变切片的值,其指向空间的值改变,对应的,指向该空间的所有切片都会改变
	var slice []int
	slice = []int{1,2,3,4}
	//对切片进行切片
	slice2 := slice[1:2]

	fmt.Println(slice)
	fmt.Println(slice2)
	//改变二次切片的值,所有值都变
	slice2[0] = 9
	fmt.Println(slice)
	fmt.Println(slice2)
[1 2 3 4]
[2]
[1 9 3 4]
[9]
  1. 切片可以动态增长:使用append函数进行增加
    append函数在底层实际上是创建了一个新的数组,append(slice1,1,2,3)表示创建一个新的数组,将原有元素放入,在将添加的元素加在后面 ,所以,原来的slice所指向的空间还是不变,不会指向新的数组。可以使用slice = append(slice,1,2).append函数原数据智能传入切片——老数组扩容成新数组(切片指向新数组,不再指向原数组,原数组不变) 是否创建新数组,是否改变原数组取决于切片容量,见PS
    那么定义的新数组,和前面说的make定义一样,没有数组名,只能间接使用切片进行数组的维护
    请添加图片描述
	//切片动态增长
	fmt.Println("================切片动态增长===============")
	fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
	fmt.Printf("原切片指向的地址:%p\n",&slice[0])
	fmt.Println("-----------------------------------------")

	//slice追加元素
	slice = append(slice,9,8,10,11) 
	fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
	fmt.Printf("新切片指向的地址:%p\n",&slice[0])
================切片动态增长===============
slice的长度为:3         slice的容量为:5
原切片指向的地址:0x14000016098
-----------------------------------------
slice的长度为:7         slice的容量为:10
新切片指向的地址:0x1400001a050

还可以添加切片

//切片动态增长
	fmt.Println("================切片动态增长===============")
	fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
	fmt.Printf("原切片指向的地址:%p\n",&slice[0])
	fmt.Println("-----------------------------------------")

	//slice追加元素
	slice = append(slice,9,8,10,11) 
	fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
	fmt.Printf("新切片指向的地址:%p\n",&slice[0])
	fmt.Println("-----------------------------------------")

	//slice追加切片
	slice1 := []int{1,2,3}
	slice = append(slice,slice1...)
	fmt.Printf("追加slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
	fmt.Printf("新切片指向的地址:%p\n",&slice[0])
================切片动态增长===============
slice的长度为:3         slice的容量为:5
原切片指向的地址:0x140000b0008
-----------------------------------------
slice的长度为:7         slice的容量为:10
新切片指向的地址:0x140000b6000
-----------------------------------------
追加slice的长度为:10    slice的容量为:10
新切片指向的地址:0x140000b6000

PS:如果添加的元素超过原切片的容量,那么会创建新数组,且切片指向新的数组;如果没有超过原有切片的容量,那么,则会在原切片上增加元素,并且所引用的数组也会改变

package main 
import "fmt"
func main ()  {
	arr := [...]int{1,2,3,4,5,6}
	var slice []int
	slice = arr[1:4]
	//对切片进行切片
	slice2 := slice[1:2]

	fmt.Println(slice)
	fmt.Println(slice2)
	//改变二次切片的值,所有值都变
	slice2[0] = 9
	fmt.Println(slice)
	fmt.Println(slice2)

	//切片动态增长
	fmt.Println("================切片动态增长===============")
	fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
	fmt.Printf("原切片为:%v\n",slice)
	fmt.Printf("原数组为:%v\n",arr)

	fmt.Printf("原切片指向的地址:%p\n",&slice[0])
	fmt.Println("-----------------------------------------")

	//slice追加元素
	slice = append(slice,9,8) 
	fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
	fmt.Printf("新切片指向的地址:%p\n",&slice[0])
	fmt.Println("-----------------------------------------")
	fmt.Printf("新切片为:%v\n",slice)
	fmt.Printf("新数组为:%v\n",arr)
}
================切片动态增长===============
slice的长度为:3         slice的容量为:5
原切片为:[2 9 4]
原数组为:[1 2 9 4 5 6]
原切片指向的地址:0x140000b0008
-----------------------------------------
slice的长度为:5         slice的容量为:5
新切片指向的地址:0x140000b0008
-----------------------------------------
新切片为:[2 9 4 9 8]
新数组为:[1 2 9 4 9 8]
	//切片动态增长
	fmt.Println("================切片动态增长(超过切片容量)===============")
	fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
	fmt.Printf("原切片为:%v\n",slice)
	fmt.Printf("原数组为:%v\n",arr)

	fmt.Printf("原切片指向的地址:%p\n",&slice[0])
	fmt.Println("-----------------------------------------")

	//slice追加元素
	slice = append(slice,9,8,10) 
	fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
	fmt.Printf("新切片指向的地址:%p\n",&slice[0])
	fmt.Println("-----------------------------------------")
	fmt.Printf("新切片为:%v\n",slice)
	fmt.Printf("新数组为:%v\n",arr)
================切片动态增长(超过切片容量)===============
slice的长度为:3         slice的容量为:5
原切片为:[2 9 4]
原数组为:[1 2 9 4 5 6]
原切片指向的地址:0x140000b0008
-----------------------------------------
slice的长度为:6         slice的容量为:10
新切片指向的地址:0x140000b6050
-----------------------------------------
新切片为:[2 9 4 9 8 10]
新数组为:[1 2 9 4 5 6]

切片初始容量实际上是通过数组长度决定的,如果一个数组长度为6,切片从1开始切,则切片会留下剩下的数组长度作为容量

	arr := [...]int{1,2,3,4,5,6,7}
	var slice []int
	slice = arr[1:4]
	fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
slice的长度为:3         slice的容量为:6
	arr := [...]int{1,2,3,4,5,6}  //减少一个数组长度
	var slice []int
	slice = arr[1:4]
	fmt.Printf("slice的长度为:%v\t slice的容量为:%v\n",len(slice),cap(slice))
slice的长度为:3         slice的容量为:6

所以着也能解释为什么使用slice := []int{1,2,3,4}这样定义slice,其长度为4,因为数组的长度只有4,且此处,引用的整个数组。

  1. 切片的拷贝——copy函数
    请添加图片描述
//切片拷贝
	fmt.Println("================切片拷贝===============")
	//创建新的切片空间
	slice3 := make([]int,10)
	//拷贝slice
	copy(slice3,slice)
	fmt.Println(slice3)
================切片拷贝===============
[2 9 4 9 8 10 0 0 0 0]
  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值