学习《GO语言学习笔记》的学习笔记之5. 3 数据之切片 (详解)

本文适合初学者阅读
  • 5.3 切片
  • 切片本身并非动态数组或数组指针. 它内部通过指针引用底屋数组, 设定相关属性将数据读写操作限定在指定区域内.
  • 切片本身是只只读对象, 其工作机制类似数据指针的一种包装.
  • 可基于数组或数组指针创建切片, 以开始和结束索引位置确定所引用的数组片段. 不支持反向索引, 实际范围是个右半开区间.
  • 属性cap表示切片所引用数组片段的真实长度, len用于限定可读写的元素数量, 另外, 数组必须addressable, 否则会引发错误.
  • 和数组一样, 切片同样使用索引号访问元素的内容. 超始索引为0, 而非对应的底层数组真实索引位置.
package main

import "fmt"

func main() {
	x := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0}

	s := x[2:5]

	for i := 0; i < len(s); i++ {
		fmt.Printf("%d: %x\n", i, s[i])
	}

}
// 结果输出:
// 0: 2
// 1: 3
// 2: 4

这点类似于python的切片, 可以是list或tuple,dict的切片

  • 可直接创建切片对象, 无须预先准备数组. 因为是引用类型, 须使用make函数或显式初始化语句, 它会自动完成底层数组内存分配
package main

import "fmt"

func main() {
	s1 := make([]int, 3, 5)    //指定长度len为3, 容量cap为5, 初始值为0
	s2 := make([]int, 6)		// 指定长度len为6, 省略容量cap的值, 和len相等, 初始值为0
	s3 := []int{10, 20, 8:10}   // 按初始元素分配底层数组, 并设置len和cap

	fmt.Println(s1, len(s1), cap(s1))
	fmt.Println(s2, len(s2), cap(s2))
	fmt.Println(s3, len(s3), cap(s3))

}
// 结果输出:
//[0 0 0] 3 5
// [0 0 0 0 0 0] 6 6
// [10 20 0 0 0 0 0 0 10] 9 9

注意以下两种方式的区别:

package main

func main() {
	var a []int   // 仅定义了一个[]int类型变量, 没有初始化操作

	b := []int{}  // 使用初始化表达式完成了全部创建过程

	println(a == nil, b == nil)
}
// true false
  • 切片不支持比较操作., 就算元素类型支持也不行, 仅能判断是否为nil
  • 可获取元素地址, 但不能向数组那样直接用指针访问元素内容
  • 切片只是很小的结构体对象, 用来代替数组传参可避免复制开锁. 还有make函数允许在运行期动态指定数组长度, 绕开了数组类型必须使用编译期常量的限制.
reslice(再切片)也就是在切片的基础再次切片
  • 不能超出cap, 但不受len限制
package main

import "fmt"

func main() {
	d := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	s1 := d[2:7]

	s2 := s1[:4]

	for i := 0;i < len(s2); i++{
		s2[i] += 100
	}

	fmt.Println(d)
	fmt.Println(s1)
	fmt.Println(s2)
}


// result out:
// [0 1 102 103 104 105 6 7 8 9]
// [102 103 104 105 6]
// [102 103 104 105]
  • 利用reslice操作, 很容易就能实现一个栈式数据结构
package main

import (
	"errors"
	"fmt"
)

func main() {
	stack := make([]int, 0, 5) // 初始化, 容量最大为5

	push := func(x int) error {
		n := len(stack)
		if n == cap(stack) {
			return errors.New("stack is full")
		}
		stack = stack[:n+1]
		stack[n] = x
		return nil
	}

	pop := func() (int, error) {
		n := len(stack)

		if n == 0 {
			return 0, errors.New("stack is empty")
		}

		x := stack[n-1]
		stack = stack[:n-1]

		return x, nil

	}

	for i := 0; i < 7; i++ {
		fmt.Printf("push %d: %v, %v\n", i, push(i), stack)
	}

	for i := 0; i < 7; i++ {
		x, err := pop()
		fmt.Printf("pop %d: %v, %v\n", x, err, stack)
	}

}

// result out:
// push 0: <nil>, [0]
// push 1: <nil>, [0 1]
// push 2: <nil>, [0 1 2]
// push 3: <nil>, [0 1 2 3]
// push 4: <nil>, [0 1 2 3 4]
// push 5: stack is full, [0 1 2 3 4]
// push 6: stack is full, [0 1 2 3 4]
// pop 4: <nil>, [0 1 2 3]
// pop 3: <nil>, [0 1 2]
// pop 2: <nil>, [0 1]
// pop 1: <nil>, [0]
// pop 0: <nil>, []
// pop 0: stack is empty, []
// pop 0: stack is empty, []
append
  • 向切片尾部添加数据, 返回新的切片对象.
package main

import (
	"fmt"
)

func main() {
	s := make([]int, 0, 5)

	s1 := append(s,10)
	s2 := append(s1, 20, 30)

	fmt.Println(s, len(s), cap(s))
	fmt.Println(s1, len(s1), cap(s1))
	fmt.Println(s2, len(s2), cap(s2))
}

// result out:
// [] 0 5
// [10] 1 5
// [10 20 30] 3 5
  • 数据被追加到原底层数据.若超出cap限制, 则为新切片对象重新分配数组.
package main

import (
	"fmt"
)

func main() {
	s := make([]int, 0, 100)

	s1 := s[:2:4]
	s2 := append(s1, 20, 30, 40, 50, 60, 70)

	fmt.Printf("s1: %p: %v\n", &s1[0], s1)
	fmt.Printf("s2: %p: %v\n", &s2[0], s2)

	fmt.Printf("s data: %v\n", s[:10])
	fmt.Printf("s1 cap: %d, s2 cap %d\n", cap(s1), cap(s2))

}

// result out:

// s1: 0xc000082000: [0 0]
// s2: 0xc000084000: [0 0 20 30 40 50 60 70]    // 数组地址不同, 确认新分配
// s data: [0 0 0 0 0 0 0 0 0 0]                // append并未向原数组写入部分数据
// s1 cap: 4, s2 cap 8							// 新数组是原cap的2倍
  1. 是超出切片cap限制, 而非底层数组长度限制, 因为cap可小于数组长度
  2. 新分配数组长度是原cap的2倍, 而非原数组的2倍
  • 向nil切片追加数据时, 会为其分配 底层数组内存.
  • 正因为存在重新分配底层数组, 应该预留足够多的空间, 避免中途内存分配和数据复制的开销.
copy

在两个切片对象间复制数据, 允许指向同一底层数据, 允许目标区间重叠. 最终所复制长度以较短的切片长度(len)为准.

package main

import (
	"fmt"
)

func main() {
	s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

	s1 := s[5:8]

	n := copy(s[4:], s1) // 在同一底层数组的不同区间复制
	fmt.Println(n, s)

	s2 := make([]int, 6)

	n = copy(s2, s)   // 在不同数组间复制

	fmt.Println(n, s2)

}

// result out:
// 3 [0 1 2 3 5 6 7 7 8 9]
// 6 [0 1 2 3 5 6]
  • 如果切片中长时间引用大数组中很小的片段, 则建议新建独立切片, 复制出所需要的数据, 以便原数组内存可被及时回收.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值