Go 中切片赋值不会创建底层数组副本:如何避免函数意外修改原始字节切片

在 go 中,切片是引用类型,直接赋值(如 `cryptkey := alphabet`)仅复制切片头(指针、长度、容量),不复制底层数组;因此对 `cryptkey` 的就地修改会同步影响 `alphabet`。解决方法是在函数内显式创建底层数组的深拷贝。

Go 的切片([]byte)本质上是一个轻量级结构体,包含指向底层数组的指针、当前长度(len)和容量(cap)。当你执行 cryptkey := alphabet 时,两个变量共享同一块内存——这正是 shuffle() 函数中 out := b 导致原始 alphabet 被意外打乱的根本原因。

要真正隔离数据,必须创建底层数组的副本。最简洁、惯用的方式是使用 append([]byte(nil), b...):

func shuffle(b []byte) []byte {
    l := len(b)
    // ✅ 创建独立副本:分配新底层数组并拷贝所有元素
    out := append([]byte(nil), b...)
    for key := range out {
        dest := rand.Intn(l)
        out[key], out[dest] = out[dest], out[key]
    }
    return out
}

该写法等价于 make([]byte, len(b)); copy(out, b),但更简洁且零分配冗余。注意:append([]byte(nil), b...) 是 Go 官方推荐的无依赖深拷贝模式,安全、高效、无需额外导入。

⚠️ 其他常见误区需避免:

  • out := b 或 out := &b[0]:仍指向原数组,无效;
  • out := b[:len(b):len(b)]:仅改变容量,不复制数据;
  • 忘记初始化 rand.Seed():会导致每次运行生成相同“随机”序列(生产环境应添加 rand.Seed(time.Now().UnixNano()))。

最终,alphabet 保持不变,cryptkey 是其独立、随机重排后的副本——语义清晰,内存安全,符合 Go 的显式拷贝哲学。