go 切片slice学习总结

2024-08-30 21:36
文章标签 go 切片 学习 总结 slice

本文主要是介绍go 切片slice学习总结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

切片的结构

切片的底层结构:

type SliceHeader struct {Data uintptr  // 指向底层数组的指针 Len  int      //长度Cap  int      //空间容量
}

切片的初始化

1 通过数组或者已有的slice创建新的slice

1.1 使用数组创建切片

通过数组的一部分来初始化切片。

array := [10]int{0,1, 2, 3, 4, 5,6,7,8,9}  
slice := a[5:7] 

Slice将与原数组共用一部分内存。

1.2 通过slice创建新的切片对象

x := []int{2, 3, 5, 7, 11}  
y := x[1:3]

x:   长度len=5 cap=5 data指针指向长度为5的底层数组结构。

y  长度为2 cap为y底层原始数组结构第一个元素位置到的最后一个容量空间位置的长度即5-1=4

data为指针指向底层原始数组结构第1个元素的地址(索引从0开始)

图示如下:

2,使用make函数

make函数是Go中用于分配和初始化内置类型的内置函数,也可以用来创建切片。

slice := make([]int, 5, 10) // 创建一个长度为5、容量为10的int类型切片

长度为5,即可以使用下标slice[0] ~ slice[4]来操作里面的元素,capacity为10,表示后续向slice添加新的元素时可以不必重新分配内存,直接使用预留内存即可。

3,切片字面量

直接使用切片字面量创建切片。

s := []int{1, 2, 3} // 直接初始化一个切片


4,较特殊的切片:

nil 切片

var a []int 

未初始化的切片默认值为nil

空切片

var b = []int{} 

也叫零值切片。和 nil 不相等, 一般用来表示一个空的集合。len 和 cap 都为 0。空切片在内部拥有一个非nil的、零长度的底层数组


在判断一个切片是否为空时,一般通过 len 获取切片的长度来判断,一般很少将切片和 nil 值做直接的比较

数组与切片

数组截取元素生成切片,共用底层元素存储底层数组结构

func main() {data := [...]int{0, 1, 2, 3, 4, 5}s := data[2:4]s[0] += 100s[1] += 200fmt.Println(s)fmt.Println(data)
}

打印结果:

 [102 203]
 [0 1 102 203 4 5]

 切片赋值

	data := [...]int{0, 1, 2, 3, 4, 5}s2 := data[:]fmt.Println("s2是:", s2)s3 := s2fmt.Println("s3是:", s3)s3[0] = 1000 + s3[0]fmt.Println("s3是:", s3)fmt.Println("s2是:", s2)fmt.Println("data是:", data)

打印结果:

s3是: [1000 1 2 3 4 5]
s2是: [1000 1 2 3 4 5]
data是: [1000 1 2 3 4 5]

切片参数

函数参数是切片时,是引用传递,函数内对切片的改动影响源切片

    func main(){sl := []int{1, 2, 3, 4, 5, 6, 7}PrintElements(sl)fmt.Printf("slice a : %v\n", sl)}func PrintElements(sl []int) {fmt.Println("-------------")for i, v := range sl {fmt.Println(v)sl[i] = v + 1}
}

打印结果:

-------------

 1
2
3
4
5
6
7
slice a : [2 3 4 5 6 7 8]

切片容量

切片扩容

扩容实际上是重新分配一块更大的内存,将原Slice数据拷贝进新Slice,然后返回新Slice,扩容后再将数据追加进去。

例如,当向一个capacity为5,且length也为5的Slice再次追加1个元素时,就会发生扩容,如下图所示:

扩容操作只关心容量,会把原Slice数据拷贝到新Slice,追加数据由append在扩容结束后完成。上图可见,扩容后新的Slice长度仍然是5,但容量由5提升到了10,原Slice的数据也都拷贝到了新Slice指向的数组中。

扩容容量的选择遵循以下规则:

  • 如果原Slice容量小于1024,则新Slice容量将扩大为原来的2倍;
  • 如果原Slice容量大于等于1024,则新Slice容量将扩大为原来的1.25倍;

使用append()向Slice添加一个元素的实现步骤如下:

  • 假如Slice容量够用,则将新元素追加进去,Slice.len++,返回原Slice
  • 原Slice容量不够,则将Slice先扩容,扩容后得到新Slice
  • 将新元素追加进新Slice,Slice.len++,返回新的Slice。

参考:slice-地鼠文档 

切片字面量创建的切片

    slice := []int{1, 2, 3, 4, 5, 6, 7, 8}

当你使用字面量来创建切片时,切片的初始容量(capacity)通常等于其长度(length)。这意味着,例子slice := []int{1, 2, 3, 4, 5, 6, 7, 8},切片的长度和容量都是8。

	slice := []int{1, 2, 3, 4, 5, 6, 7, 8}fmt.Print("cap:", cap(slice))fmt.Print("len:", len(slice))

cap: 8
len: 8 

扩容:

	slice := []int{1, 2, 3, 4, 5, 6, 7, 8}fmt.Println("cap:", cap(slice))fmt.Println("len:", len(slice))fmt.Println("----------append后扩容----------")slice2 := append(slice, 0) // 切片扩展 1 个空间fmt.Println("cap:", cap(slice2))fmt.Println("len:", len(slice2))fmt.Println(&slice[0] == &slice2[0])

 打印结构

cap: 8
len: 8
----------append后扩容----------
cap: 16
len: 9
false

append元素后容量不够使用,扩容为原来的两倍

make创建的切片

make创建的切片,类型申明之后,第一个数值是长度,第二个数值是容量

slice2 := make([]int, 3, 7)fmt.Println("cap:", cap(slice2))fmt.Println("len:", len(slice2))

cap: 7
len: 3

append元素后,容量不够就会扩容为之前的两倍容量。随之是地址的改动。

	slice2 := make([]int, 3, 7)fmt.Println("cap:", cap(slice2))fmt.Println("len:", len(slice2))slice3 := append(slice2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10) // 切片扩展 1 个空间fmt.Println("----------append后扩容----------")fmt.Println("cap:", cap(slice3))fmt.Println("len:", len(slice3))fmt.Println(&slice2[0] == &slice3[0])

cap: 7
len: 3
----------append后扩容----------
cap: 14
len: 13
false

切片操作

1,append操作

使用append函数,生成新的切片

append函数返回的slice跟原slice是独立的slice,互不影响

    var a = []int{1, 2, 3}fmt.Printf("slice a : %v\n", a)var b = []int{4, 5, 6}fmt.Printf("slice b : %v\n", b)c := append(a, b...)fmt.Printf("slice c  : %v\n", c)b = append(b, 7)fmt.Printf("slice b  after apend 7: %v\n", b)a = append(a, 7)fmt.Printf("slice a  after apend 7: %v\n", a)fmt.Printf("slice c : %v\n", c)c = append(c, 7)fmt.Printf("slice c  after apend 7: %v\n", c)

打印结果:

slice a : [1 2 3]
slice b : [4 5 6]
slice c  : [1 2 3 4 5 6]
slice b  after apend 7: [4 5 6 7]
slice a  after apend 7: [1 2 3 7]
slice c : [1 2 3 4 5 6]
slice c  after apend 7: [1 2 3 4 5 6 7]

其中b...是使用...展开运算符将slice展开作为单独元素传递给函数使用

以上apend操作是在末尾添加,还可以在头部添加

s := []int{2, 3, 4, 5, 6, 7}s = append([]int{1}, s...)fmt.Println("s是:", s)s = append([]int{-1, -2}, s...)fmt.Println("s是:", s)

s是: [1 2 3 4 5 6 7]
s是: [-1 -2 1 2 3 4 5 6 7] 

2,切片copy

copy(dst,src)将src内容复制给dst切片

src := []int{1, 2, 3}dst := make([]int, 3)copy(dst, src) // 现在dst是[1, 2, 3]的拷贝fmt.Println("dst是:", dst)

copy复制后两个切片是独立的

src := []int{1, 2, 3}dst := make([]int, 3)copy(dst, src) // 现在dst是[1, 2, 3]的拷贝fmt.Println("dst是:", dst)dst = append(dst, 4)fmt.Println("dst是:", dst)fmt.Println("src是:", src)

 dst是: [1 2 3]
dst是: [1 2 3 4]
src是: [1 2 3]

copy与append实现高效 添加元素

a = append(a, 0)     // 切片扩展 1 个空间copy(a[i+1:], a[i:]) // a[i:] 向后移动 1 个位置a[i] = x             // 设置新添加的元素

具体实现:

a := []int{1, 2, 3, 4, 5, 6, 7, 8}a = append(a, 0)     // 切片扩展 1 个空间copy(a[4+1:], a[4:]) // a[i:] 向后移动 1 个位置a[4] = 88            // 设置新添加的元素fmt.Println("a是:", a)

打印:

a是: [1 2 3 4 88 5 6 7 8] 

3,切片删除 

从末尾删除

slice5 := []int{1, 2, 3, 4, 5, 6, 7, 8}new_slice5 := slice5[:len(slice5)-1]fmt.Println("new_slice5:", new_slice5)new_slice5 = slice5[:len(slice5)-5]fmt.Println("new_slice5:", new_slice5)

new_slice5: [1 2 3 4 5 6 7]
new_slice5: [1 2 3]

从头部删除 

    slice5 = slice5[1:]  //删除头部第1个元素

    slice5 = slice[5:]   //删除头部第N个元素

使用append删除

append删除元素非常灵活,可以删除头部,中间,尾部元素。注意若是希望地址不变,需要将append的结果赋值回去

 删除头部元素

slice5 = append(slice5[:0], slice5[3:]...) //删除头部元素fmt.Println("slice5:", slice5)

slice5: [4 5 6 7 8]

删除中间元素

	slice5 = append(slice5[:3], slice5[5:]...) //删除中间两个元素fmt.Println("slice5:", slice5)

slice5: [1 2 3 6 7 8]

删除尾部元素

slice5 = append(slice5[:0], slice5[0:len(slice5)-3]...) //删除尾部元素fmt.Println("slice5:", slice5)

slice5: [1 2 3 4 5] 

以上便是对slice的学习总结,若是不当之处,烦请指出。后面有新的学习领悟,会继续添加。 

这篇关于go 切片slice学习总结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1121992

相关文章

Go路由注册方法详解

《Go路由注册方法详解》Go语言中,http.NewServeMux()和http.HandleFunc()是两种不同的路由注册方式,前者创建独立的ServeMux实例,适合模块化和分层路由,灵活性高... 目录Go路由注册方法1. 路由注册的方式2. 路由器的独立性3. 灵活性4. 启动服务器的方式5.

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

Go语言中三种容器类型的数据结构详解

《Go语言中三种容器类型的数据结构详解》在Go语言中,有三种主要的容器类型用于存储和操作集合数据:本文主要介绍三者的使用与区别,感兴趣的小伙伴可以跟随小编一起学习一下... 目录基本概念1. 数组(Array)2. 切片(Slice)3. 映射(Map)对比总结注意事项基本概念在 Go 语言中,有三种主要

Go Mongox轻松实现MongoDB的时间字段自动填充

《GoMongox轻松实现MongoDB的时间字段自动填充》这篇文章主要为大家详细介绍了Go语言如何使用mongox库,在插入和更新数据时自动填充时间字段,从而提升开发效率并减少重复代码,需要的可以... 目录前言时间字段填充规则Mongox 的安装使用 Mongox 进行插入操作使用 Mongox 进行更

Python中连接不同数据库的方法总结

《Python中连接不同数据库的方法总结》在数据驱动的现代应用开发中,Python凭借其丰富的库和强大的生态系统,成为连接各种数据库的理想编程语言,下面我们就来看看如何使用Python实现连接常用的几... 目录一、连接mysql数据库二、连接PostgreSQL数据库三、连接SQLite数据库四、连接Mo

Go语言利用泛型封装常见的Map操作

《Go语言利用泛型封装常见的Map操作》Go语言在1.18版本中引入了泛型,这是Go语言发展的一个重要里程碑,它极大地增强了语言的表达能力和灵活性,本文将通过泛型实现封装常见的Map操作,感... 目录什么是泛型泛型解决了什么问题Go泛型基于泛型的常见Map操作代码合集总结什么是泛型泛型是一种编程范式,允

Git提交代码详细流程及问题总结

《Git提交代码详细流程及问题总结》:本文主要介绍Git的三大分区,分别是工作区、暂存区和版本库,并详细描述了提交、推送、拉取代码和合并分支的流程,文中通过代码介绍的非常详解,需要的朋友可以参考下... 目录1.git 三大分区2.Git提交、推送、拉取代码、合并分支详细流程3.问题总结4.git push

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操

Kubernetes常用命令大全近期总结

《Kubernetes常用命令大全近期总结》Kubernetes是用于大规模部署和管理这些容器的开源软件-在希腊语中,这个词还有“舵手”或“飞行员”的意思,使用Kubernetes(有时被称为“... 目录前言Kubernetes 的工作原理为什么要使用 Kubernetes?Kubernetes常用命令总