切片是一种数据结构,这种数据结构便于使用和管理数据集合。

切片是围绕动态数组的概念构建的,可以按需自动增长和缩小。

切片的动态增长是通过内置函数append来实现的。这个函数可以快速且高效地增长切片。

还可以通过对切片再次切片来缩小一个切片地大小。

因为切片的底层内存也是在连续中分配的,所以切片还能获得索引、迭代以及垃圾回收优化的好处。

1.内部实现

切片是一个很小的对象,对底层数组进行了抽象,并提供相关的操作方法。

切片有3个字段的数据结构,这些数据结构包含Go语言需要操作底层数组的元数据

这3个字段分别是指向底层数组的指针切片访问的元素个数(即长度)和切片允许增长到的元素个数(即容量)

2.创建和初始化

Go语言中有几种方法可以创建和初始化切片。是否能提前知道切片需要的容量通常会决定要如何创建切片。

(1)make和切片字面量

一种创建切片的方法是使用内置的make函数。当使用make时,需要传入一个参数,指定切片的长度。

//使用长度声明一个字符串切片

//创建一个字符串切片
//其长度和容量都是5个元素
slice := make([]string, 5)

如果只指定长度,那么切片的容量和长度相等。也可以分别指定长度和容量。

//使用长度和容量声明整型切片

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

分别指定长度和容量时,创建的切片,底层数组的长度是指定的容量,但初始化后并不能访问所有的数组元素。

上述代码中的切片可以访问3个元素,而底层数组拥有5个元素。

剩余的2个元素可以在后期操作中合并到切片,可以通过切片访问这些元素。

如果基于这个切片创建新的切片,新切片会和原有的切片共享底层数组,也能通过后期操作来访问多余容量的元素。

不允许创建容量小于长度的切片。

slice := make([]int, 4, 3)  //len larger than cap in make([]int)

另一种常用的创建切片的方法是使用切片字面量。

这种方法和创建数组类似,只是不需要指定[]运算符里的值。

初始的长度和容量会基于初始化时提供的元素个数确定。

//通过切片字面量来声明切片

//创建字符串切片
//其长度和容量都是5个元素
slice := []string{"red","pink",''yellow","blue"} //创建一个整型切片
//其长度和容量都是3个元素
slice := []int{10,20,30}

当使用切片字面量时,可以设置初始长度和容量。要做的就是在初始化时给出所需长度和容量作为索引。

//使用索引声明切片

//创建字符串切片
//使用空字符串初始化第100个元素
slice := []string{99:""}

记住,如果在[]运算符里指定了一个值,那么创建的就是数组而不是切片。

只有不指定值得时候才会创建切片。

//声明数组和声明切片得不同

//创建有3个元素得整型数组
array := [3]int{10,20,30} //创建长度和容量都是3的整型切片
slice := []int{10,20,30}

  

(2)nil和空切片

有时,程序可能需要声明一个值为nil的切片(也称nil切片),只需要在声明时不做任何初始化,就会创建一个nil切片。

//创建nil整型切片
var slice []int

在Go语言里,nil切片是很常见的创建切片的方法。

nil切片可以用于很多标准库和内置函数。在需要描述一个不存在的切片时,nil切片会很好用。

例如,函数要求返回一个切片但是发生异常的时候。

利用初始化,通过声明一个切片可以创建一个空切片。

//声明空切片

//使用make创建空的整型切片
slice := make([]int, 0) //使用切片字面量创建空的整型切片
slice := []int{}

空切片在底层数组0个元素,也没有分配任何存储空间。

想表示空集合空切片很有用,例如数据库查询返回0个查询结果时。

不管是使用nil切片还是空切片,对其调用内置函数append、len和cap的效果都是一样的。

3.使用切片

(1)赋值和切片

对切片里某个索引指向的元素赋值和对数组里某个索引指向的元素赋值的方法完全一样。

//创建一个整型切片
//其容量和长度都是5个元素
slice := []int{10, 20, 30, 40, 50} //改变索引为1的元素的值
slice[1] = 25

切片之所以被称之为切片,是因为创建一个新的切片就是把底层数组切除一部分

//创建一个整型切片
//其长度和容量都是5个元素
slice := []int{10, 20, 30, 40,50} //创建一个新切片
//其长度为2个元素,容量为4个元素
newSlice := slice[1:3]

我们有两个切片,它们共享一段底层数组,但通过不同的切片会看到底层数组不同的部分。

第一个切片slice能够看到底层数组全部5个元素的容量,不过之后的newSlice就不能看到。

对于newSlice,底层数组的容量只有4个元素。newSlice无法访问到它所指向的底层数组的第一个元素之前。

所以对于newSlice来说,之前的那些元素就是不存在的。

如何计算长度和容量

对底层数组容量是k的切片slice[i:j]
长度:j - i
容量:k - i 对底层数组容量是5的切片slice[1:3]来说
长度: 3 - 1 = 2
容量: 5 - 1 = 4

需要记住的是,现在两个切片共享同一个底层数组。

如果一个切片修改了该底层数组的共享部分,另一个切片也能感知到。

//创建一个整型切片
//其长度和容量都是5个元素
slice := []int{10, 20, 30, 40, 50} //创建一个新切片
//其长度是2个元素,容量是4个元素
newSlice := slice[1:3] //修改newSlice索引为1的元素
//同时也修改了原来的slice的索引为2的元素
newSlice[1] = 35

切片只能访问到其长度内的元素。试图访问长度超过其长度的元素将会导致语言运行异常。

//创建一个整型切片
//其长度和容量都是5个元素
slice := []int{10, 20, 30, 40, 50} //创建一个新切片
//其长度是2个元素,容量是4个元素
newSlice := slice[1:3] //修改newSlice索引为3的元素
//这个元素对于newSlice来说不存在
newSlice[3] = 45 //index out of range  

切片有额外的容量是很好的,但是如果不能把这些容量合并到切片的长度里,这些容量就没有用处。

好在可以用Go语言内置函数append来做这种合并很容易。

(2)切片增长

相对于数组而言,使用切片的一个好处是,可以按需增加切片的容量。

Go语言内置的append函数会处理增加长度时的所有操作细节。

要使用append,需要一个被操作的切片和一个追加的值。

//创建一个整型切片
//其长度和容量都是5个元素
slice := []int{10, 20, 30, 40, 50} //创建一个新切片
//其长度是2个元素,容量是4个元素
newSlice := slice[1:3] //使用原有的容量来分配一个新的元素
//将新元素赋值为60
newSlice := append(newSlice, 60)

当append调用返回时,会返回一个包含修改结果的新切片。

函数append总是会增加新切片的长度,而容量有可能会改变,也可能不变,这取决于被操作的切片的可用容量。

因为newSlice在底层数组里还有额外的容量可用,append操作将可用的元素合并到切片的长度,并对其进行赋值。

由于和原始的slice共享一个底层数组,slice中索引为3的元素的值也被改动了。

如果切片的底层数组没有足够的可用容量,append函数会创建一个新的底层数组,将被引用的现有的值复制到新数组里,再追加新的值。

//创建一个整型切片
//其长度和容量都是4个元素
slice := []int{10, 20, 30, 40} //追加一个元素
//将新元素赋值为50
newSlice := append(slice, 50)

 当这个append操作完成后,newSlice拥有一个全新的底层数组,这个数组的容量原来的两倍。

函数append会智能的处理底层数组的容量增长,在切片的容量小于1000个元素时,总是会成倍的增加容量。

一旦元素个数超过1000,容量的增长因子会设为1.25,也就是会每次增加25%的容量。

(3)创建切片时的3个索引

在创建切片时,还可以使用之前我们没有提及的第三个索引选项。

第三个索引可以用来控制新切片的容量。其目的并不是要增加容量,而是要限制容量。

可以看到,允许限制新切片的容量为底层数据提供了一定的保护,可以更好的控制追加操作。

//创建字符串切片
//其长度和容量都是5个长度
source := []string{"apple", "orange", "plum", "banana", "grape"}

使用第三索引来完成切片操作。

//将第三个元素切片并限制容量
//其长度为1个元素,容量为2个元素
slice := source[2:3:4]

这个切片操作执行后,新切片里从底层数组引用了1个元素,容量是2个元素。

具体来说,新切片引用了plum元素,并将容量扩展到banana元素。

我们应用之前定义的公式来计算新切片的长度和容量。

对于slice[i:j:k] 或 slice[2:3:4]
长度: j - i 或 3 - 2 = 1
容量: k - i 或 4 - 2 = 2

如果试图设置的容量比可用容量还大,就会得到一个语言运行时错误。

//这个切片操作试图设置容量为4
//这比可用容量大
slice := source[2:3:6] //slice bounds out of range

内置函数append会首先使用可用容量,一旦没有可用容量,会分配一个新的底层数组。

这导致很容易忘记切片间正在共享同一个底层数组。

一旦发生这种情况,对切片进行修改,很可能会导致随机且奇怪的问题。

对切片内容的修改会影响多个切片,却很难找到问题的原因。

如果在切片时设置切片的容量和长度一样,就可以强制让新切片的第一个append操作创建新的底层数组,与原有底层数组分离。

新切片与原有的底层数组分离后,可以安全的进行后续修改。

//创建字符串切片
//其长度和容量都是5个长度
source := []string{"apple", "orange", "plum", "banana", "grape"} //对第三个元素做切片,并限制容量
//其长度和容量都是一个长度
slice := source[2:3:3] //向slice追加新字符串
slice = append(slice, "Kiwi")

如果不加第三个索引,由于剩余的所有容量都属于slice,向slice追加kiwi会改变原有底层数组索引为3的元素的值Banana。

当我们限制slice容量为1的时候,再进行append操作的时候会创建一个新的底层数组,

这个数组包括两个元素,并将水果pium复制进来,再追加新水果Kiwi,并返回一个引用了这个底层数组的新切片。

因为新的切片slice拥有了自己的底层数组,所以杜绝了可能发生的问题。

内置函数append也是一个可变参数的函数。这意味着可以在一次调用传递多个追加多个值。

如果使用...运算符,可以将一个切片的所有元素追加到另一个切片里。

//创建两个切片,并分别用两个整数进行初始化
s1 := []int{1, 2}
s2 := []int{3, 4} //将两个切片追加到一起
fmt.Println(append(s1, s2...)) //[1 2 3 4]

切片s2里的所有值都追加到了切片s1的后面。

(4)迭代切片

既然切片是一个集合,可以迭代其中的元素。Go语言有个特殊的关键字range,它可以配合关键字for来迭代切片里的元素。

package main

import "fmt"

func main() {
//创建一个整型切片
//其长度和容量都是4个元素
slice := []int{10, 20, 30, 40} //迭代每一个元素
for index, value := range slice {
fmt.Printf("Index: %d Value: %d\n", index, value)
}
} /*
Index: 0 Value: 10
Index: 1 Value: 20
Index: 2 Value: 30
Index: 3 Value: 40
*/

当迭代切片时,关键字range会返回两个值。

第一个值是当前迭代到的索引位置,第二个值是该位置对应元素值得一个副本。

需要强调得是,range创建了每个元素的副本,而不是直接返回该元素的引用。

如果使用该值变量的地址作为每个元素的指针,就会造成错误。这是为什么了?

package main

import "fmt"

func main() {
//创建一个整型切片
//其长度和容量都是4个元素
slice := []int{10, 20, 30, 40} //迭代每一个元素,并显式值和地址
for index, value := range slice {
fmt.Printf("Value: %d Value-Addr: %X ElemAddr: %X\n", value, &value, &slice[index])
}
} /*
Value: 10 Value-Addr: C00000A168 ElemAddr: C00000E480
Value: 20 Value-Addr: C00000A168 ElemAddr: C00000E488
Value: 30 Value-Addr: C00000A168 ElemAddr: C00000E490
Value: 40 Value-Addr: C00000A168 ElemAddr: C00000E498
*/

因为迭代返回的变量是一个迭代过程中根据切片依次赋值的新变量,所以Value的地址总是相同的。

要想获取每个元素的地址,可以使用切片变量和索引值。

如果不需要索引值,可以使用空白占位符来忽略这个值。

package main

import "fmt"

func main() {
//创建一个整型切片
//其长度和容量都是4个元素
slice := []int{10, 20, 30, 40} //迭代每一个元素
for _, value := range slice {
fmt.Printf("Value: %d\n", value)
}
} /*
Value: 10
Value: 20
Value: 30
Value: 40
*/

关键字range总是会从切片头部开始迭代。如果想对迭代进行控制,依旧可以使用传统的for循环。

package main

import "fmt"

func main() {
//创建一个整型切片
//其长度和容量都是4个元素
slice := []int{10, 20, 30, 40} //迭代每一个元素
for index := 2; index < len(slice); index++ {
fmt.Printf("Index: %d Value: %d\n", index, slice[index])
}
} /*
Index: 2 Value: 30
Index: 3 Value: 40
*/

有两个特殊的内置函数len和cap,可以用于处理数组、切片和通道。

对于切片,函数len返回切片的长度,函数cap返回切片的容量。

4.多维切片

和数组一样,切片也是一维的。不过可以组合多个切片形成多维切片。

//创建一个整型切片的切片
slice := [][]int{{10}, {100, 200}}

我们有了一个包含两个元素的外层切片,每个元素包含一个内层的整型切片。

外层切片包含两个元素,每一个元素都是一个切片。

这种组合可以让用户创建非常负责且强大的数据结构。同时append的规则也可以用到组合后的切片上。

//创建一个整型切片的切片
slice := [][]int{{10}, {100, 200}} //为第一个切片追加值为20的元素
slice[0] = append(slice[0], 20)

Go语言里使用append函数处理追加的方式很简明:先增长切片,再将新的整型切片赋值给外层切片的第一个元素。

即使是这么简单的多维切片,操作时也会涉及众多布局和值。

不过切片本身结构很简单,可以以很小的成本再函数间传递。

5.在函数间传递切片

在函数间传递切片就是要在函数间以值得方式传递切片。

由于切片的尺寸很小,在函数间复制和传递切片成本也很低。

//分配包含100万个整型值得切片
slice := make([]int, le6) //将slice传递给函数foo
slice = foo(slice) //函数foo接受一个整型切片,并返回这个切片
func foo(slice []int) []int{
...
return slice
}

  

在64位架构的机器上,一个切片需要24字节的内存:指针字段需要8个字节,长度和容量分别需要8个字节。

由于与切片关联的数据包含在底层数组里,不属于切片本身,所以说将切片复制到任意函数的时候,对底层数组大小都不会影响。

复制时只会复制切片本身,不会涉及底层数组。

在函数间传递24个字节的数据会非常快速、简单。这也是切片效率高的地方。

不需要传递指针和处理复杂的语法,只需要复制切片。

go——切片(二)的更多相关文章

  1. PostGIS计算矢量切片(二)--按值渲染

    方案背景     今年三月份写了一篇postgis计算矢量切片,参考了网上资料给出了一份很粗糙的相关方案(文章写的更粗糙).当时的方案中只能针对gis形状进行渲染,而不能用属性渲染.针对这个情况,本文 ...

  2. GO语言学习——切片二

    使用make()函数构造切片 格式: make([]T, size, cap) 其中: T:切片的元素类型 size:切片中元素的数量 cap:切片的容量 切片的本质 切片的本质就是对底层数组的封装, ...

  3. Python array,list,dataframe索引切片操作 2016年07月19日——智浪文档

    array,list,dataframe索引切片操作 2016年07月19日——智浪文档 list,一维,二维array,datafrme,loc.iloc.ix的简单探讨 Numpy数组的索引和切片 ...

  4. photoshop,用切片工具等分图片

    一,切片 二,导出: 菜单->文件->存储为Web和设备所用格式 将预设改为PNG-24,然后点存储.

  5. go内建容器-切片

    1.基础定义 看到'切片'二字,满脸懵逼.切的啥?用的什么刀法切?得到的切片有什么特点?可以对切片进行什么操作? 先看怎么得到切片,也就是前两个问题.切片的底层是数组,所以切片切的是数组:切的时候采用 ...

  6. Python之路【第二篇】python基础 之基本数据类型

    运算符 1.算数运算: 2.比较运算: 3.赋值运算: 4.逻辑运算: 5.成员运算: name = "yehaoran " # in 判断ye是否在name里面 在的话返回ok ...

  7. ArcGIS API for Silverlight中加载Google地形图(瓦片图)

    原文:ArcGIS API for Silverlight中加载Google地形图(瓦片图) 在做水利.气象.土地等行业中,若能使用到Google的地形图那是再合适不过了,下面就介绍如何在ArcGIS ...

  8. python学习第九讲,python中的数据类型,字符串的使用与介绍

    目录 python学习第九讲,python中的数据类型,字符串的使用与介绍 一丶字符串 1.字符串的定义 2.字符串的常见操作 3.字符串操作 len count index操作 4.判断空白字符,判 ...

  9. 数据分析——numpy

    DIKW DATA-->INFOMATION-->KNOWLEDGE-->WISDOM 数据-->信息-->知识-->智慧 爬虫-->数据库-->数据分 ...

  10. np金融量化分析

    在所有的np中都是已返回值的形式进行修改的,否则不会修改  只是显示内容 形状是三维数据  全0数组 reshape也可以将二维的变成一维的 下标和切片 一维的切片 二维切片 . 列表切片 给一个数组 ...

随机推荐

  1. poj3680 Intervals 区间k覆盖问题 最小费用最大流 建图巧妙

    /** 题目:poj3680 Intervals 区间k覆盖问题 最小费用最大流 建图巧妙 链接:http://poj.org/problem?id=3680 题意:给定n个区间,每个区间(ai,bi ...

  2. edmx-新建表

  3. ARGOX 力象 OS-214Plus 条码打印机 B/S 打印

    官网demo下载地址: http://www.argox.com.cn/servicedev/5/c 页面中嵌入activeX控件: <object id="ArgoxPrinter& ...

  4. java网络编程5-SSL

    服务器端 System.out.println("等待客户端连接..."); File keyFile=new File("C:/Users/mu/Desktop/mu. ...

  5. [转帖收集] Java注解

    1.Annotation 它的作用是修饰编程元素.什么是编程元素呢?例如:包.类.构造方法.方法.成员变量等.Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和任何元数据( ...

  6. 微信小程序入门学习-- 简易Demo:计算器

    简单学习下微信小程序 官网 简易教程 · 小程序 https://mp.weixin.qq.com/debug/wxadoc/dev/ 需要通过开发者工具,来完成小程序创建和代码编辑. 下载安装,运行 ...

  7. 《JAVA多线程编程核心技术》 笔记:第七章:拾遗增补

    一.线程的状态 1.1 状态种类及理解:(一共6个) 文字说明和理解: NEW状态:线程实例化后还从未执行start()方法时的状态: RUNNABLE状态:线程进入运行的状态: TERMINATED ...

  8. Spring MVC 多语言化的实践和学习

    一.主要参考: SpringMVC简单实现国际化/多语言 - CSDN博客 https://blog.csdn.net/u013360850/article/details/70860144/ 二.总 ...

  9. 通过脚本同时运行几个spider

    # 通过脚本同时运行几个spider目录结构: 1.在命令行能通过的情况下创建两个spider如TestSpiderTest2Spider 2.在items.py的同级目录创建run.py文件,有三种 ...

  10. 用jQuery的attr()设置option默认选中无效的解决 attr设置属性失效

    表单下拉选项使用selected设置,发现第一次默认选中成功,在页面不刷新的情况下,再次下拉,selected属性设置了,默认选中不生效 在手机端有些浏览器用jQuery的attr()方法设置sele ...