在go中,slice是对固定长度数组的一段切片,其底层是用对数值空间的指针实现的。

在slice的赋值过程中,slice的容量会被初始化成“数组长度 - slice的起点位置”,举例说明:

假设有长度为5的int数组

1
arr := [5]int{1, 2, 3, 4, 5}

创建一个slice,并切出数组的中间3个值:

1
slice1 := arr[1:4]

打印以下slice1,得出:

1
[2 3 4]

看起来一切正常,是不,我们试下对slice1做append,同时打印一下arr和slice1:

1
2
slice1 = append(slice1, -5)
fmt.Println(slice1, arr)

得出结果是:

1
[2 3 4 -5] [1 2 3 4 -5]

一切好像还是正常是不,如果我们在这个时候,通过slice1来修改arr[0]的值:

1
slice1[0] = 99

得出arr的输出为:

1
[1 99 3 4 -5]

但是!!!

如果我们先对slice1进行append(这个时候,slice1的长度超出了arr),再修改slice1[0]的话,arr还会像预期中被修改了吗?

1
2
slice1 = append(slice1, 100)
slice1[0] = 99

此时得到的arr将会是:

1
[1 2 3 4 -5]

要命了,slice1不是通过指针的方式跟arr建立关联的吗?为毛这种情况却没有改变arr的值呢?

结论:

因为当slice1超出arr的长度时,Go语言会隐含式地对arr做了copy,并让slice1内部的指针重新指向了新数值,所以一切预期中修改arr的值的操作,都不会生效!

这种设定无疑是个陷阱,因为业务的开发者不应该关注slice的长度是否超出了所指向数组的许可,从而可能引出的问题将非常的隐蔽。

对策:

除非你十分聪明并且对代码所对应的问题了然于心,否则尽量:

  1. 少用slice,尤其是多个slice同时指向同一个数组

  2. 时刻记得不要超出slice初始化后的容量、、

概念

Slice切片是对底层数组Array的封装,在内存中的存储本质就是数组,体现为连续的内存块,Go语言中的数组定义之后,长度就已经固定了,在使用过程中并不能改变其长度,而Slice就可以看做一个长度可变的数组进行使用,最为关键的,是数组在使用的过程中都是值传递,将一个数组赋值给一个新变量或作为方法参数传递时,是将源数组在内存中完全复制了一份,而不是引用源数组在内存中的地址,为了满足内存空间的复用和数组元素的值的一致性的应用需求,Slice出现了,每个Slice都是都源数组在内存中的地址的一个引用,源数组可以衍生出多个Slice,Slice也可以继续衍生Slice,而内存中,始终只有源数组,当然,也有例外,后边再说。

用法

1.Slice的定义

Slice可以通过两种方式定义,一种是从源数组中衍生,一种是通过make函数定义,本质上来说都一样,都是在内存中通过数组的初始化的方式开辟一块内存,将其划分为若干个小块用来存储数组元素,然后Slice就去引用整个或者局部数组元素。

从数组中切片构建Slice:

a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
  s := a[2:8]
  fmt.Println(s)    //输出:[3 4 5 6 7 8]

定义一个数组a,截取下标为2到8之间部分(包括2不包括8),构建一个Slice。

通过make函数定义:

s := make([]int, 10, 20)
  fmt.Println(s) //输出:[0 0 0 0 0 0 0 0 0 0]

make函数第一个参数表示构建的数组的类型,第二个参数为数组的长度,第三个参数可选,是slice的容量,默认为第二个参数值。

2.Slice的长度和容量

Slice有两个比较混淆的概念,就是长度和容量,何谓长度?这个长度跟数组的长度是一个概念,即在内存中进行了初始化实际存在的元素的个数。何谓容量?如果通过make函数创建Slice的时候指定了容量参数,那内存管理器会根据指定的容量的值先划分一块内存空间,然后才在其中存放有数组元素,多余部分处于空闲状态,在Slice上追加元素的时候,首先会放到这块空闲的内存中,如果添加的参数个数超过了容量值,内存管理器会重新划分一块容量值为原容量值*2大小的内存空间,依次类推。这个机制的好处在能够提升运算性能,因为内存的重新划分会降低性能。

a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
  s := a[0:]
  s = append(s, 11, 22, 33)
  sa := a[2:7]
  sb := sa[3:5]
  fmt.Println(a, len(a), cap(a))    //输出:[1 2 3 4 5 6 7 8 9 0] 10 10
  fmt.Println(s, len(s), cap(s))    //输出:[1 2 3 4 5 6 7 8 9 0 11 22 33] 13 20
  fmt.Println(sa, len(sa), cap(sa)) //输出:[3 4 5 6 7] 5 8
  fmt.Println(sb, len(sb), cap(sb)) //输出:[6 7] 2 5

可以看出,数组的len和cap是永远相等的,并且是在定义的时候就已经指定的,不能改变。切片s引用这个数组的全部元素,初始长度和容量都为10,继续追加3个元素后,其长度变为13容量为20,。切片sa截取下标2到7的数组片段,长度为5,容量为8,这个容量的改变规则为原容量值减掉起始下标,此时若追加元素,会覆盖掉原内存地址中存在的值。切片sb截取切片sa下标3到5的数组片段,注意,这里的下标指的是sa的下标,不是源数组的下标,长度为2,容量为8-3=5。

3.Slice是引用类型

上边已经提到过,Slice是对源数组的一个引用,改变Slice中的元素的值,实质上就是改变源数组的元素的值。

a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
  sa := a[2:7]
  sa = append(sa, 100)
  sb := sa[3:8]
  sb[0] = 99
  fmt.Println(a)  //输出:[1 2 3 4 5 99 7 100 9 0]
  fmt.Println(sa) //输出:[3 4 5 99 7 100]
  fmt.Println(sb) //输出:[99 7 100 9 0]

可以看到,不管是append操作,还是赋值操作,都影响了源数组或者其他引用同一数组的Slice的元素。Slice进行数组引用的时候,其实是将指针指向了内存中具体元素的地址,如数组的内存地址,事实上是数组中第一个元素的内存地址,Slice也是如此。

a := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
  sa := a[2:7]
  sb := sa[3:8]
  fmt.Printf("%p\n", sa)         //输出:0xc084004290
  fmt.Println(&a[2], &sa[0])      //输出:0xc084004290 0xc084004290
  fmt.Printf("%p\n", sb)         //输出:0xc0840042a8
  fmt.Println(&a[5], &sb[0])      //输出:0xc0840042a8 0xc0840042a8

4.Slice引用传递发生“意外”

上边我们一直在说,Slice是引用类型,指向的都是内存中的同一块内存,不过在实际应用中,有的时候却会发生“意外”,这种情况只有在像切片append元素的时候出现,Slice的处理机制是这样的,当Slice的容量还有空闲的时候,append进来的元素会直接使用空闲的容量空间,但是一旦append进来的元素个数超过了原来指定容量值的时候,内存管理器就是重新开辟一个更大的内存空间,用于存储多出来的元素,并且会将原来的元素复制一份,放到这块新开辟的内存空间。

a := []int{1, 2, 3, 4}
  sa := a[1:3]
  fmt.Printf("%p\n", sa) //输出:0xc0840046e0
  sa = append(sa, 11, 22, 33)
  fmt.Printf("%p\n", sa) //输出:0xc084003200

可以看到执行了append操作后,内存地址发生了变化,说明已经不是引用传递。

[转]慎用slice的更多相关文章

  1. Matlab slice方法和包络法绘制三维立体图

    前言:在地球物理勘探,流体空间分布等多种场景中,定位空间点P(x,y,x)的物理属性值Q,并绘制三维空间分布图,对我们洞察空间场景有十分重要的意义. 1. 三维立体图的基本要件: 全空间网格化 网格节 ...

  2. jQuery之常用且重要方法梳理(target,arguments,slice,substring,data,trigger,Attr)-(一)

    1.jquery  data(name) data() 方法向被选元素附加数据,或者从被选元素获取数据. $("#btn1").click(function(){ $(" ...

  3. js url.slice(star,end) url.lastIndexOf('/') + 1, -4

    var url = '"http://60.195.252.25:15518/20151228/XXSX/作三角形的高.mp4")' document.title = url.sl ...

  4. JavaScript中的slice,splice,substr,substring,split的区别

    万恶的输入法,在sublime中会显示出繁体字,各位看官见谅. 1.slice()方法:该方法在数组和string对象中都拥有. var a = [1,2,3,4,5,6]; var s = 'thi ...

  5. 慎用mutableCopy

    因为逻辑需要,我在present到一个页面时,将一个存放uiimage的数组mutablecopy了过去(因为再返回的时候防止对数组做了改动),时间长了也忘了这事儿,后来发现添加多张图片上传时,app ...

  6. Max double slice sum 的解法

    1. 上题目: Task description A non-empty zero-indexed array A consisting of N integers is given. A tripl ...

  7. js中substr,substring,slice。截取字符串的区别

    substr(n1,n2) n1:起始位置(可以为负数) n2:截取长度(不可以为0,不可以为负数,可以为空) 当n1为正数时,从字符串的n1下标处截取字符串(起始位置),长度为n2. 当n1为负数时 ...

  8. JS 中 Array.slice() 和 Array.splice()方法

    slice slice()就是对应String的substring()版本,它截取Array的部分元素,然后返回一个新的Array: var arr = ['A', 'B', 'C', 'D', 'E ...

  9. 【javascript 技巧】Array.prototype.slice的妙用

    Array.prototype.slice的妙用 开门见山,关于Array 的slice的用法可以参考这里 http://www.w3school.com.cn/js/jsref_slice_arra ...

随机推荐

  1. 创建PO

    FORM FRM_CREATE_PO USING P_POSNR CHANGING P_EBELN. DATA: LV_VENDOR TYPE LIFNR, LV_ITEM TYPE EBELP, L ...

  2. http请求详解

    GET GET方法意思是获取被请求URI(Request-URI)指定的信息(以实体的格式).如果请求URI涉及到一个数据生成过程,那么这个过程生成的数据应该被作为实体在响应中返回而不是过程的源文本, ...

  3. cookie、 sessionStorage 、localStorage之间的区别和使用

    1.cookie:存储在用户本地终端上的数据.有时也用cookies,指某些网站为了辨别用户身份,进行session跟踪而存储在本地终端上的数据,通常经过加密.一般应用最典型的案列就是判断注册用户是否 ...

  4. Android: Intent实现活动之间的交互

    Intent的作用:是Android中各个组件直接交互的一种重要方式,且利用Intent可以启动Activity.Service以及Broadcast Receiver. Intent的创建:显示和隐 ...

  5. freemarker小例子

    1.在D盘下创建一个目录D:\\freemarker 2.在以上目录中放入一个模板文件test.ftl,内容如下:     第一个测试程序:${abc} 3.java代码如下(需要导入freemark ...

  6. CF #365 (Div. 2) D - Mishka and Interesting sum 离线树状数组

    题目链接:CF #365 (Div. 2) D - Mishka and Interesting sum 题意:给出n个数和m个询问,(1 ≤ n, m ≤ 1 000 000) ,问在每个区间里所有 ...

  7. mysql在一台服务器搭建主从1

    1. 登录mysq的方法:  mysql-S /tmp/mysql.sock 登录3306  mysql -S /tmp/mysql_slave.sock 登录3307 mysql -h 127.0. ...

  8. netbios wins dns LLMNR

    NetBIOS名称 Network Basic Input/Output System  (RFC-1001,1002)网络基本输入/输出系统协议 NetBIOS是一种高级网络接口,最初是在硬件中实 ...

  9. HDU 4890 One to Four(2014 Multi-University Training Contest 3)

    题意:给定一个长方形网格,要把它切成完全相同4个部分(这里完全相同指可以旋转平移后能重叠).把4个重叠后每个网格对应有四个数字相加,得到一种方案,所有格子中和最小就是该种方案的值,在多种方案中,最后问 ...

  10. 牛场围栏(vijos 1054)

    题目大意: 给出N种木棍(每种木棍数量无限)的长度(<=3000),每根木棍可以把它切掉[1,M]的长度来得到新的木棍. 求最大的不能被组合出来的长度. 如果任何长度都能组合出来或者最大值没有上 ...