Go切片详解

今天我们将讨论 切片(slice) 。它们和数组非常相似,但由于切片更加灵活,在 Go 中我们会更频繁地使用切片而不是数组


切片声明(Slice declaration)

切片的声明方式和数组很像,只是不需要指定大小

go 复制代码
var s []int

由于切片的类型中不包含长度信息 ,你可以给同一个切片变量 s 赋值不同长度的切片:

go 复制代码
s = []int{0, 1, 3}
s = []int{120, 56}
s = []int{43, 42, 12, 12}

和数组一样,你可以使用 [] 来访问或修改切片中的元素:

go 复制代码
s[0] = 20
fmt.Println(s[0]) // 将打印 20

切片与数组的核心区别

数组和切片的核心区别在于:

  • 数组直接保存数据

  • 切片 保存的是指向数组的引用(指针)

就像指针一样,切片的默认值是 nil。当切片未初始化时,也会表现出类似指针的行为。

go 复制代码
var s []int // 声明了一个切片,但没有初始化;
            // s 等于 nil

s[0] = 10   // 会 panic:index out of range [0] with length 0

代码解释

这里的 snil,没有底层数组,因此不能访问任何元素。


初始化切片(Initialization)

如果你需要一个包含大量元素的切片(例如 1000 个元素),该怎么办呢?

这时就需要用到 make 函数

make 函数

make 用于分配并初始化切片(或 map、channel)。

new 不同:

  • new 只接收类型

  • make 接收 长度(len)容量(cap)

make 返回的是 切片本身,而不是切片的指针,因为切片已经是引用类型。

go 复制代码
var s []int
s = make([]int, 6) // 如果不指定容量,cap 默认等于 len

代码解释

  • len:切片中已经存在的元素个数

  • 这些元素都会被初始化为类型的零值

  • cap:在不重新分配底层数组的情况下,切片最多可以扩展到的长度


获取长度和容量

你可以使用内置函数获取切片(或数组)的长度和容量:

go 复制代码
var length = len(s)
var capacity = cap(s)

数组 vs 切片(类比)

数组和切片的关系,有点类似于:

变量 vs 指针

它们的相似点包括:

  • 都需要使用内置函数进行初始化

  • 默认值都是 nil

  • 都不直接存储数据,而是引用数据


创建多维切片(Making a multi-dimensional slice)

make 只会初始化最外层切片

如果是 切片的切片,你需要手动初始化每一层。

下面的例子中,我们创建了一个 10×10 的二维整型切片

go 复制代码
var mds [][]int              // 声明一个二维切片

mds = make([][]int, 10)      // 初始化最外层切片
fmt.Println("mds =", mds)    // mds = [[] [] [] [] [] [] [] [] [] []]

for i := range mds {         // 遍历外层切片
    mds[i] = make([]int, 10) // 初始化每一个内层切片
}

代码解释

  • make([][]int, 10) 只创建了 10 个 nil[]int

  • 必须在循环中为每个元素单独 make([]int, 10)

如果是三维切片,就需要嵌套两层循环。

目前只需要记住这个概念即可。


切片赋值(Slice assignment)

切片在赋值时的行为非常像指针

来看下面的例子:

go 复制代码
var s = []int{12, 23, 34}
var sn = s

sn[0] = 0
sn[1] = 11

代码解释

这里的 sn 并不是 s 的独立副本

当你修改 sn 的元素时,s 也会被修改:

go 复制代码
fmt.Println(s)  // [0 11 34]
fmt.Println(sn) // [0 11 34]

这是因为:

  • 切片只是指向同一个底层数组

  • var sn = s 只是复制了引用,而不是数据


数组的行为不同

如果 s 是数组,那么赋值时会复制整个数据

go 复制代码
var s = [3]int{12, 23, 34} // 指定长度,变成数组
var sn = s

sn[0] = 0
sn[1] = 11

fmt.Println(s)  // [12 23 34]
fmt.Println(sn) // [0 11 34]

代码解释

  • 数组是值类型

  • 赋值会拷贝全部元素

  • 修改 sn 不会影响 s


相关推荐
二进制coder6 小时前
C++ 中的 Interface:概念、实现与应用详解
开发语言·c++
古城小栈6 小时前
Go 与 Rust:系统编程语言的竞争与融合
开发语言·golang·rust
随风一样自由6 小时前
React编码时,什么时候用js文件,什么时候用jsx文件?
开发语言·javascript·react.js
bing.shao6 小时前
Golang select多路复用踩坑
数据库·golang·php
0和1的舞者6 小时前
SpringBoot配置文件
java·spring boot·后端·web·配置·spirng
by__csdn6 小时前
Vue3 生命周期全面解析:从创建到销毁的完整指南
开发语言·前端·javascript·vue.js·typescript·前端框架·ecmascript
小年糕是糕手6 小时前
【C++同步练习】模板初阶
服务器·开发语言·前端·javascript·数据库·c++·改行学it
多则惑少则明6 小时前
SpringAI框架接入-jdk升级21后报错“run failed: Unsupported class file major version 65”
java·后端·spring·springai
weixin_307779136 小时前
Jenkins Folders插件详解:组织、管理与最佳实践
运维·开发语言·自动化·jenkins