在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. ios基础篇(十)——UINavgationController的使用(一)UIBarButtonItem的添加

    UINavigationController又被成为导航控制器,继承自UIViewController,以栈的方式管理所控制的视图控制器,下面就详细说一下UINavigationController的 ...

  2. Java多线程-新特性-线程池

    Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定可靠的多线程程序 ...

  3. MYSQL 日期函数【转】

    MySQL日期时间函数大全 DAYOFWEEK(date) 返回日期date是星期几(=星期六,ODBC标准) mysql> select DAYOFWEEK('1998-02-03'); WE ...

  4. 使用SMSManager短信管理器发送短信

    import android.os.Bundle;import android.app.Activity;import android.app.PendingIntent;import android ...

  5. python中的urlencode与urldecode

    当url地址含有中文,或者参数有中文的时候,这个算是很正常了,但是把这样的url作为参数传递的时候(最常见的callback),需要把一些中文甚至'/'做一下编码转换. 所以对于一些中文或者字符,ur ...

  6. nginx不支持pathinfo 导致thinkphp出错解决办法

    location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s=$1 last; break; } } 然后项目配置下url ...

  7. JavaScript中字符串转Json方法小记

    例如: JSON字符串:var str1 = '{ "name": "cxh", "sex": "man" }'; JS ...

  8. Hibernate 的配置文件

    Hibernate 配置文件 •Hibernate 配置文件主要用于配置数据库连接和 Hibernate 运行时所需的各种属性 •每个 Hibernate 配置文件对应一个 Configuration ...

  9. hashmap和hashtable,arraylist和vector的区别

    hashmap线程不安全,hashtable线程安全 hashmap允许使用 null 值和 null 键.(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同. ...

  10. SharePoint 2013 Nintex Workflow 工作流帮助(五)

    博客地址 http://blog.csdn.net/foxdave 工作流动作 4. Assign To-Do Task(User interaction分组) 直观理解,指派待办任务给一个或多个用户 ...