Go language BASIC

Slice (slice)

    Is a sequence of variable length have the same type of element. It is based on array type package made of a layer. It is very flexible and supports automatic expansion. Slice is a reference type, it comprises an internal structure 地址, 长度and 容量. Sections generally for rapidly operating a data set.

Length of the array is fixed and part of the length of the array is of a type, so the array has many limitations.

  1 func arraySum(x [3]int) int{
  2     sum := 0
  3     for _, v := range x{
  4         sum = sum + v
  5     }
  6     return sum
  7 }

The sum function can only receive [3]inttype, the other is not supported. Another example,

  1 a := [3]int{1, 2, 3}

Array a has three elements, we can not continue to add new elements to the array a.

Defined sections

The basic syntax for declaring a slice type is as follows:

  1 var name []T

among them,

  • name: Specifies the variable name
  • T: represents the type of element sections

for example:

  . 1 FUNC main () {
   2  	// declare slice type 
  . 3  	var A [] String               // declare a string sections 
  . 4  	var B = [] {} int              // declaration and initialization of an integer slice 
  . 5  	var C = [] BOOL to false {,} to true // declaration and initialization of a Boolean slice 
  . 6  	var D = [] to false BOOL {,} to true // declare a Boolean slice and initialized 
  . 7  	fmt.Println (a)               // [] 
  . 8  	fmt.Println (B )               // [] 
  . 9  	fmt.Println (C)               // [to false to true] 
10  	fmt.Println (A == nil)        // to true 
. 11  	fmt.Println (B == nil)        // to false 
12 is 	fmt.Println (C == nil)        // to false 
13 is  	// fmt.Println (C == D) // slice is a reference type, does not support direct comparison, comparison only and nil 
14 }
Slice length and capacity

Slice has its own length and capacity, we can find the length by using the built-in len () function, using the built-cap () function evaluation capacity slice.

Based on the definition of an array slice

As the underlying slice is an array, so we can define array-based slice.

  . 1 FUNC main () {
   2  	// array slice based on the definition 
  . 3  	a: = [. 5] int {55, 56 is, 57 is, 58, 59}
   . 4  	B: = a [. 1:. 4]                      // Create based array a slice, comprising an element A [. 1], A [2], A [. 3] 
  . 5  	fmt.Println (B)                   // [56 is 57 is 58] 
  . 6  	fmt.Printf ( " type of B:% T \ n- ", B) // of B type: [] int 
  . 7 }

Also supports the following way:

  1 c := a[1:] //[56 57 58 59]
  2 d := a[:4] //[55 56 57 58]
  3 e := a[:]  //[55 56 57 58 59]
Then slice sliced

In addition to an array of get-based slice, we can also get a slice by slice.

  1 func main() {
  2 	//切片再切片
  3 	a := [...]string{"北京", "上海", "广州", "深圳", "成都", "重庆"}
  4 	fmt.Printf("a:%v type:%T len:%d  cap:%d\n", a, a, len(a), cap(a))
  5 	b := a[1:3]
  6 	fmt.Printf("b:%v type:%T len:%d  cap:%d\n", b, b, len(b), cap(b))
  7 	c := b[1:5]
  8 	fmt.Printf("c:%v type:%T len:%d  cap:%d\n", c, c, len(c), cap(c))
  9 }

输出:

  1 a:[北京 上海 广州 深圳 成都 重庆] type:[6]string len:6  cap:6
  2 b:[上海 广州] type:[]string len:2  cap:5
  3 c:[广州 深圳 成都 重庆] type:[]string len:4  cap:4

注意: 对切片进行再切片时,索引不能超过原数组的长度,否则会出现索引越界的错误。

使用make()函数构造切片

我们上面都是基于数组来创建的切片,如果需要动态的创建一个切片,我们就需要使用内置的make()函数,格式如下:

  1 make([]T, size, cap)

其中:

  • T:切片的元素类型
  • size:切片中元素的数量
  • cap:切片的容量

举个例子:

  1 func main() {
  2 	a := make([]int, 2, 10)
  3 	fmt.Println(a)      //[0 0]
  4 	fmt.Println(len(a)) //2
  5 	fmt.Println(cap(a)) //10
  6 }

上面代码中a的内部存储空间已经分配了10个,但实际上只用了2个。 容量并不会影响当前元素的个数,所以len(a)返回2,cap(a)则返回该切片的容量。

切片的本质

切片的本质就是对底层数组的封装,它包含了三个信息:底层数组的指针、切片的长度(len)和切片的容量(cap)。

举个例子,现在有一个数组a := [8]int{0, 1, 2, 3, 4, 5, 6, 7},切片s1 := a[:5],相应示意图如下。

slice_01

切片s2 := a[3:6],相应示意图如下:slice_02

切片不能直接比较

切片之间是不能比较的,我们不能使用==操作符来判断两个切片是否含有全部相等元素。 切片唯一合法的比较操作是和nil比较。 一个nil值的切片并没有底层数组,一个nil值的切片的长度和容量都是0。但是我们不能说一个长度和容量都是0的切片一定是nil,例如下面的示例:

  1 var s1 []int         //len(s1)=0;cap(s1)=0;s1==nil
  2 s2 := []int{}        //len(s2)=0;cap(s2)=0;s2!=nil
  3 s3 := make([]int, 0) //len(s3)=0;cap(s3)=0;s3!=nil

所以要判断一个切片是否是空的,要是用len(s) == 0来判断,不应该使用s == nil来判断。

切片的赋值拷贝

下面的代码中演示了拷贝前后两个变量共享底层数组,对一个切片的修改会影响另一个切片的内容,这点需要特别注意。

  1 func main() {
  2 	s1 := make([]int, 3) //[0 0 0]
  3 	s2 := s1             //将s1直接赋值给s2,s1和s2共用一个底层数组
  4 	s2[0] = 100
  5 	fmt.Println(s1) //[100 0 0]
  6 	fmt.Println(s2) //[100 0 0]
  7 }
  8 

切片遍历

切片的遍历方式和数组是一致的,支持索引遍历和for range遍历。

  1 func main() {
  2 	s := []int{1, 3, 5}
  3 
  4 	for i := 0; i < len(s); i++ {
  5 		fmt.Println(i, s[i])
  6 	}
  7 
  8 	for index, value := range s {
  9 		fmt.Println(index, value)
 10 	}
 11 }

append()方法为切片添加元素

Go语言的内建函数append()可以为切片动态添加元素。 每个切片会指向一个底层数组,这个数组能容纳一定数量的元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在append()函数调用时。 举个例子:

  1 func main() {
  2 	//append()添加元素和切片扩容
  3 	var numSlice []int
  4 	for i := 0; i < 10; i++ {
  5 		numSlice = append(numSlice, i)
  6 		fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)
  7 	}
  8 }

输出:

  1 [0]  len:1  cap:1  ptr:0xc0000a8000
  2 [0 1]  len:2  cap:2  ptr:0xc0000a8040
  3 [0 1 2]  len:3  cap:4  ptr:0xc0000b2020
  4 [0 1 2 3]  len:4  cap:4  ptr:0xc0000b2020
  5 [0 1 2 3 4]  len:5  cap:8  ptr:0xc0000b6000
  6 [0 1 2 3 4 5]  len:6  cap:8  ptr:0xc0000b6000
  7 [0 1 2 3 4 5 6]  len:7  cap:8  ptr:0xc0000b6000
  8 [0 1 2 3 4 5 6 7]  len:8  cap:8  ptr:0xc0000b6000
  9 [0 1 2 3 4 5 6 7 8]  len:9  cap:16  ptr:0xc0000b8000
 10 [0 1 2 3 4 5 6 7 8 9]  len:10  cap:16  ptr:0xc0000b8000

从上面的结果可以看出:

  1. append()函数将元素追加到切片的最后并返回该切片。
  2. 切片numSlice的容量按照1,2,4,8,16这样的规则自动进行扩容,每次扩容后都是扩容前的2倍。

append()函数还支持一次性追加多个元素。 例如:

  1 var citySlice []string
  2 // 追加一个元素
  3 citySlice = append(citySlice, "北京")
  4 // 追加多个元素
  5 citySlice = append(citySlice, "上海", "广州", "深圳")
  6 // 追加切片
  7 a := []string{"成都", "重庆"}
  8 citySlice = append(citySlice, a...)
  9 fmt.Println(citySlice) //[北京 上海 广州 深圳 成都 重庆]

切片的扩容策略

可以通过查看$GOROOT/src/runtime/slice.go源码,其中扩容相关代码如下:

  1 newcap := old.cap
  2 doublecap := newcap + newcap
  3 if cap > doublecap {
  4 	newcap = cap
  5 } else {
  6 	if old.len < 1024 {
  7 		newcap = doublecap
  8 	} else {
  9 		// Check 0 < newcap to detect overflow
 10 		// and prevent an infinite loop.
 11 		for 0 < newcap && newcap < cap {
 12 			newcap += newcap / 4
 13 		}
 14 		// Set newcap to the requested cap when
 15 		// the newcap calculation overflowed.
 16 		if newcap <= 0 {
 17 			newcap = cap
 18 		}
 19 	}
 20 }

从上面的代码可以看出以下内容:

  • 首先判断,如果新申请容量(cap)大于2倍的旧容量(old.cap),最终容量(newcap)就是新申请的容量(cap)。
  • 否则判断,如果旧切片的长度小于1024,则最终容量(newcap)就是旧容量(old.cap)的两倍,即(newcap=doublecap),
  • 否则判断,如果旧切片长度大于等于1024,则最终容量(newcap)从旧容量(old.cap)开始循环增加原来的1/4,即(newcap=old.cap,for {newcap += newcap/4})直到最终容量(newcap)大于等于新申请的容量(cap),即(newcap >= cap)
  • 如果最终容量(cap)计算值溢出,则最终容量(cap)就是新申请容量(cap)。

需要注意的是,切片扩容还会根据切片中元素的类型不同而做不同的处理,比如intstring类型的处理方式就不一样。

使用copy()函数复制切片

首先我们来看一个问题:

  1 func main() {
  2 	a := []int{1, 2, 3, 4, 5}
  3 	b := a
  4 	fmt.Println(a) //[1 2 3 4 5]
  5 	fmt.Println(b) //[1 2 3 4 5]
  6 	b[0] = 1000
  7 	fmt.Println(a) //[1000 2 3 4 5]
  8 	fmt.Println(b) //[1000 2 3 4 5]
  9 }

由于切片是引用类型,所以a和b其实都指向了同一块内存地址。修改b的同时a的值也会发生变化。

Go语言内建的copy()函数可以迅速地将一个切片的数据复制到另外一个切片空间中,copy()函数的使用格式如下:

  1 copy(destSlice, srcSlice []T)

其中:

  • srcSlice: 数据来源切片
  • destSlice: 目标切片

举个例子:

  1 func main() {
  2 	// copy()复制切片
  3 	a := []int{1, 2, 3, 4, 5}
  4 	c := make([]int, 5, 5)
  5 	copy(c, a)     //使用copy()函数将切片a中的元素复制到切片c
  6 	fmt.Println(a) //[1 2 3 4 5]
  7 	fmt.Println(c) //[1 2 3 4 5]
  8 	c[0] = 1000
  9 	fmt.Println(a) //[1 2 3 4 5]
 10 	fmt.Println(c) //[1000 2 3 4 5]
 11 }

从切片中删除元素

Go语言中并没有删除切片元素的专用方法,我们可以使用切片本身的特性来删除元素。 代码如下:

  1 func main() {
  2 	// 从切片中删除元素
  3 	a := []int{30, 31, 32, 33, 34, 35, 36, 37}
  4 	// 要删除索引为2的元素
  5 	a = append(a[:2], a[3:]...)
  6 	fmt.Println(a) //[30 31 33 34 35 36 37]
  7 }




归类 :GO语言

Guess you like

Origin www.cnblogs.com/lz1996/p/12078146.html