
go 中切片是引用类型,直接赋值不会复制底层数组;若需保持原切片不变,必须显式创建独立副本(如用 append([]byte(nil), src...)),否则 shuffle 等就地操作会同时影响原始数据。
在 Go 语言中,[]byte 是一个切片(slice),其底层结构包含指向数组的指针、长度(len)和容量(cap)。当你执行 cryptkey := alphabet 时,并未创建新底层数组,而是让 cryptkey 和 alphabet 共享同一块内存。因此,后续对 cryptkey 的任何就地修改(如交换元素)都会直接反映在 alphabet 上——这正是你观察到“两个切片都被打乱”的根本原因。
要真正隔离数据,必须进行深拷贝(deep copy)。最简洁、惯用且安全的方式是使用 append 构造新切片:
out := append([]byte(nil), b...)
该语句等价于:分配一个长度为 len(b)、类型为 []byte 的新切片,并将 b 的所有元素逐个复制进去。[]byte(nil) 提供空切片作为起点,append 自动处理内存分配,语义清晰且零分配冗余(相比 make([]byte, len(b)) + copy() 更简练)。
修正后的完整示例:
package main
import (
"fmt"
"math/rand"
"time" // 注意:添加 time 包以正确初始化随机种子
)
func main() {
alphabet := []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz.")
cryptkey := alphabet // 此时仍共享底层数组(但后续 shuffle 不再影响它)
fmt.Println("Original alphabet:", string(alphabet))
// 初始化随机种子,避免每次运行结果相同
rand.Seed(time.Now().UnixNano())
cryptkey = shuffle(cryptkey)
fmt.Println("After shuffle — alphabet unchanged:", string(alphabet))
fmt.Println("Shuffled cryptkey:", string(cryptkey))
}
func shuffle(b []byte) []byte {
l := len(b)
if l <= 1 {
return append([]byte(nil), b...) // 边界情况也保证副本
}
out := append([]byte(nil), b...) // ✅ 关键:创建独立副本
for i := range out {
dest := rand.Intn(l)
out[i], out[dest] = out[dest], out[i]
}
return out
}⚠️ 注意事项:
- rand.Intn 在 Go 1.20+ 已弃用,生产环境建议改用 rand.New(rand.NewSource(seed)).Intn(l) 或 rand.New(rand.NewPCG()),但本例为简洁保留旧用法(需配合 rand.Seed);
- 切勿使用 out := b[:len(b):len(b)] 或 out := b,它们仍共享底层数组;
- 若需高性能批量复制,copy(dst, src) 亦可,但 append(..., b...) 更符合 Go 的惯用风格且不易出错。
总结:Go 中切片赋值不等于复制数据。凡涉及可能修改切片内容的函数(如 shuffle、reverse、sort.Slice 等),务必在函数内部通过 append([]T(nil), s...) 或 copy 显式创建副本,才能保障输入参数的不可变性(immutability)与函数纯度(purity)。







