Go 数组、切片与Map:复制、共享和标签统计
在同一个示例中观察数组值复制、切片共享底层存储、显式复制切片,以及Map计数和键存在性检查。
本节目标
在同一个示例中观察数组值复制、切片共享底层存储、显式复制切片,以及Map计数和键存在性检查。
前置知识
掌握变量、for range和基本函数调用。示例中的标签都是字符串,不涉及嵌套对象的深度复制。
准备与运行
在一个新的练习目录执行以下命令。目录名可以修改;若该目录已经初始化过模块,不要重复执行go mod init。
mkdir go-lesson-05
cd go-lesson-05
go mod init example.com/devcn/lesson05将下方完整代码保存为当前目录的main.go,然后执行:
go fmt ./...
go run .先用go version确认工具链可用。本例在Go 1.25.7、macOS ARM64环境验证,只使用标准库;没有测试所有操作系统和Go版本。
核心概念
数组具有固定长度,长度属于类型,例如[2]string与[3]string是不同类型。给数组变量赋值会复制它的元素。本例数组元素是字符串,因此修改副本中的一个字符串位置,不影响原数组。
切片用于描述底层数组的一段范围。把一个切片赋给另一个变量,两个切片可能访问同一组元素。通过其中一个切片改元素,另一个就能看到变化。append的返回值必须接收;当容量不足时它可能换用新的底层数组,所以不要把追加后的共享关系当成固定保证。
需要独立元素副本时,可以创建等长切片并使用copy。Map则用于按键查找数据;本例把标签映射到次数,比每次扫描整个列表更直接。
完整示例
package main
import "fmt"
func main() {
original := [2]string{"Go", "SQL"}
copied := original
copied[0] = "Rust"
fmt.Println(original[0], copied[0])
tags := []string{"Go", "SQL", "Go"}
shared := tags
shared[0] = "Golang"
cloned := make([]string, len(tags))
copy(cloned, tags)
cloned[0] = "Rust"
fmt.Println(tags[0], shared[0], cloned[0])
counts := make(map[string]int)
for _, tag := range tags {
counts[tag]++
}
fmt.Println(counts["Go"], counts["Golang"])
value, ok := counts["Java"]
fmt.Println(value, ok)
}运行后应得到:
Go Rust
Golang Golang Rust
1 1
0 false代码如何工作
第一组代码复制数组并改动副本,输出Go与Rust,说明原数组没有跟着变化。
第二组代码让shared直接引用tags,再改shared[0],tags[0]也变为Golang。随后显式复制得到cloned,改cloned[0]不会改变tags。
最后遍历标签统计次数,并通过count, ok := counts[key]区分缺失的键。示例按固定键读取,不依赖Map遍历顺序,因此预期输出稳定。
常见错误与边界
- 切片赋值不是元素的完整复制;是否共享要从创建和修改方式判断。
- 对nil map可以读,但不能直接写;使用make或字面量初始化后再计数。
- 数组、切片、Map里的元素如果本身还包含引用关系,浅层复制不等于复制整个对象图。
- 不依赖Map遍历顺序生成需要稳定排序的页面。需要稳定顺序时,先提取键并排序。
练习
为counts写入一个值为0的已存在键,再查询它和一个不存在的键,比较ok的差别。
参考答案
在Map初始化之后加入下面代码:
counts["empty"] = 0
value, ok := counts["empty"]
fmt.Println(value, ok)
value, ok = counts["missing"]
fmt.Println(value, ok)两次数值都是0,但标记分别为true和false。因此业务上不能只凭数值为0判断一个键不存在。
文章回复
0 条公开回复