您好,欢迎来到年旅网。
搜索
您的当前位置:首页Golang学习(十六) 切片

Golang学习(十六) 切片

来源:年旅网

为什么要切片

 基本介绍

1. 切片的英文是slice
2. 切片是数组的一个引用,因此切片是引用数据类型,在进行传递时,遵守引用传递的机制
3. 切片的使用和数组相似,遍历切片、访问切片的元素和求切片长度len(slice)都一样
4. 切片的长度是可以变化的,因此切片是一个可以动态变化数组

切片的语法定义

var 变量名 []类型
  
//和数组类似,不过不需要定义长度

基本使用

package main

import (
	"fmt"
)

func main()  {
	var intArr [5]int = [...]int{1,2,3,4,5} //这里定义一个数组

	slice := intArr[1:3]      //基于这个数组,我们生成一个切片 其中[1:3]表示数组中1、2索引位的数据,不包括3索引
	fmt.Println("intArr=",intArr)
	fmt.Println("slice 的元素是=",slice)       //切片的值
	fmt.Println("slice 的元素个数",len(slice)) //切片的长度
	fmt.Println("slice 的容量 =",cap(intArr)) //切片的容量
                                             //切片的容量一般是已经存放元素大小的一倍以上

}

返回

intArr= [1 2 3 4 5]
slice 的元素是= [2 3]
slice 的元素个数 2
slice 的容量 = 5    //切片的容量一半是已经存放元素的一倍以上

切片的内存布局

var intArr [5]int = [...]int{1,2,3,4,5}

定义了一个intArr的数组变量,会在内存空间划分一块区域

在这块区域中划分5块空间单独存储5个值

在定义切片时,他会指定数组的值的内存空间   slice := intArr[1:3]

 

package main

import (
	"fmt"
)

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

	//下面的代码可以看到,intArr数组的索引位1和切片的索引位0是相同的
	//也证明了上面是正确的
	fmt.Println(&intArr[1])
	fmt.Println(&slice[0]) 
}

返回

0xc00000e458
0xc00000e458

说明

1、slice 里面存放着3种东西
      //原数组中索引位的的内存地址
      // len = 2  存放切片的长度
      // cap=4  存放切片的总容量  一般为长度的一倍以上

2. slice 切片是一个引用类型的,和之前的值数据类型不同的是.值类型是把他当一个值作为存放的
   而切片相当于是一个软连接的存在,如果修改了slice的值,那么数组的值也会被修改

 案例

package main

import (
	"fmt"
)

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

	//修改数据
	slice[0] = 100

	fmt.Println(intArr)

}

//[1 100 3 4 5]

一、切片的使用

方法1 通过引用数组来使用切片  上面有略

方法2 通过make来创建切片

var 切片名 []type = make([],len,[cap])


#参数说明
type  //数据类型
len   //切片元素数量
cap   //指定切片容量   #可选,如果要分配了cap,则要求cap至少大于等于len

案例

package main

import (
	"fmt"
)

func main(){
	var slice []int = make([]int,4,10)  //设置切片类型为int,元素数量为4,空间大小为10,
	fmt.Println(slice)  //默认值为0 0 0 0 因为元素数量为4
	fmt.Println("sclice len=",len(slice),"slice cap",cap(slice))
	slice[0] = 100
	slice[2] = 200
	fmt.Println(slice)
}

返回

[0 0 0 0]
sclice len= 4 slice cap 10
[100 0 200 0]

小结

1. 通过make方式创建切片可以指定切片的大小和容量
2. 如果没有给切片的各个元素赋值,那么就会使用基本数据类型的默认值
3. 通过make方式创建的切片对应的数组是由make底层维护,对外不可见,只能通过slice去访问各个元素

方法3  定义一个切片,直接指定具体数组,使用类似make

package main

import (
	"fmt"
)

func main(){
	var slice []string = []string{"tom","jack","mary"}

	fmt.Println(slice)
	fmt.Println(len(slice))
	fmt.Println(cap(slice))
}

返回

[tom jack mary]
3
3  //这样定义,cap默认和元素数量一致

二、遍历切片

和数组类型,使用for循环 或者  for-range

案例 for循环

package main

import (
	"fmt"
)

func main(){
    var arr [5]int = [...]int{10,20,30,40,50}
    slice := arr[1:4]    //20,30,40
    for  i := 0; i < len(slice); i++{
        fmt.Printf("slice[%v]=%v\n",i,slice[i])
    }
}

案例2 for-range

package main

import (
	"fmt"
)

func main(){
	var arr [5]int = [...]int{10,20,30,40,50}
	slice := arr[1:4]    //20,30,40

	//for-range  (这里i可以不要)
	for i, v := range slice {
		fmt.Printf("i=%v  v=%v\n",i,v)
	}
}

三、切片的注意事项

1、切片初始化

 var slice = arr[startindex:endlindex]

   //切片初始化时,从arr数组下标为startindex,取值到下标为endindex的元素,不包含arr[endlindex]

2、切片引用数组的技巧

1. var slice = arr[0:end] 
     //可以简写成 var slice =arr[:end]

2. var slice = arr[start:len(arr)] 
     //可以简写成 var slice =arr[start:]

3. var slice = arr[0:len(arr)] 
       //可以简写: var slice=arr[:]

3、 cap函数

cap是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素


//使用方法
// cap(切片)

4、 空

切片定义完成后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,
或者说 make一个空间供切片来使用

5. 切片可以继续切片

package main

import "fmt"

func main(){

	var slice3 []int = []int{100,200,300}
	fmt.Println("slice3",slice3)

	slice3 = append(slice3, slice3...)  //给slice这个切片追加自身的切片
	                                    //... 是固定格式
	                                    //后面的必须是切片或者具体的值,不能是数组
	fmt.Println(slice3)

}

返回

slice3 [100 200 300]
[100 200 300 100 200 300]

6. 切片 append 扩展新的值

package main

import "fmt"

func main(){
    //定义数组,定义切片取值
	var shuzu [5]int = [5]int{1,2,3,4,5}
	var slice = shuzu[1:4]
	fmt.Println(slice)

	//追加
	slice = append(slice, 10,20,30,40)
	fmt.Println(slice)
}

返回

[2 3 4]
[2 3 4 10 20 30 40]

切片append操作的底层原理分析

1. 切片append操作的本质就是对数组进行扩容
2. go底层会创建一下新的数组,newArr(安装扩容后大小)
3. 将slice原来包含的元素拷贝到新的数组newArr
4. slice重新引用到newArr
5. 注意newArr是在底层维护的,不可见

案例

package main

import "fmt"

func main(){
	//定义一个切片
	var slice3 []int = []int{100,200,300}
	fmt.Println("slice3",slice3)

	//append给切片追加元素
	//这里append相当于是新创建了一个数组,然后将这个数组赋值给slice4
	slice4 := append(slice3,400)
	fmt.Println(slice4)

}

返回

slice3 [100 200 300]
[100 200 300 400]

内存图 append

var intArr [3]int = [...]int{100,200,300}  //定义一个数组
 
slice := intArr[1:3]   //基于数组创建一个切片

#通过append添加值
slice3 = append(slice3, 400,500,600)  

append会根据原先切片的元素和要新增的元素,来判断新增数组的空间大小(cap=6),因为是int类型默认值都为0

 

 他会先读取原先切片指定的数组的值复制到临时数组中

然后将append后面要添加的值再追加到该数组中

 

 最后将切片指向这个新的数组

 

而重新指定指针后,原先的数组就变成垃圾了,就会被回收

 

7、切片的拷贝操作

package main

import "fmt"

func main(){

	var a []int = []int{1,2,3,4,5} //定义一个切片 [1 2 3 4 5]


	var slice = make([]int,10)  //定义一个切片空间为10  [0 0 0 0 0 0 0 0 0 0]
	fmt.Println(slice)


	copy(slice,a)   //将a切片的的值拷贝给slice切片  [1 2 3 4 5 0 0 0 0 0]

	fmt.Println(slice,a)

}

当slice切片的空间大小不足以包含a切片的所有值的适合,会损失超出的值

package main

import "fmt"

func main(){

	var a []int = []int {1,2,3,4,5}
	var slice =make([]int,1)
	fmt.Println(slice)
	
	copy(slice,a)
	fmt.Println(slice)

}

 返回

[0]
[1]

8、切片是引用类型

分析两段代码

 案例1

package main

import "fmt"

func main(){
	var slice []int    //定义一个切片 目前为空
	var arr [5]int = [...]int {1,2,3,4,5}  //定义一个数组
	slice =arr[:]               //将数组的值全交给切片
	var slice2 = slice           //声明slice2 然后将切片的值交给slice2切片
	slice2[0] = 10

	fmt.Println(slice2)    //输出数组、slice、slice2切片
	fmt.Println(slice)
	fmt.Println(arr)

}

返回

[10 2 3 4 5]
[10 2 3 4 5]
[10 2 3 4 5]


//数组被切片拿到后,实际上 切片的值是指定的数组的内存地址
//再将切片交给其他切片后,实际上他里面的值不变还是指向数组的内存地址


//在上面我们slice2[0] = 10 给切片的值做修改的时候,实际上是修改了arr数组的值
//然而slice和slice2都是指向arr数组来提供数据的,所以3个值相同
//这就是引用数据类型的特点,值不是我自己的

案例2

package main

import "fmt"
func test(slice []int){   
	slice[0] = 100         //切片是引用数据类型,可以在不同的函数中直接修改值
}

func main(){
	var slice = []int{1,2,3,4}
	fmt.Println("slice=",slice)
	test(slice)  
	fmt.Println("slice=",slice)
}

返回

slice= [1 2 3 4]
slice= [100 2 3 4]

//事实证明,如果我们将切片传入到其他函数中后,那么相当于是一个全局的变量,在任何地方都可以修改
//slice是一个切片,切片是引用类型的值,那么他func test(slice []int) 传递的并不是切片的值,而是切片指定数组的内存地址
//test 函数中的slice 实际上是指定到了main中的slice,因此可以直接被修改

四、string和slice的关系

1、string 底层是一个byte数组,因此string也可以进行切片处理

package main

import "fmt"


func main(){
	
	str := "hello@atguigu"  //string底层是一个byte数组,一昵称string也可以进行切片处理
	
	slice := str[6:]    //使用切片获取到atguigu
	fmt.Println(slice)
}

返回

atguigu

2. string 的切片在内存的形式

//a b c d  字符串的底层是一个byte的数组
//str这个切片指向了byte数组的首地址
//因为他是一个切片,所以他可以使用切片的方法和函数

3. string 是不可变的

str[0] = 'z'   #这样是不可用的

#报错的原因
  string是不可变的

4、 如何修改字符串

package main

import "fmt"

        //如果需要修改字符串,可以先将string 转换为byte 或者rune,修改,重新转成字符串

func main(){
	str := "hello@atguigu"
	slice := str[6:]
	fmt.Println(slice)

	                       //如果非要修改字符串,如下
	arr1 := []byte(str)    //先转成byte数组
	arr1[0] = 'z'          //修改
	str = string(arr1)    //转回字符串
	fmt.Println(str)


}

返回

atguigu
zello@atguigu

说明

我们转换成[]byte 后可以处理英文和数字,但是不能处理中文
原因是[]byte字节来处理,而一个汉字,是3个字节,因此就会出现乱码


#解决方法
  将string转换为[]rune即可, 因为runc是兼容中文的,他是按照字符进行处理的,
package main

import "fmt"


func main(){
	str := "hello@atguigu"
	slice := str[6:]
	fmt.Println(slice)

	//修改为rune,添加的字符为的
	arr1 := []rune(str)   
	arr1[0] = '的'     
	str = string(arr1)  
	fmt.Println(str)


}

//atguigu
//的ello@atguigu

切片练习(斐波那契)

编写一个函数 fbn(n int) 要求完成下面的功能

1, 可以接收一个n int
2. 能够将斐波那契的数列放到切片重
3. 提示  斐波那契的数列形式
  arr[0] = 1;
  arr[1] = 1
  arr[2] = 2
  arr[3] = 3
  arr[4] = 5
  arr[5] = 8

案例

package main

import (
	"fmt"
)


func fbn(n int) []uint{

	fbnSlice := make([]uint,n)   //初始化切片 设置空间大小为n  10

	fbnSlice[0] = 1  //因为0和1的索引位的值,斐波那契都为1,那么可以写死
	fbnSlice[1] = 1


	for i := 2; i < n; i++ {     //通过循环来存放斐波那契的数列,索引位值从2开始

		fbnSlice[i] = fbnSlice[i-1] + fbnSlice[i-2]   //斐波那契的值都是前两位相加的值
		                                              //-1 前一位
		                                              //-2 前前一位
		                                              //然后将这两位值相加后赋值
		                                              //下波循环再拿最新的值
	}

	return fbnSlice

}


func main(){

	fbnSlice := fbn(10)    //定义要获取的元素的个数
	fmt.Println(fbnSlice)
}

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- oldu.cn 版权所有 浙ICP备2024123271号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务