DevCN
菜单
全部文章快讯开发深度热点

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判断一个键不存在。

DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。