从零开始学Go之容器(二):切片

切片(slice)是对数组一个连续片段的引用(该数组我们称之为相关数组,通常是匿名的),所以切片是一个引用类型。

切片的内部结构包含开始位置地址(&)、大小(len)和容量(cap)。

切片并不存储任何数据,它只是描述了底层数组中的一段。

更改切片的元素会修改其底层数组中对应的元素。

声明:

var 数组名 []类型

var a []int

切片的声明跟数组很相似,但是长度是不定长的,所以不需要说明长度。

切片的初始化与使用:

var 数组名 []类型{初始化值列表}

var a []int{1,2,3}

实际上则是创建一个相同长度的数组然后对其引用

由数组或切片生成新的切片:

切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:

a[low : high]
例子:
func main() {
 var number [50]int //声明一个长度为50的数组
?
 //从1到50下标输入
 for i := 0; i < len(number); i++ {
  number[i] = i + 1
 }
?
 var s1 = number[10:20]
 s2 := number[20:25]
 s3 := s1[4:8]
?
 fmt.Println(s1, s2, s3)
}

运行结果

[11 12 13 14 15 16 17 18 19 20] [21 22 23 24 25] [15 16 17 18]

切片的默认行为:

在进行切片时,你可以利用它的默认行为来忽略上下界。

切片下界的默认值为 0,上界则是该切片的长度。

对于数组 var a [10]int 来说,以下切片是等价的:

a[0:10]
a[:10]
a[0:]
a[:]

从数组或切片生成新的切片拥有如下特性:

  • 取出的元素数量为:结束位置-开始位置。
  • 取出元素不包含结束位置对应的索引,切片最后一个元素使用 slice[len(slice)] 获取。
  • 当缺省开始位置时,表示从连续区域开头到结束位置。
  • 当缺省结束位置时,表示从开始位置到整个连续区域末尾。
  • 两者同时缺省时,与切片本身等效。
  • 两者同时为0时,等效于空切片,一般用于切片复位。

根据索引位置取切片 slice 元素值时,取值范围是(0~len(slice)-1),超界会报运行时错误。生成切片时,结束位置可以填写 len(slice) 但不会报错。

切片的内部结构:

切片内部的结构有开始位置的地址、长度和容量。

地址:即切片创建时指向一个底层数组元素的指针

长度:即切片内部元素数量,可以用len求得

容量:当长度大于容量时,成倍增长,可以用cap求得

例:当一个切片创建时长度为3,容量为4;则当切片长度为5时,容量为8...

make创建切片:

var 变量名 = make( []类型, 长度, 预分配元素数量即cap)

变量名 := make( []类型, 长度, 预分配元素数量即cap)

a := make([]int, 5)

其中容量是可选的,若没有这个参数,则make出的切片长度和容量相同

切片的空值:

切片的零值是 nil

nil 切片的长度和容量为 0 且没有底层数组

要记得切片的初始化后即使元素为空,长度和容量为零,但地址仍然指向底层数组

 var x []int
 fmt.Println(x, len(x), cap(x))
 fmt.Printf("%p\n",x)
 if x == nil {
  fmt.Println("ok1")
 }

 x = make([]int, 0)
 fmt.Println(x, len(x), cap(x))
 fmt.Printf("%p\n",x)
 if x == nil {
  fmt.Println("ok2")
 }

 x = []int{}
 fmt.Println(x, len(x), cap(x))
 fmt.Printf("%p\n",x)
 if x == nil {
  fmt.Println("ok3")
 }

 x = number[0:0]
 fmt.Println(x, len(x), cap(x))
 fmt.Printf("%p\n",x)
 if x == nil {
  fmt.Println("ok4")
 }

运行结果

[] 0 0

0x0

ok1

[] 0 0

0x55c988

[] 0 0

0x55c988

[] 0 50

0xc04207e000

切片元素添加append:

func append(slice []Type, elems ...Type) []Type
元素添加:
var a []int
a = append(a,1)//单元素添加
a = append(a,2,3,4)//多元素添加
a = append(a,[]int{1,2,3}...)//多元素添加
头部添加:
var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片

在开头一般都会导致内存的重新分配,而且会导致已有的元素全部复制 1 次。因此,从切片的开头添加元素的性能一般要比从尾部追加元素的性能差很多

特定位置插入:

可以将多个 append 操作组合起来,实现在切片中间插入元素:

var a []int
a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i个位置插入x
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i个位置插入切片

每个添加操作中的第二个 append 调用都会创建一个临时切片,并将 a[i:] 的内容复制到新创建的切片中,然后将临时创建的切片再追加到 a[:i] 。

切片元素复制copy:

func copy(dst, src []Type) int

第一个参数是要复制的目标 slice,第二个参数是源 slice

切片复制:

如果两切片相等则源slice复制所有元素到目标slice,当两切片长度不相等则复制按最小的切片长度复制元素

func main() {
 slice1 := []int{1, 2, 3, 4, 5, 6}
 slice2 := []int{11, 12, 13}
 copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
 fmt.Println("slice1:",slice1,len(slice1),cap(slice1))
 fmt.Println("slice2:",slice2,len(slice2),cap(slice2))
 slice1 = []int{1, 2, 3, 4, 5, 6}
 slice2 = []int{11, 12, 13}
 copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
 fmt.Println("slice1:",slice1,len(slice1),cap(slice1))
 fmt.Println("slice2:",slice2,len(slice2),cap(slice2))
}

运行结果:

slice1: [1 2 3 4 5 6] 6 6

slice2: [1 2 3] 3 3

slice1: [11 12 13 4 5 6] 6 6

slice2: [11 12 13] 3 3

切片元素删除:

Go没有内置的切片元素删除函数,所以我们要删除切片的元素的时候仍然要使用append

删除切片尾部的元素:
a = []int{1, 2, 3}
a = a[:len(a)-1] // 删除尾部1个元素
a = a[:len(a)-N] // 删除尾部N个元素
删除开头的元素可以直接移动数据指针:
//直接移动指针
a = []int{1, 2, 3}
a = a[1:] // 删除开头1个元素
a = a[N:] // 删除开头N个元素
//利用append不改变内存空间结构变化完成
a = []int{1, 2, 3}
a = append(a[:0], a[1:]...) // 删除开头1个元素
a = append(a[:0], a[N:]...) // 删除开头N个元素
//copy完成删除
a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 删除开头1个元素
a = a[:copy(a, a[N:])] // 删除开头N个元素
对于删除中间的元素:
//append完成
a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
//copy完成
a = []int{1, 2, 3, ...}
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素

切片遍历:

跟数组一样,有下标遍历和range遍历两种方式:

func main() {
    slice := []int{1, 2, 3, 4, 5, 6}
    //仿C的写法
    for i := 0; i < len(slice); i++ {
        fmt.Println(slice[i])
    }

    //go中的range方法
    for k, v := range slice {
        fmt.Println(k, v)
    }
}

运行结果

1

2

3

4

5

6

0 1

1 2

2 3

3 4

4 5

5 6

原文地址:https://www.cnblogs.com/VingB2by/p/11087315.html

时间: 2024-11-10 02:09:31

从零开始学Go之容器(二):切片的相关文章

从零开始学ios开发(二):Hello World!

今天看了书的第二章,主要介绍了一下Xcode的使用方法和一些必要的说明,最后做了一个“Hello World!”的小程序,其实就是在屏幕上用一个Label显示“Hello World!”,一行代码都没有写,拖拖控件,改变一下Label字体大小颜色什么的,还是比较简单的,下面把这个过程写一下,至于xcode的使用说明就不在这里具体叙述了,反正到时候用到哪就介绍到哪里吧. 1)启动Xcode 选择“Create a new Xcode project”,创建一个新的项目. 2)模板选择,选择“Sin

从零开始学Go之基本(二):包、函数声明与格式化输出

包: 每个go程序在开头都要有一个package来声明当前包 其中包含main函数的包必须为main包来声明入口 从零开始学Go之基本(二):包.函数声明与格式化输出 导入包: import 包名称 //import "fmt" 单个导入 import ( "fmt" "math" ) 引用其他包时必须通过import来获取,根据包中的变量或者函数的开头大小写来判断能否导出:大写才能导出,否则不能被外部包引用 函数声明: func 函数名 (函数

从零开始学Go之容器(三):映射

map 是一种特殊的数据结构:一种元素对(pair)的无序集合,pair 的一个元素是 key,对应的另一个元素是 value,所以这个结构也称为关联数组或字典. 声明: var 映射名称 map[键类型]值类型 var map1 map[string]int key 可以是任意可以用 == 或者 != 操作符比较的类型,比如 string.int.float value 可以是任意类型的:通过使用空接口类型,我们可以存储任意值,但是使用这种类型作为值时需要先做一次类型断言 在map中key值是

从零开始学安全(四十二)●利用Wireshark分析ARP协议数据包

wireshark:是一个网络封包分析软件.网络封包分析软件的功能是撷取网络封包,并尽可能显示出最为详细的网络封包资料.Wireshark使用WinPCAP作为接口,直接与网卡进行数据报文交换,是目前全世界最广泛的网络封包分析软件 什么是ARP协议    协议分析篇第一个要研究的就是ARP协议.ARP(Address Resolution Protocol,地址解析协议)用于将IP地址解析为物理地址(MAC地址).这里之所以需要使用MAC地址,是因为网络中用于连接各个设备的交换机使用了内容可寻址

从零开始学Go之容器(四):列表

链表内部的实现原理是双链表,可以高效地进行任意位置的元素插入和删除操作 类似的还有堆heap和环列表ring,声明和初始化的方式是相同的,只是原理以及操作的函数不同,当理解了原理操作也是很简单的. 声明与初始化: list 的初始化有两种方法:New 和声明.两种方法的初始化效果都是一致的. 变量名 := list.New() var 变量名 list.List //通过 container/list 包的 New 方法初始化 list a := list.New() //通过声明初始化list

【高德地图API】从零开始学高德JS API(二)地图控件与插件——测距、圆形编辑器、鼠标工具、地图类型切换、鹰眼鱼骨

摘要:无论是控件还是插件,都是在一级API接口的基础上,进行二次开发,封装的一系列更加便于开发者使用,减少开发者工作量的二级API接口.除了官方通用的鱼骨.鹰眼控件,还有大量官方开发的地图插件,类似谷歌的lib.当然本文还会介绍自定义插件的使用. ------------------------------------------------------------------------------------------------- 第一部分 控件 目前官方支持的控件包含:缩放控制条-地图

从零开始学android&lt;ListView数据列表显示组件.二十一.&gt;

与滚动视图(ScrollView)类似的还有一种列表组件(ListView),可以将多个组件加入到ListView之中以达到组件的滚动显示效果,ListView组件本身也有对应的ListView类支持,可以通过操作ListView类以完成对此组组件的操作,ListView类的继承结构如下所示: java.lang.Object ? android.view.View ? android.view.ViewGroup ? android.widget.AdapterView<T extends a

从零开始学android&lt;android事件的处理方式.二十四.&gt;

在android中一共有 多种事件,每种事件都有自己相对应的处理机制 如以下几种 1 单击事件 View.OnClickListener public abstract void onClick (View v) 单击组件时触发 2 单击事件 View.OnLongClickListener public abstract boolean onLongClick (View v) 长按组件时触发 3 键盘事件 View.OnKeyListener public abstract boolean

从零开始学android&lt;RatingBar评分组件.二十三.&gt;

如果现在用户要对某个应用程序打分往往会使用图所示的组件,通过选择的"五角星"的个数来决定最终的打分成绩 这样的功能在Android之中,可以使用RatingBar组件实现,使用此组件可以方便用户的输入,而且很直观,RatingBar类的定义结构如下: java.lang.Object ? android.view.View ? android.widget.ProgressBar ? android.widget.AbsSeekBar ? android.widget.RatingBa