go语言基础之切片(代码片段)

dabric dabric     2023-04-18     522

关键词:

1 切片介绍

  Golang提供数组这种存储相同类型数据的数据结构,由于在现实生活中一件事物的个数不是固定,比如说一个班级的学生人数等,然而数组的长度是固定,因此在Golang中很少直接使用数组。和数组相对应的类型是切片slice,其代表变长的序列,序列中每个元素都是相同的类型。

1.1 切片的内部实现

  如图1所示,可以看出切片的底层还是数组。slice从底层来说,其实就是一个数据结构(struct结构体),切片有3个字段的数据结构,这三个字段分别是指向底层数组的指针、切片访问的元素的个数(即长度)和切片允许增长到的元素个数(即容量)。

 

技术图片

图1 切片在内存中的形式

 

 

2 切片的使用

2.1 基于数组定义切片

  定义一个切片,然后让切片去引用一个已经创建好的数组,如下案例:

1 func main() 
2     // 方式1:基于数组定义切片,定义一个切片,然后让切片去引用已经创建好的数组
3     var arr = [...]int1, 2, 3, 4, 5
4     var slice = arr[1:3] //左包含右不包含
5     fmt.Printf("slice的类型%T
", slice)    //slice的类型[]int
6     fmt.Println("slice=", slice)            //slice= [2 3]
7     fmt.Println("slice len =", len(slice))    //slice len = 2
8     fmt.Println("slice cap =", cap(slice))    //slice cap = 4
9 

2.2 使用make()函数创建切片

  基本语法: var 切片名 []type = make([]type, len, [cap]) 

  参数说明: type 就是数据类型; len 大小; cap 指定切片容量,可选,如果分配了 cap ,则要求 cap >= len 。

 1 func main() 
 2     // 方式2:使用make函数创建切片
 3     var slice []float64 = make([]float64, 5, 10)
 4     slice[1] = 10
 5     slice[2] = 20
 6     // 对于切片,必须make使用
 7     fmt.Println(slice)                        //[0 10 20 0 0]
 8     fmt.Println("slice len =", len(slice))    //slice len = 5
 9     fmt.Println("slice cap =", cap(slice))    //slice cap = 10
10 
  • 通过make方式创建切片可以指定切片的大小和容量
  • 如果没有给切片的各个元素赋值,那么就会使用默认值
  • 通过make方式创建的切片对应的数组是由make底层维护,对外不可见,即只能通过slice去访问各个元素

2.3 直接定义并赋值

  定义一个切片,直接就指定具体数组,使用原理类似make的方式

1 func main() 
2     // 方式3:定义一个切片,直接就指定具体数组
3     var slice []string = []string"tom", "jerry", "mary"
4     fmt.Println(slice)                     //[tom jerry mary]
5     fmt.Println("slice len =", len(slice)) //slice len = 3
6     fmt.Println("slice cap =", cap(slice)) //slice cap = 3
7 

  方式一和方式二的区别:

  • 方式1是直接引用数组,这个数组是事先存在的,程序员是可见的
  • 方式2是通过make来创建切片,make也会创建一个数组,是由切片在底层进行维护,程序员不可见

2.4 切片再切片

  除了基于数组和make函数得到切片,我们还可以通过切片来得到切片。 

 1 func main() 
 2     // 切片再切片
 3     // 不仅可以基于数组和make函数来创建切片,也可以通过切片创建切片
 4 
 5     cityArray := [...]string"北京", "上海", "广州", "深圳", "成都", "重庆"
 6     fmt.Printf("cityArray:%v type:%T len:%d cap:%d
", cityArray, cityArray, len(cityArray), cap(cityArray))
 7     citySlice := cityArray[1:3]
 8     fmt.Printf("citySlice:%v type:%T len:%d cap:%d
", citySlice, citySlice, len(citySlice), cap(citySlice))
 9     newCitySlice := citySlice[1:5]
10     fmt.Printf("newCitySlice:%v type:%T len:%d cap:%d
", newCitySlice, newCitySlice, len(newCitySlice), cap(newCitySlice))
11 

  输出:

1 cityArray:[北京 上海 广州 深圳 成都 重庆] type:[6]string len:6 cap:6
2 citySlice:[上海 广州] type:[]string len:2 cap:5
3 newCitySlice:[广州 深圳 成都 重庆] type:[]string len:4 cap:4

  切片初始化时,仍然不能越界。范围在[0-len(arr)]之间,但可以动态增长。

  •  var slice = arr[0:end]  可以简写  var slice = arr[:end] 
  •  var slice = arr[:end] 可以简写  var slice = arr[:end] 
  •  var slice = arr[0:len(arr)]  可以简写  var slice = arr[:] 

3 切片的遍历

  切片的遍历和数组一样,也有两种方式: for 循环常规方式遍历; for-range  结构遍历切片。

 1 func main() 
 2     s := []int1, 3, 5
 3 
 4     for i := 0; i < len(s); i++ 
 5         fmt.Println(i, s[i])
 6     
 7 
 8     for index, value := range s 
 9         fmt.Println(index, value)
10     
11 

4 appned()方法为切片添加元素

  Go语言的内建函数 appen() 可以为切片动态添加元素,每个切片会指向一个底层数组,这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在 appen() 函数调用时,所以通常都需要用原变量接收 append() 函数的返回值。

1 func main() 
2     //append()添加元素和切片扩容
3     var numSlice []int
4     for i := 0; i < 10; i++ 
5         numSlice = append(numSlice, i)
6         fmt.Printf("%v  len:%d  cap:%d  ptr:%p
", numSlice, len(numSlice), cap(numSlice), numSlice)
7     
8 

  输出:

 1 [0]  len:1  cap:1  ptr:0xc0000a8000
 2 [0 1]  len:2  cap:2  ptr:0xc0000a8040
 3 [0 1 2]  len:3  cap:4  ptr:0xc0000b2020
 4 [0 1 2 3]  len:4  cap:4  ptr:0xc0000b2020
 5 [0 1 2 3 4]  len:5  cap:8  ptr:0xc0000b6000
 6 [0 1 2 3 4 5]  len:6  cap:8  ptr:0xc0000b6000
 7 [0 1 2 3 4 5 6]  len:7  cap:8  ptr:0xc0000b6000
 8 [0 1 2 3 4 5 6 7]  len:8  cap:8  ptr:0xc0000b6000
 9 [0 1 2 3 4 5 6 7 8]  len:9  cap:16  ptr:0xc0000b8000
10 [0 1 2 3 4 5 6 7 8 9]  len:10  cap:16  ptr:0xc0000b8000

  从上面的结果可以看出:

  •  append() 函数将元素追加到切片的最后并返回该切片;
  • 切片 numSlice 的容量按照1,2,4,8,16这样的规则自动进行扩容,每次扩容后都是扩容前的2倍;
  • 切片在动态增加元素时,如果有一次增加的元素超过切片的容量,go底层会抛弃原先的数组,创建一个新的数组newArr,将slice原来包含的元素拷贝到新的数组newArr,slice重新引用到newArr;注意newArr是在底层来维护的,程序员不可见。

4.1 一次追加多个元素

  append()函数还支持一次性追加多个元素

1 var citySlice []string
2 // 追加一个元素
3 citySlice = append(citySlice, "北京")
4 // 追加多个元素
5 citySlice = append(citySlice, "上海", "广州", "深圳")
6 // 追加切片
7 a := []string"成都", "重庆"
8 citySlice = append(citySlice, a...)    //...代表的含义是将切片a展开,将其中的元素一一添加到citySlice中
9 fmt.Println(citySlice) //[北京 上海 广州 深圳 成都 重庆]

4.2 删除切片中的元素  

  由于切片没有特定的方法删除切片中的元素,于是使用 append() 函数结合切片本身的特性实现切片中元素的删除。例如:

1 func main() 
2     // 从切片中删除元素
3     a := []int30, 31, 32, 33, 34, 35, 36, 37
4     // 要删除索引为2的元素
5     a = append(a[:2], a[3:]...)
6     fmt.Println(a) //[30 31 33 34 35 36 37]
7 

  要从切片 slice 中删除索引为 index 的元素,操作方式是 slice = append(slice[:index], slice[index+1:]...) 

 5 切片的扩容原则

  可以通过查看 $GOROOT/src/runtime/slice.go 源码,其中扩容相关代码如下:

 1 newcap := old.cap
 2 doublecap := newcap + newcap
 3 if cap > doublecap 
 4     newcap = cap
 5  else 
 6     if old.len < 1024 
 7         newcap = doublecap
 8      else 
 9         // Check 0 < newcap to detect overflow
10         // and prevent an infinite loop.
11         for 0 < newcap && newcap < cap 
12             newcap += newcap / 4
13         
14         // Set newcap to the requested cap when
15         // the newcap calculation overflowed.
16         if newcap <= 0 
17             newcap = cap
18         
19     
20 

  从上面的代码可以看出以下内容:

  • 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap);
  • 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap);
  • 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap, for newcap += newcap / 4)直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap);
  • 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。

  需要注意的是,切片扩容还会根据切片中元素的类型不同而做出不同的处理,比如 int 和 string 类型的处理方式就不一样。

6 切片的拷贝操作

  切片是引用类型,在赋值拷贝和使用 copy() 函数拷贝时要注意二者的区别。

6.1 切片的赋值拷贝

  下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

1 func main() 
2     s1 := make([]int, 3) //[0 0 0]
3     s2 := s1             //将s1直接赋值给s2,s1和s2共用一个底层数组
4     s2[0] = 100
5     fmt.Println(s1) //[100 0 0]
6     fmt.Println(s2) //[100 0 0]
7 

  切片在函数之间传参时,也遵守引用传递机制,如果在函数中修改切片中的内容,那么也会改变实参。

 1 func test(slice []int)
 2     slice[0] = 100
 3 
 4 
 5 func main()
 6     var slice = []int1, 2, 3, 4, 5
 7     fmt.Println("slice =", slice)  // slice = [1 2 3 4 5]
 8     test(slice)
 9     fmt.Println("slice =", slice)  // slice = [100 2 3 4 5]
10 

6.2 使用copy()函数复制切片

  切片使用copy内置函数完成拷贝,举例说明:

1 func main() 
2     // 切片的拷贝操作
3     var slice1 []int = []int1, 2, 3, 4, 5
4     var slice2 = make([]int, 10)
5     copy(slice2, slice1)  //将slice1复制到slice2
6     fmt.Println("slice1=", slice1)  // slice1= [1 2 3 4 5]
7     fmt.Println("slice2=", slice2)  // slice2= [1 2 3 4 5 0 0 0 0 0]
8 

  对上面代码的说明:

  • copy(para1, para2)参数的数据类型是切片;
  • 按照上面的代码,slice1和slice2的数据空间是独立的,相互不影响,也就是说 slice1[0]=999 , slice2[0] 仍然是1;

  注意:如果在拷贝时,目标切片的容量小于源切片的容量,此时目标的容量有多少就存多少源切片的内容:

1 func main() 
2     var a []int = []int1, 2, 3, 4, 5
3     var slice = make([]int, 1)
4     fmt.Println(slice)  // [0]
5     copy(slice, a)
6     fmt.Println(slice)  //[1]
7 

7 string和slice

  • string底层是一个byte数组,因此string也可以进程切片处理,案例演示:
1 func main() 
2     str := "abc"
3     slice := str[1:3]
4     fmt.Printf("%T
", slice)  // string
5     fmt.Println("slice =", slice)  // slice = bc
6     fmt.Println(len(slice))  // 2
7 
  • string和切片在内存的形式,以上面代码为例画出内存示意图

技术图片

 

 

 

  • string是不可变得,也就是说不能通过 str[0] = z 方式来修改字符串
  • 如果需要修改字符串,可以先将 string  ->  []byte 或者 []byte  -> 修改 -> 重新转成 string ,代码如下:
 1 func main() 
 2     str1 := "hello world"
 3     arr1 := []byte(str1)
 4     arr1[0] = z
 5     str1 = string(arr1)
 6     fmt.Println("str1=", str1)
 7     /*
 8         细节,我们转成[]byte后,可以处理英文和数字,但是不能处理中文,
 9         原因是 []byte 按字节来处理,而一个汉字是3个字节,因此就会出现乱码
10         解决方法是将string转成[]rune即可,因为[]rune是按字符处理,兼容汉字
11     */
12     str2 := "Hello Golang"
13     arr2 := []rune(str2)
14     arr2[0] = 
15     str2 = string(arr2)
16     fmt.Println("str2=", str2)
17 

8 练习题

  • 请写出下面代码的输出结果:
技术图片
1 func main() 
2     var a = make([]string, 5, 10)
3     for i := 0; i < 10; i++ 
4         a = append(a, fmt.Sprintf("%v", i))
5     
6     fmt.Println(a)
7 
View Code
  • 请使用内置的 sort 包对数组 var a = [...]int3, 7, 8, 9, 1 进行排序

go语言之go语言引用类型(代码片段)

GO语言引用类型Go语言切片Go语言切片(Slice)Go语言切片是对数组的抽象。Go数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是... 查看详情

go语言之切片slice练习(代码片段)

切片Slice理论知识其本身并不是数组,它指向底层的数组作为编程数组的替代方案,可以关联底层数组的局部或者全部为引用类型可以直接创建或从底层数组获取生成使用len()获取元素个数,cap()获取容量一般使用make()创建如果多... 查看详情

go语言之字符串指针数组切片(代码片段)

...字节单元,但是不能修改某个字节的值(2〕宇符串转换为切片[]byte(要慎用,尤其是当数据量较大时(每转换一次都需复制内容)a:=”hello,world!”b:=[]byte(a)(3)字符串尾部不包含NULL字符(4)字符串类型底层实 查看详情

go语言基础之切片(代码片段)

1切片介绍  Golang提供数组这种存储相同类型数据的数据结构,由于在现实生活中一件事物的个数不是固定,比如说一个班级的学生人数等,然而数组的长度是固定,因此在Golang中很少直接使用数组。和数组相对应的类型是切... 查看详情

go语言入门之切片的概念(代码片段)

切片是对数组的抽象,对切片的改变会改变原数组的值packagemainimport"fmt"functest6()arr:=[...]int0,1,2,3,4,5,6,7,8,9,10s1:=arr[2:6]fmt.Println(arr)fmt.Println(s1)s1[0]=200fmt.Println(arr)fmt.Println(s1)funcmain()test6()//结果[012345678910][2345][01200345678910][200345]slice的... 查看详情

你不知道的go之slice(代码片段)

简介切片(slice)是Go语言提供的一种数据结构,使用非常简单、便捷。但是由于实现层面的原因,切片也经常会产生让人疑惑的结果。掌握切片的底层结构和原理,可以避免很多常见的使用误区。底层结构切片结构定义在源码ru... 查看详情

go语音基础之切片的创建和截取(代码片段)

1、切片的创建示例:packagemain//必须有个main包import"fmt"funcmain() //切片和数组的区别 //数组[]里面的长度时固定的一个常量,数组不能修改长度,len和cap永远都是5 a:=[5]int fmt.Printf("len=%d,cap=%d",len(a),cap(a)) //切片,[]里面为空,或者为... 查看详情

go语言泛型编程之切片

...么用途呢?在没有泛型之前Go不能实现什么样的代码?Go切片Slice我们先来看一下切片,切片在Go中并不是简单的数组,而是一个结构体,其定义如下:typeslicestructarrayunsafe.Pointer//指向存放数据的数组指针lenint//长度capint//容量用图... 查看详情

①★go语言面试之数据格式舒适版♥√(代码片段)

...数据格式👈👍Go语言面试👈1、🐹数组和切片1.1☀️有如下代码,请说明两个切片的不同1.2❄️写出下面程序运行的结果1.3🐱下面代码输出什么?1.4🐹是否可以编译通过?如果通过,输出什么&#... 查看详情

①★go语言面试之数据格式舒适版♥√(代码片段)

...数据格式👈👍Go语言面试👈1、🐹数组和切片1.1☀️有如下代码,请说明两个切片的不同1.2❄️写出下面程序运行的结果1.3🐱下面代码输出什么?1.4🐹是否可以编译通过?如果通过,输出什么&#... 查看详情

#yyds干货盘点#愚公系列2022年08月go教学课程021-go容器之切片操作(代码片段)

一、切片操作1.什么是切片切片和数组类似,都是数据集合。和数组不同的是,切片是一块动态分配大小的连续空间。2.切片的定义2.1切片的格式var变量名[]T//T表示切片类型。相关案例:packagemainimport"fmt"funcmain()//声明整型切片varn... 查看详情

go语言容器(container)(代码片段)

...历数组—访问每一个数组元素Go语言多维数组简述Go语言切片详解从数组或切片生成新的切片1)从指定范围中生成切片2)表示原有的切片3)重置切片,清空拥有的元素直接声明新的切片使用make()函数构造切片Go语言append()为切片... 查看详情

go语言基础切片,map(代码片段)

目录切片(Slice)切片使用切片内存申请切片赋值拷贝切片遍历切片扩容动态扩容切片扩切片切片copy删除切片map简单使用判断键是否在map中遍历map删除键值对按照某个固定顺序遍历map元素为map类型的切片元素为map类型的切片循环初... 查看详情

go语言范围(range)(代码片段)

阅读目录Go语言范围(Range)切片迭代map迭代起始节点就随机了字符串迭代channel结构体golang中数组与切片的区别详析Go切片和Go数组定义切片与数组的区别数组切片代码数组声明与赋值切片声明与初始化值传递与引用传递Go... 查看详情

go语言范围(range)(代码片段)

阅读目录Go语言范围(Range)切片迭代map迭代起始节点就随机了字符串迭代channel结构体golang中数组与切片的区别详析Go切片和Go数组定义切片与数组的区别数组切片代码数组声明与赋值切片声明与初始化值传递与引用传递Go... 查看详情

go语言|03数组指针切片用法(代码片段)

...用指针指针数组指向指针的指针指针作为函数参数Go语言切片(Slice)定义切片切片初始化len()和cap()函数切片截取append()和copy()函数Go语言数组Go语言提供了数组类型的数据结构。数组是具有相同唯一类型的一组已编号且长度固定的... 查看详情

go语言容器—数组切片(代码片段)

...目录一、Go语言数组1.1数组的概念1.2多维数组二、Go语言切片2.1切片的概念2.2切片语法说明2.3使用演示2.4多维切片一、Go语言数组1.1数组的概念语法说明var数组变量名[元素数量]Type元素数量:必须在编译器就能够确定Type:可以是任意... 查看详情

go语言切片(slice)(代码片段)

Go语言切片是对数组的抽象。定义切片声明切片:varidentifier[]typevarslice1[]type=make([]type,len)slice1:=make([]type,len)make([]T,length,capacity)切片初始化s:=[]int1,2,3s:=arr[:]s:=arr[startIndex:end 查看详情