从数组或者切片派生切片(取子切片)
baseContainer
baseContainer[low : high] // 双下标形式
baseContainer[low : high : max] // 三下标形式
上面所示的双下标形式等价于下面的三下标形式:
baseContainer[low : high : cap(baseContainer)]
所以双下标形式是三下标形式的特例。在实践中,双下标形式使用得相对更为广泛。
(注意:三下标形式是从Go 1.2开始支持的。)
上面所示的取子切片表达式的语法形式中的下标必须满足下列关系,否则代码要么编译不通过,要么在运行时刻将造成恐慌。
// 双下标形式
0 <= low <= high <= cap(baseContainer)
// 三下标形式
0 <= low <= high <= max <= cap(baseContainer)
下面我们举几个案例:
注意:from ,to 都是从下标0开始计算
baseContainer[from:to]
baseContainer[3:5] //表示包含下标为3的数值,不包含下包为5的数值
s := make([]int, 0, 6)
s = append(s,[]int{1,2,3,4,5,6}...)
//s[包含下标:不包含下标]
//注意:from to 下标从0开始数
fmt.Println(s[3:]) // 4 5 6 [from:] 包含下标为3的值
fmt.Println(s[:3]) // 1 2 3 [:to] 不包含下标为3的值
fmt.Println(s[3:4]) //4 [from:to) 左闭右开 from <= s < to 包含下标为3的 不包含下标为4的
一些关于遍历映射条目的细节:
- 映射中的条目的遍历顺序是不确定的(可以认为是随机的)。或者说,同一个映射中的条目的两次遍历中,条目的顺序很可能是不一致的,即使在这两次遍历之间,此映射并未发生任何改变。
- 如果在一个映射中的条目的遍历过程中,一个还没有被遍历到的条目被删除了,则此条目保证不会被遍历出来。
- 如果在一个映射中的条目的遍历过程中,一个新的条目被添加入此映射,则此条目并不保证将在此遍历过程中被遍历出来。
mm
for key := range m {
delete(m, key)
}
for
for i := 0; i < len(anArrayOrSlice); i++ {
element := anArrayOrSlice[i]
// ...
}
for-range
for key, element = range aContainer {...}
有三个重要的事实存在:
aContaineraContainer
aContaineraContainerfor-range
下面这个例子验证了上述第一个和第二个事实
package main
import "fmt"
func main() {
type Person struct {
name string
age int
}
persons := [2]Person {{"Alice", 28}, {"Bob", 25}}
for i, p := range persons {
fmt.Println(i, p)
// 此修改会体现在这个遍历过程中,
persons[1].name = "Jack"
// 此修改不会反映到persons数组中,因为p
// 是persons数组的副本中的一个元素的副本。
p.age = 31
}
fmt.Println("persons:", &persons)
}
输出结果:
0 {Alice 28}
1 {Bob 25}
persons: &[{Alice 28} {Jack 25}]
如果我们将上例中的数组改为一个切片,则在循环中对此切片的修改将在循环过程中体现出来。 但是对循环变量的修改仍然不会体现在此切片中。
...
// 改为一个切片。
persons := []Person {{"Alice", 28}, {"Bob", 25}}
for i, p := range persons {
fmt.Println(i, p)
// 这次,此修改将反映在此次遍历过程中。
persons[1].name = "Jack"
// 这个修改仍然不会体现在persons切片容器中。
p.age = 31
}
fmt.Println("persons:", &persons)
}
输出结果变成了:
0 {Alice 28}
1 {Jack 25}
persons: &[{Alice 28} {Jack 25}]
下面这个例子验证了上述的第二个和第三个事实:
package main
import "fmt"
func main() {
langs := map[struct{ dynamic, strong bool }]map[string]int{
{true, false}: {"JavaScript": 1995},
{false, true}: {"Go": 2009},
{false, false}: {"C": 1972},
}
// 此映射的键值和元素类型均为指针类型。
// 这有些不寻常,只是为了讲解目的。
m0 := map[*struct{ dynamic, strong bool }]*map[string]int{}
for category, langInfo := range langs {
m0[&category] = &langInfo
// 下面这行修改对映射langs没有任何影响。
category.dynamic, category.strong = true, true
}
for category, langInfo := range langs {
fmt.Println(category, langInfo)
}
m1 := map[struct{ dynamic, strong bool }]map[string]int{}
for category, langInfo := range m0 {
m1[*category] = *langInfo
}
// 映射m0和m1中均只有一个条目。
fmt.Println(len(m0), len(m1)) // 1 1
fmt.Println(m1) // map[{true true}:map[C:1972]]
}
上面已经提到了,映射条目的遍历顺序是随机的。所以下面前三行的输出顺序可能会略有不同:
{false true} map[Go:2009]
{false false} map[C:1972]
{true false} map[JavaScript:1995]
1 1
map[{true true}:map[Go:2009]]
range
for-rangeforfafb
type Buffer struct {
start, end int
data [1024]byte
}
func fa(buffers []Buffer) int {
numUnreads := 0
for _, buf := range buffers {
numUnreads += buf.end - buf.start
}
return numUnreads
}
func fb(buffers []Buffer) int {
numUnreads := 0
for i := range buffers {
numUnreads += buffers[i].end - buffers[i].start
}
return numUnreads
}
把数组指针当做数组来使用
对于某些情形,我们可以把数组指针当做数组来使用。
range
package main
import "fmt"
func main() {
var a [100]int
for i, n := range &a { // 复制一个指针的开销很小
fmt.Println(i, n)
}
for i, n := range a[:] { // 复制一个切片的开销很小
fmt.Println(i, n)
}
}
for-rangerange
package main
import "fmt"
func main() {
var p *[5]int // nil
for i, _ := range p { // okay
fmt.Println(i)
}
for i := range p { // okay
fmt.Println(i)
}
for i, n := range p { // panic
fmt.Println(i, n)
}
}
切片克隆
对于目前的标准编译器(1.16版本),最简单的克隆一个切片的方法为:
sClone := append(s[:0:0], s...)
s
sClone := append([]T(nil), s...)
上面这两种append实现都有一个缺点:它们开辟的内存块常常会比需要的略大一些从而可能造成一点小小的不必要的性能损失。 我们可以使用这两种方法来避免这个缺点:
// 两行make+copy实现:
sClone := make([]T, len(s))
copy(sClone, s)
// 或者下面的make+append实现。
// 对于目前的官方Go工具链v1.16来说,这种
// 实现比上面的make+copy实现略慢一点。
sClone := append(make([]T, 0, len(s)), s...)
s
var sClone []T
if s != nil {
sClone = make([]T, len(s))
copy(sClone, s)
}
appendmakecopymake+copyappend
删除一段切片元素
前面已经提到了切片的元素在内存中是连续存储的,相邻元素之间是没有间隙的。所以,当切片的一个元素段被删除时,
- 如果剩余元素的次序必须保持原样,则被删除的元素段后面的每个元素都得前移。
- 如果剩余元素的次序不需要保持原样,则我们可以将尾部的一些元素移到被删除的元素的位置上。
fromtofromto
// 第一种方法(保持剩余元素的次序):
s = append(s[:from], s[to:]...)
// 第二种方法(保持剩余元素的次序):
s = s[:from + copy(s[from:], s[to:])]
// 第三种方法(不保持剩余元素的次序):
if n := to-from; len(s)-to < n {
copy(s[from:to], s[to:])
} else {
copy(s[from:to], s[len(s)-n:])
}
s = s[:len(s)-(to-from)]
如果切片的元素可能引用着其它值,则我们应该重置因为删除元素而多出来的元素槽位上的元素值,以避免暂时性的内存泄露:
// "len(s)+to-from"是删除操作之前切片s的长度。
temp := s[len(s):len(s)+to-from]
for i := range temp {
temp[i] = t0
}
for-rangememclr
删除一个元素
删除一个元素是删除一个元素段的特例。在实现上可以简化一些。
i
// 第一种方法(保持剩余元素的次序):
s = append(s[:i], s[i+1:]...)
// 第二种方法(保持剩余元素的次序):
s = s[:i + copy(s[i:], s[i+1:])]
// 上面两种方法都需要复制len(s)-i-1个元素。
// 第三种方法(不保持剩余元素的次序):
s[i] = s[len(s)-1]
s = s[:len(s)-1]
如果切片的元素可能引用着其它值,则我们应该重置刚多出来的元素槽位上的元素值,以避免暂时性的内存泄露:
s[len(s):len(s)+1][0] = t0
// 或者
s[:len(s)+1][len(s)] = t0
// 第一种方法(保持剩余元素的次序):
s = append(s[:i], s[i+1:]...)
// 第二种方法(保持剩余元素的次序):
s = s[:i + copy(s[i:], s[i+1:])]
// 第三种方法(不保持剩余元素的次序):
s[i] = s[len(s)-1]
s = s[:len(s)-1]
条件性地删除切片元素
有时,我们需要删除满足某些条件的切片元素。
// 假设T是一个小尺寸类型。
func DeleteElements(s []T, keep func(T) bool, clear bool) []T {
// result := make([]T, 0, len(s))
result := s[:0] // 无须开辟内存
for _, v := range s {
if keep(v) {
result = append(result, v)
}
}
if clear { // 避免暂时性的内存泄露。
temp := s[len(result):]
for i := range temp {
temp[i] = t0 // t0是类型T的零值
}
}
return result
}
TTfor-rangeT
将一个切片中的所有元素插入到另一个切片中
ielementss
// 第一种方法:单行实现。
s = append(s[:i], append(elements, s[i:]...)...)
// 上面这种单行实现把s[i:]中的元素复制了两次,并且它可能
// 最多导致两次内存开辟(最少一次)。
// 下面这种繁琐的实现只把s[i:]中的元素复制了一次,并且
// 它最多只会导致一次内存开辟(最少零次)。
// 但是,在当前的官方标准编译器实现中(1.16版本),此
// 繁琐实现中的make调用将会把所有刚开辟出来的元素清零。
// 这其实是没有必要的。所以此繁琐实现并非总是比上面的
// 单行实现效率更高。事实上,它仅在处理小切片时更高效。
if cap(s) >= len(s) + len(elements) {
s = s[:len(s)+len(elements)]
copy(s[i+len(elements):], s[i:])
copy(s[i:], elements)
} else {
x := make([]T, 0, len(elements)+len(s))
x = append(x, s[:i]...)
x = append(x, elements...)
x = append(x, s[i:]...)
s = x
}
// Push(插入到结尾)。
s = append(s, elements...)
// Unshift(插入到开头)。
s = append(elements, s...)
插入若干独立的元素
插入若干独立的元素和插入一个切片中的所有元素类似。 我们可以使用切片组合字面量构建一个临时切片,然后使用上面的方法插入这些元素。
特殊的插入和删除:前推/后推,前弹出/后弹出
es
// 前弹出(pop front,又称shift)
s, e = s[1:], s[0]
// 后弹出(pop back)
s, e = s[:len(s)-1], s[len(s)-1]
// 前推(push front)
s = append([]T{e}, s...)
// 后推(push back)
s = append(s, e)
append