Go语言-切片类型

 切片(Slice)与数组一样,也是可以容纳若干类型相同的元素的容器。与数组不同的是,无法通过切片类型来确定其值的长度。每个切片值都会将数组作为其底层数据结构。我们也把这样的数组称为切片的底层数组。
  
    表示切片类型的字面量如:

[]int 

  或

[]string    

    可以看到,它们与数组的类型字面量的唯一不同是不包含代表其长度的信息。因此,不同长度的切片值是有可能属于同一个类型的。相对的,不同长度的数组值必定属于不同类型。对一个切片类型的声明可以这样:

type MySlice []int

    这时,类型MySlice即为切片类型[]int的一个别名类型。除此之外,对切片值的表示也与数组值也极其相似,如:

[]int{1, 2, 3}  

    这样的字面量与数组(值)的字面量的区别也只在于最左侧的类型字面量。
  
    我们在上一节讲到的操作数组值的方法也同样适用于切片值。不过,还有一种操作数组值的方法我们没讲到。这种操作的名称就叫“切片”。实施切片操作的方式就是切片表达式。举例如下:

var numbers3 = [5]int{1, 2, 3, 4, 5}
var slice1 = numbers3[1:4]

    请注意第二条赋值语句中在“=”右边那个部分。切片表达式一般由字符串、数组或切片的值以及由方括号包裹且由英文冒号“:”分隔的两个正整数组成。这两个正整数分别表示元素下界索引和元素上界索引。在本例中,切片表达式numbers3[1:4]的求值结果为[]int{2, 3, 4}。可见,切片表达式的求值结果相当于以元素下界索引和元素上界索引作为依据从被操作对象上“切下”而形成的新值。注意,被“切下”的部分不包含元素上界索引指向的元素。另外,切片表达式的求值结果会是切片类型的,且其元素类型与被“切片”的值的元素类型一致。实际上,slice1这个切片值的底层数组正是numbers3的值。
  
    实际上,我们也可以在一个切片值上实施切片操作。操作的方式与上述无异。请看下面这个例子:

var slice2 = slice1[1:3] 

    据此,slice2的值为[]int{3, 4}。注意,作为切片表达式求值结果的切片值的长度总是为元素上界索引与元素下界索引的差值。
  
    除了长度,切片值以及数组值还有另外一个属性——容量。数组值的容量总是等于其长度。而切片值的容量则往往与其长度不同。请看下图。
  
    如图所示,一个切片值的容量即为它的第一个元素值在其底层数组中的索引值与该数组长度的差值的绝对值。为了获取数组、切片或通道类型的值的容量,我们可以使用内建函数cap,如:

var capacity2 int = cap(slice2)

    最后,要注意,切片类型属于引用类型。它的零值即为nil,即空值。如果我们只声明一个切片类型的变量而不为它赋值,那么该变量的值将会是nil。例如,若有这样一个变量:

var slice3 []int

  则它的值会是

nil

  

 我们已经知道,在进行“切片”操作的时候需要指定元素下界索引和元素上界索引,就像这样:

numbers3[1:4]

    在有些时候,我们还可以在方括号中放入第三个正整数,如下所示:

numbers3[1:4:4] 

    这第三个正整数被称为容量上界索引。它的意义在于可以把作为结果的切片值的容量设置得更小。换句话说,它可以限制我们通过这个切片值对其底层数组中的更多元素的访问。下面举个例子。让我们先来回顾下在上一节讲到的numbers3slice1。针对它们的赋值语句是这样的:

var numbers3 = [5]int{1, 2, 3, 4, 5}
var slice1 = numbers3[1:4]  

    这时,变量slice1的值是[]int{2, 3, 4}。但是我们可以通过如下操作将其长度延展得与其容量相同:

slice1 = slice1[:cap(slice1)]   

    通过此操作,变量slice1的值变为了[]int{2, 3, 4, 5},且其长度和容量均为4。现在,numbers3的值中的索引值在[1,5)范围内的元素都被体现在了slice1的值中。这是以numbers3的值是slice1的值的底层数组为前提的。这意味着,我们可以轻而易举地通过切片值访问其底层数组中对应索引值更大的更多元素。如果我们编写的函数返回了这样一个切片值,那么得到它的程序很可能会通过这种技巧访问到本不应该暴露给它的元素。这是确确实实是一个安全隐患。
  
    如果我们在切片表达式中加入了第三个索引(即容量上界索引),如:

var slice1 = numbers3[1:4:4]   

    那么在这之后,无论我们怎样做都无法通过slice1访问到numbers3的值中的第五个元素。因为这超出了我们刚刚设定的slice1的容量。如果我们指定的元素上界索引或容量上界索引超出了被操作对象的容量,那么就会引发一个运行时恐慌(程序异常的一种),而不会有求值结果返回。因此,这是一个有力的访问控制手段。
  
    虽然切片值在上述方面受到了其容量的限制,但是我们却可以通过另外一种手段对其进行不受任何限制地扩展。这需要使用到内建函数appendappend会对切片值进行扩展并返回一个新的切片值。使用方法如下:

slice1 = append(slice1, 6, 7)

    通过上述操作,slice1的值变为了[]int{2, 3, 4, 6, 7}。注意,一旦扩展操作超出了被操作的切片值的容量,那么该切片的底层数组就会被自动更换。这也使得通过设定容量上界索引来对其底层数组进行访问控制的方法更加严谨了。
  
    我们要介绍的最后一种操作切片值的方法是“复制”。该操作的实施方法是调用copy函数。该函数接受两个类型相同的切片值作为参数,并会把第二个参数值中的元素复制到第一个参数值中的相应位置(索引值相同)上。这里有两点需要注意:
  
  1. 这种复制遵循最小复制原则,即:被复制的元素的个数总是等于长度较短的那个参数值的长度。
  2. 与append函数不同,copy函数会直接对其第一个参数值进行修改。
  
      举例如下:

var slice4 = []int{0, 0, 0, 0, 0, 0, 0}
copy(slice4, slice1)   

    通过上述复制操作,slice4会变为[]int{2, 3, 4, 6, 7, 0, 0}




猜你喜欢

转载自blog.csdn.net/defending/article/details/80159766