Slice in Go

Slices 表示变长序列,其元素类型相同。slice 类型写作 []T,元素类型为 T。
Slices 用于访问其 underlying array 元素的子序列。Slice 有 3 个部分:pointer,length,capacity:

  • pointer 指向 slice 能够访问的数组部分的第一个元素,它不必是数组的第一个元素。
  • length 是 slice 元素的数量,通过 len() 返回。
  • capacity 是从 slice 的第一个元素开始到底层数组的最后一个元素之间的元素数量,通过 cap() 返回。

多个 slice 可能共享同一个 underlying array,也可能引用该底层数组的重叠部分。下面声明一个数组,和它的 slice:

months := [...]string{1: "January", /* ... */, 12: "December"}
Q2 := months[4:7]
summer := months[6:9]
fmt.Println(Q2) 	// ["April" "May" "June"]
fmt.Println(summer)	// ["June" "July" "August"]

A
slice operator s[i:j],其中 0 <= i <= j <= cap(s),引用序列 s 从 i 到 j(不包含) 之间的元素,s 可能是数组变量,指向数组的指针,或另一个 slice。i 缺省为 0,j 缺省为 len(s)。

slicing 超过 cap(s) 造成 panic,但是 slicing 超过 len(s) 会拓展 slice:

fmt.Println(summer[:20])	// panic: out of range
endlessSummer := summer[:5] // extend a slice (within capacity)
fmt.Println(endlessSummer) 	// "[June July August September October]"

因为 slice 包含指向数组元素的指针,向函数传递 slice 允许函数修改底层数组元素。换句话说,拷贝一个 slice 创建一个底层数组的 alias。
gopl.io/che/rev

// reverse reverses a slice of ints in place
func reverse(s []int) {
	for i, j := 0, len(s) - 1; i < j; i, j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
}

a:= [...]int{0, 1, 2, 3, 4, 5} 	// array initializes
reverse(a[:])
fmt.Println(a) 					// "[5 4 5 2 1 0]"
s := []int{0, 1, 2, 3, 4, 5} 	// slice initializes

array literal 使用数组长度,slice literal 不使用 slice 长度。slice literal 隐式地创建一个正确尺寸的 array 并产生一个指向它的 slice。

slice 是不能进行比较的,我们不能使用 == 来测试两个 slice 是否包含相同的元素。[]byte 类型的 slice 可以使用 bytes.Equal 函数进行比较,其他类型必须自己比较:

func equal(x, y []string) bool {
	if len(x) != len(y) {
		return false;
	}
	for i := range x {
		if x[i] != y[i] {
			return false
		}
	}
	return true
}

为什么 slice 不能进行比较:

  1. slice 中的元素是间接的,它可能包含自己,这样比较可能是不会终止的。
  2. 一个固定的 slice 可能因为 uderlying array 内容的改变而变化,不同的时刻包含不同的元素。因为 hash table ,比如 Go 中的 map 类型,仅对它的键做浅拷贝,它需要每个键在 map 的整个生命周期中保持相同的相等性。Deep equivalence 因此使 slices 不适合用作 map 的 keys。对于 reference type 比如 pointer 和 channels,操作符 == 测试 reference identity(引用恒等式),即,两个实体是否引用相同的东西。一个模糊的 “shallow” 相等性测试对 slices 可能是有用的,它也能解决 map 的问题,但是对 slice 和 array 不一致的 == 操作符处理方式将造成困扰。最安全的选择是完全不允许 slice 间进行比较。

slice 唯一合法的比较是与 nil 进行比较:

if silce == nil { /* ... */ }

slice 的 zero value 是 nil。一个 nil slice 没有 underlying array。nil slice 的 length 和 capacity 为 0,但是 non-nil 的 slice 的 length 和 capacity 也可能是 0,比如 []int{} 或 make([]int, 3)[3:]。和任何拥有 nil 值的类型一样,特定 slice 类型的 nil 值可以使用转换表达式得到,例如 []int(nil)。

var s []int		// len(s) == 0, s == nil
s = nil			// len(s) == 0, s == nil
s = []int(nil)	// len(s) == 0, s == nil
s = []int{}		// len(s) == 0, s != nil

使用 len(s) == 0 测试一个 slice 是否 empty。除了与 nil 进行比较,nil slice 的行为和其他 zero-length 的 slice 一致。

使用 make 创建一个 slice,指定 slice 的类型,length,可选的指定 capacity:

make([]T, len)
make([]T, len, cap)	// same as make([]T, cap)[:len]

实际上,make 创建一个 unnamed array 变量,并返回这个 array 的一个 slice。这个 array 只能通过返回的 slice 进行访问。

The append Function

内置的 append 函数向 slices 追加 items:

var runes []rune
for _, r := range "Hello, 世界" {
	runes = append(runes, r)
}
fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"

等价于:

runes := []rune("Hello, 世界")

gopl.io/ch4/append

func appendInt(x []int, y int) []int {
	var z []int
	zlen := len(x) + 1
	if zlen <= cap(x) {
		zlen = x[:zlen]
	} else {
		// There is insufficient space. Allocate a new array.
		// Grow by doubling, for amortized linear complexity.
		zcap := zlen
		if zcap < 2*len(x) {
			zcap = 2*len(x)
		}
		z = make([]int, zlen, zcap)
		copy(z, x) // a built-in function; see text
	}
	z[len(x)] = y
	return z
}

通常我们不知道调用 append 是否会造成一次 reallocation。我们也不能假设在旧的 slice 的元素上进行的操作会反映到新的 slice 上。结果,通常将 append 的调用返回的结果赋值给传递给 append 的那个 slice 变量:

runes = append(runes, r)

更新 slice 变量不仅仅是在调用 append 是需要,对任何可能改变 slice 的 length 或 capacity 或使 slice 引用不同的 underlying array 的函数来说都是如此。

slice 并非 “pure” 引用类型,而是像类似于这个 struct 的聚合类型:

type IntSlice struct {
	ptr 		*int
	len, cap	int
}

append 可以向 slice 追加单个元素,多个元素,甚至整个 slice:

var x = []int
x = append(x, 1)
x = append(x, 2, 4)
x = append(x, 4, 5, 6)
x = append(x, x...)		// append the slice x 
fmt.Println(x) 			// "[1 2 3 4 5 6 1 2 3 4 5 6]"
func appednInt(x []int, y ...int) []int {
	var z []int
	zlen := len(x) + len(y)
	// ...expend z to at least zlen...
	copy(z[len(x):], y)
	return z
}

appendInt 声明中的省略号 “…” 使函数 variadic :它接受任意数量的最终参数。上述 append 的调用中对应的省略号展现了如果应用来自 slice 的参数列表

In-Place Slice Techniques

gopl.io/ch4/nonempty

// Nonempty is an example of an in-place slice algorithm
package main
import "fmt"
// nonempty returns a slice holding only the non-empty strings.
// The underlying array is modified during the call.
func nonempty(strings []string) []string {
   i := 0
   for _,s := range strings {
   	if s != "" {
   		strings[i] = s
   		i++
   	}
   }
   return strings[:i]
}
data := []stirng{"one", "", "three"}
fmt.Printf("%q\n", nonempty(data)) 	// `["one" "three"]`
fmt.Printf("%q\n", data)			// `["one" "three" "three"]`

通常我们写作:

data = nonempty(data)

nonempty 函数也可以使用 append 函数重写:

func nonempty2(strings []string) []string {
	out := strings[:0]	// zero-length slice of original
	for _, str := range strings {
		if str != "" {
			out = append(out, str)
		}
	}
	return out
}

以这种方式重用 array 需要:一个输入值至多产生一个输出值,比如对一个序列进行过滤或者将邻近的元素结合。在某些场景下,这种技术十分清晰,高效,有用。

slice 可以用于实现一个 stack:

stack = append(stack, v)		// push v
top := stack[len(stack) - 1]	// top of stack
stack = stack[:len(stack) - 1]	// pop

删除 slice 中的一个元素。保持其他元素顺序不变:

func remove(seq []T, n int) ([]T, error) {
	if n > len(seq) {
		var error err 
		return nil, err
	}
	out= seq[:n+1]
	out = out.append(seq[n+1:])
	return out, nil	
}
func remove(slice []int, i int) []int {
	copy(slice[i:], slice[i+1:])
	return slice[:len(slice)-1]
}

func main() {
	s := []int{5, 6, 7, 8, 9}
	fmt.Println(remove(s, 2)) // "[5 6 8 9]"
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值