
copy() 函数仅复制 len(src) 和 len(dst) 中的较小值个元素,因此目标切片必须预先分配足够容量,否则复制结果为空。本文详解其原理、正确用法及常见误区。
`copy()` 函数仅复制 `len(src)` 和 `len(dst)` 中的较小值个元素,因此目标切片必须预先分配足够容量,否则复制结果为空。本文详解其原理、正确用法及常见误区。
在 Go 中,copy() 是内置的高效切片复制函数,但初学者常误以为“只要传入源切片就能自动创建副本”,结果却得到空切片。根本原因在于:copy(dst, src) 仅复制 min(len(dst), len(src)) 个元素——它不会为 dst 分配内存,也不会扩展其长度;它只在 dst 已有的底层数组范围内进行写入。
❌ 错误示例:目标切片未初始化或长度为 0
arr := []int{1, 2, 3}
tmp := []int{} // len(tmp) == 0,底层数组可能 nil 或存在但长度为 0
n := copy(tmp, arr)
fmt.Println("copied:", n, "tmp:", tmp) // 输出: copied: 0 tmp: []此处 len(tmp) == 0,因此 min(0, 3) == 0,copy 不执行任何复制操作,tmp 保持原状。
✅ 正确做法:为目标切片预分配长度(非容量)
要获得完整副本,dst 必须具有至少与 src 相同的长度(len):
arr := []int{1, 2, 3}
tmp := make([]int, len(arr)) // ✅ 长度明确为 3,底层数组已分配
n := copy(tmp, arr)
fmt.Println("copied:", n, "tmp:", tmp) // 输出: copied: 3 tmp: [1 2 3]? 注意:make([]T, len) 分配的是长度(length),而非仅容量(capacity)。copy() 依据的是 len(dst),不是 cap(dst)。即使 cap(tmp) > len(arr),只要 len(tmp) >= len(arr),即可完整复制。
? 更灵活的写法(推荐用于通用工具函数)
若需动态适配不同长度的源切片,可统一按源长度构造目标切片:
func cloneSlice[T any](src []T) []T {
dst := make([]T, len(src))
copy(dst, src)
return dst
}
// 使用示例
original := []string{"a", "b", "c"}
duplicate := cloneSlice(original)
duplicate[0] = "X" // 修改副本不影响原切片
fmt.Println(original, duplicate) // [a b c] [X b c]⚠️ 关键注意事项
- copy() 不会 panic:即使 dst 为 nil 或长度不足,它也安全返回复制数量(0),不会崩溃——这既是便利也是隐患,需主动校验返回值;
- 区分 len 与 cap:copy() 只关心 len(dst);cap(dst) 更大时,多余容量不可用,除非后续用 append;
- 深拷贝限制:copy() 是浅拷贝。若切片元素为指针、map、slice 或 struct 含引用字段,则副本与原切片共享底层数据,修改嵌套内容仍会影响彼此;
- 字符串到字节切片:copy([]byte, string) 是合法特例,此时 dst 必须是 []byte,且长度足够容纳字符串 UTF-8 字节数(可用 len([]byte(s)) 获取)。
✅ 总结
| 场景 | 是否可行 | 说明 |
|---|---|---|
| copy(make([]T, len(src)), src) | ✅ 推荐 | 安全、清晰、语义明确 |
| copy(make([]T, 0, len(src)), src) | ❌ 无效 | len(dst)==0 → 复制 0 个元素 |
| copy(append([]T(nil), src...), src) | ⚠️ 冗余 | append 已完成复制,无需再 copy |
牢记:copy() 是“搬运工”,不是“建造者”。你需要先准备好目的地(具备足够长度的切片),它才开始工作。掌握这一前提,就能避免绝大多数切片复制陷阱。










