go中的切片

数组和切片

一、数组(Array)

  1. 定义与初始化

数组是固定长度的同类型元素的集合,长度在定义时确定,不能改变。

go 复制代码
// 定义一个长度为 3 的 int 数组
var arr [3]int

// 使用字面量初始化数组
arr := [3]int{1, 2, 3}

// 根据初始化的元素个数推导长度
arr := [...]int{1, 2, 3} // 长度为 3
  1. 数组的特点

固定长度:数组的长度在编译时确定,不能动态变化。

值类型:数组是值类型,赋值或传递给函数时会复制整个数组。

内存连续:数组的元素在内存中是连续存储的。

  1. 数组的使用
go 复制代码
arr := [3]int{1, 2, 3}
fmt.Println(arr[0]) // 输出 1
arr[1] = 10
fmt.Println(arr)    // 输出 [1 10 3]

切片是对数组的引用,数组的底层是切片的基础。切片可以通过数组创建,但数组不能从切片创建。

二、切片(Slice)

  1. 定义与初始化

切片是对数组的动态视图,长度和容量可以变化。

go 复制代码
// 定义一个空切片
var s []int

// 使用 make 函数创建切片
s := make([]int, 5)       // 长度为 5,容量为 5
s := make([]int, 5, 10)   // 长度为 5,容量为 10

// 使用字面量初始化切片
s := []int{1, 2, 3}
  1. 切片的特点

动态大小:切片的长度和容量可以在运行时动态变化。

引用类型:切片是引用类型,赋值或传递给函数时仅复制指针,不会复制底层数组。

共享底层数组:多个切片可以共享同一个底层数组。

  1. 切片的操作

切片操作:通过切片操作可以创建新的切片。

go 复制代码
arr := [5]int{1, 2, 3, 4, 5}
s := arr[1:4] // s 是 [2, 3, 4]

追加元素:使用 append 函数向切片追加元素。

go 复制代码
s := []int{1, 2}
s = append(s, 3, 4) // s 变为 [1, 2, 3, 4]

复制切片:使用 copy 函数将一个切片的元素复制到另一个切片。

go 复制代码
src := []int{1, 2, 3}
dst := make([]int, len(src))
copy(dst, src) // dst 变为 [1, 2, 3]

删除元素:通过切片操作删除元素。

go 复制代码
s := []int{1, 2, 3, 4}
s = append(s[:1], s[2:]...) // 删除索引为 1 的元素,s 变为 [1, 3, 4]
  1. 切片的零值与 nil 切片
go 复制代码
var s []int
fmt.Println(s == nil) // 输出 true
fmt.Println(len(s))   // 输出 0
fmt.Println(cap(s))   // 输出 0
  1. 切片的底层结构
    切片的底层是一个结构体,包含以下字段:
c 复制代码
type slice struct {
    array unsafe.Pointer
    len    int
    cap    int
}
array:指向底层数组的指针。

len:切片的长度。

cap:切片的容量。
  1. 切片的扩容机制

当切片的长度超过其容量时,Go 会自动扩容,通常是将容量翻倍。

扩容时,Go 会分配一个新的底层数组,并将原数组的数据复制到新数组中。

扩容的机制在容量1024以下是两倍扩容。

go 复制代码
s := make([]int, 5, 5)
s = append(s, 6) // 扩容,容量增加到 10
  1. 切片是引用类型,但赋值是浅拷贝

    切片是引用类型,但赋值操作仅复制切片的结构体,而不复制底层数组。这意味着多个切片可能共享同一个底层数组。

go 复制代码
a := []int{1, 2, 3}
b := a
b[0] = 10
fmt.Println(a) // 输出: [10, 2, 3]

如果需要创建切片的独立副本,应使用 copy 函数:

go 复制代码
c := make([]int, len(a))
copy(c, a)
  1. append 的行为可能导致切片共享底层数组

append 函数可能会扩容底层数组,也可能不会。如果扩容,append 会返回一个新的切片;否则,原切片和新切片共享底层数组。

go 复制代码
a := []int{1, 2, 3}
b := a
a = append(a, 4) //为了避免共享底层数组,可以在 append 后重新赋值
fmt.Println(a) // 输出: [1, 2, 3, 4]
fmt.Println(b) // 输出: [1, 2, 3]
  1. 切片的零值与空切片的区别

切片的零值是 nil,表示没有底层数组;而空切片是长度为 0 的切片,底层数组的容量可能大于 0。

go 复制代码
var s1 []int
s2 := []int{}
fmt.Println(s1 == nil) // 输出: true
fmt.Println(s2 == nil) // 输出: false

在判断切片是否为空时,建议使用 len(s) == 0,而不是 s == nil。

  1. 切片的切片操作不会复制底层数组

对切片进行切片操作(即创建子切片)不会复制底层数组,多个切片可能共享同一底层数组。这可能导致意外的修改。

go 复制代码
a := []int{1, 2, 3, 4}
b := a[1:3]
b[0] = 10
fmt.Println(a) // 输出: [1, 10, 3, 4]

如果需要独立的切片副本,应使用 copy 函数:

go 复制代码
c := make([]int, len(b))
copy(c, b)

切片的长度是基于切片范围的,而不是数组的元素数量

这里 arr[1:2] 表示从 arr[1] 开始,取到 arr[2](但不包括 arr[2]),所以实际上它只包含一个元素:arr[1]。

go 复制代码
brr 是从 arr 中取出的切片,起始位置是 arr[1],结束位置是 arr[2](不包括 arr[2])。因此,brr 只包含一个元素,即 arr[1],其值为 0。

	var arr [3]int
	fmt.Println(arr) //[0 0 0]
	brr := arr[1:2]//brr 的内容是 [0],并且它指向 arr[1]。
	brr = append(brr, 9)//执行 append(brr, 9) 时,Go 会检查 brr 是否有足够的容量来容纳新元素。
	//brr 的容量是 2,它可以容纳新元素 9。由于 brr 的容量足够,它就会在原来的底层数组上添加 9。
	fmt.Println(brr) //[0 9]
	fmt.Println(arr) //[0 0 9]




    var arr [3]int
    fmt.Println(arr) //[0 0 0]
    brr := arr[1:2]
    brr = append(brr, 9)
    brr = append(brr, 9)  //第二次append由于容量不足触发了重分配,不再使用arr相同内存
    
    fmt.Println(brr) //[0 9 9] 
    fmt.Println(arr) //[0 0 9]
  1. append 在循环中使用可能导致性能问题

    在循环中频繁使用 append 可能导致多次内存分配,影响性能。建议预先分配足够容量的切片。

go 复制代码
	s := make([]int, 0, 1000)
    for i := 0; i < 1000; i++ {
    s = append(s, i)
    }
相关推荐
lly2024063 小时前
Vue.js 自定义指令
开发语言
csdddn3 小时前
php 8.4.7 更新日志
开发语言·php
掘根4 小时前
【Qt】多线程
java·开发语言·qt
egoist20234 小时前
[linux仓库]图解System V共享内存:从shmget到内存映射的完整指南
linux·开发语言·共享内存·system v
兰亭妙微4 小时前
兰亭妙微QT软件开发与UI设计协同:如何避免设计与实现脱节?
开发语言·qt·ui
1710orange5 小时前
java设计模式:动态代理
java·开发语言·设计模式
开心-开心急了5 小时前
PySide6 文本编辑器(QPlainTextEdit)实现查找功能——重构版本
开发语言·python·ui·重构·pyqt
郝学胜-神的一滴6 小时前
Effective Python 第39条:通过@classmethod多态来构造同一体系中的各类对象
开发语言·python·程序人生·软件工程
IT森林里的程序猿6 小时前
基于Python的招聘信息可视化分析系统
开发语言·python