讲师中心 微信公众号
AI工具推荐 视频效率加速

Go语言中切片/向量赋值的引用行为与深度复制策略

小静君_3855

小静君_3855

发布时间:2025-07-10 20:02:02

|

617人浏览过

|

来源于php中文网

原创

Go语言中切片/向量赋值的引用行为与深度复制策略

本文深入探讨了Go语言中复合类型(特别是container/vector,以及广义上的切片)在赋值操作时的行为。当将一个vector或切片赋值给另一个变量时,实际上是复制了其内部的指针或结构头,而非底层数据,这导致两个变量共享同一份数据。文章通过具体代码示例,解释了这一“引用”行为带来的潜在问题,并提供了使用InsertVector方法实现深度复制的正确策略,以确保数据独立性。

Go语言的赋值语义:值类型与引用类型

在go语言中,变量的赋值行为取决于其类型。基本数据类型(如int, float, bool, string)以及数组(当作为值传递时)在赋值时会进行值复制,即创建一个独立的副本。这意味着修改副本不会影响原始值。

然而,对于复合类型如切片(slice)、映射(map)、通道(channel)以及指针类型,其赋值行为则表现出引用特性。这些类型在内部通常包含指向底层数据的指针或描述符。当一个变量赋值给另一个变量时,复制的不是底层数据本身,而是这个指针或描述符。因此,两个变量最终会指向或操作同一份底层数据。

在早期Go版本中,container/vector包提供的Vector类型也是如此。Vector实际上是一个包含指向底层数组指针的结构体。当您将一个Vector实例赋值给另一个变量时,您复制的是这个结构体本身,而结构体中的指针仍然指向同一份底层数据。

container/vector的特性与陷阱

在提供的PegPuzzle示例代码中,movesAlreadyDone字段被定义为*vector.Vector,即一个指向vector.Vector的指针。

type PegPuzzle struct {
    movesAlreadyDone * vector.Vector;
}

问题出现在NewChildPegPuzzle函数中:

立即学习“go语言免费学习笔记(深入)”;

func NewChildPegPuzzle(parent *PegPuzzle) *PegPuzzle{
    retVal := new(PegPuzzle);
    retVal.movesAlreadyDone = parent.movesAlreadyDone; // 问题所在
    return retVal
}

这行代码 retVal.movesAlreadyDone = parent.movesAlreadyDone; 并没有创建一个新的vector.Vector实例并复制parent中的数据,而是简单地将parent.movesAlreadyDone(一个指向vector.Vector的指针)的值赋给了retVal.movesAlreadyDone。结果是,parent和retVal的movesAlreadyDone字段都指向了同一个vector.Vector实例。

因此,当通过cp1.doMove(Move{1,1,2,3})向cp1的movesAlreadyDone添加元素时,实际上是修改了它们共享的那个vector.Vector实例。随后,当cp2 = NewChildPegPuzzle(p)被调用时,cp2的movesAlreadyDone也指向了同一个共享的vector.Vector。所以,当cp2.doMove(Move{3,2,5,1})执行后,cp2的movesAlreadyDone会包含cp1之前添加的移动以及cp2自己添加的移动,因为它们操作的是同一个底层数据结构。

Go语言(Golang)1.26.0
Go语言(Golang)1.26.0

Go语言(Golang)1.26.0版本官方下载,版本号 1.26.0,适合旧项目维护、兼容性测试和指定版本开发环境搭建。

下载

实现深度复制:正确的方法

为了解决上述问题,确保NewChildPegPuzzle返回的子谜题拥有其独立的movesAlreadyDone历史,我们需要执行深度复制。这意味着不仅要创建新的PegPuzzle实例,还要为movesAlreadyDone字段创建一个全新的vector.Vector实例,并将父谜题中的所有元素复制到这个新实例中。

container/vector包提供了InsertVector方法,可以用于将一个Vector的内容插入到另一个Vector中,从而实现深度复制。

以下是修正后的InitPegPuzzle和NewChildPegPuzzle函数:

package main

import "fmt"
import "container/vector" // 注意:此包在现代Go版本中已不推荐使用,推荐使用内置切片

type Move struct { x0, y0, x1, y1 int }

type PegPuzzle struct {
    movesAlreadyDone * vector.Vector;
}

// 修正:vector.New 已被删除,应使用 new(vector.Vector)
func (p *PegPuzzle) InitPegPuzzle(){
    p.movesAlreadyDone = new (vector.Vector);
}

// 修正:实现movesAlreadyDone的深度复制
func NewChildPegPuzzle(parent *PegPuzzle) *PegPuzzle{
    retVal := new (PegPuzzle);
    retVal.InitPegPuzzle (); // 初始化新的vector实例
    // 将父谜题的movesAlreadyDone内容深度复制到新的retVal.movesAlreadyDone中
    retVal.movesAlreadyDone.InsertVector (0, parent.movesAlreadyDone);
    return retVal
}

func (p *PegPuzzle) doMove(move Move){
    p.movesAlreadyDone.Push(move);
}

func (p *PegPuzzle) printPuzzleInfo(){
    fmt.Printf("-----------START----------------------\n");
    fmt.Printf("moves already done: %v\n", p.movesAlreadyDone);
    fmt.Printf("------------END-----------------------\n");
}

func main() {
    p := new(PegPuzzle);
    cp1 := new(PegPuzzle);
    cp2 := new(PegPuzzle);

    p.InitPegPuzzle();

    cp1 = NewChildPegPuzzle(p);
    cp1.doMove(Move{1,1,2,3});
    cp1.printPuzzleInfo(); // 此时 cp1 的 movesAlreadyDone 包含 {1,1,2,3}

    cp2 = NewChildPegPuzzle(p);
    cp2.doMove(Move{3,2,5,1});
    cp2.printPuzzleInfo(); // 此时 cp2 的 movesAlreadyDone 包含 {1,1,2,3}, {3,2,5,1}
                           // 注意:这里仍然会包含 {1,1,2,3},因为 NewChildPegPuzzle(p) 是基于 p 的初始状态,
                           // 而 p 在 cp1 操作后并未被修改。
                           // 如果期望 cp2 仅包含自己的 move,则 p 应该在 NewChildPegPuzzle(p) 调用前不被修改,
                           // 或者 NewChildPegPuzzle 应该从 cp1 复制,而非 p。
                           // 修正后的代码解决了 cp1 和 cp2 不共享 vector 实例的问题。
                           // 原始意图可能是 cp1 和 cp2 都从 "p" 的初始空状态派生,
                           // 那么 cp1 和 cp2 应该各自独立。
                           // 运行修正后的代码,cp1 会显示 {1,1,2,3},cp2 会显示 {3,2,5,1}。
                           // 这是因为 NewChildPegPuzzle(p) 每次都从 p (其 movesAlreadyDone 始终为空) 复制。
}

运行修正后的main函数,输出将是:

-----------START----------------------
moves already done: [1 1 2 3]
------------END-----------------------
-----------START----------------------
moves already done: [3 2 5 1]
------------END-----------------------

这表明cp1和cp2现在拥有独立的movesAlreadyDone向量。

注意事项与最佳实践

  1. container/vector的废弃: 值得注意的是,container/vector包在现代Go语言版本中已被废弃,不推荐使用。Go语言内置的切片(slice)提供了更强大、更灵活且性能更优越的功能,并且是Go语言中处理动态数组的标准方式。
  2. 切片的深度复制: 如果使用切片代替container/vector,深度复制通常通过copy()函数结合make()创建新切片来实现。例如:
    func NewChildPegPuzzleSlice(parent *PegPuzzleSlice) *PegPuzzleSlice {
        retVal := new(PegPuzzleSlice)
        // 创建一个足够大的新切片
        retVal.movesAlreadyDone = make([]Move, len(parent.movesAlreadyDone))
        // 深度复制元素
        copy(retVal.movesAlreadyDone, parent.movesAlreadyDone)
        return retVal
    }
  3. 理解Go的内存模型: 深入理解Go语言中值类型、指针、切片、映射等复合类型的内存布局和赋值行为至关重要。这有助于避免因共享底层数据而导致的意外副作用,特别是在并发编程中。
  4. 性能考量: 深度复制会涉及额外的内存分配和数据拷贝,对于非常大的数据结构,这可能会带来性能开销。在某些场景下,如果数据不被修改,或者可以通过其他机制(如不可变数据结构、写时复制等)来管理,共享数据可能是更高效的选择。但在需要独立状态的场景下,深度复制是必要的。

总结

Go语言中,像container/vector(以及内置切片、映射等)这样的复合类型在赋值时,复制的是指向底层数据的引用(或描述符),而非底层数据本身。这是一种“浅复制”行为。要实现数据独立性,避免多个变量意外地共享和修改同一份数据,必须进行“深度复制”。对于container/vector,可以使用InsertVector方法;对于Go内置切片,则通常结合make和copy函数来完成。理解并正确应用这些复制策略是编写健壮、可预测Go程序的关键。

热门AI工具

更多
Seko
Seko Hot

一款AI视频创作工具,主要用于商汤科技推出的创编一体的AI短视频创作Agent,适合需要提升相关任务效率的用户。

二狗PPT
二狗PPT Hot

一款AI演示文稿工具,主要用于专为中式职场打造的AI PPT生成工具,适合需要提升相关任务效率的用户。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Atoms
Atoms Hot

Atoms是一款AI智能体工具,第一支自动构建真实业务的 AI 团队。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

相关专题

更多
数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3011

2023.10.31

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

554

2025.10.31

c语言 数据类型
c语言 数据类型

本专题整合了c语言数据类型相关内容,阅读专题下面的文章了解更多详细内容。

522

2026.02.12

string转int
string转int

在编程中,我们经常会遇到需要将字符串(str)转换为整数(int)的情况。这可能是因为我们需要对字符串进行数值计算,或者需要将用户输入的字符串转换为整数进行处理。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

6479

2023.08.02

css中float用法
css中float用法

css中float属性允许元素脱离文档流并沿其父元素边缘排列,用于创建并排列、对齐文本图像、浮动菜单边栏和重叠元素。想了解更多float的相关内容,可以阅读本专题下面的文章。

6667

2024.04.28

C++中int、float和double的区别
C++中int、float和double的区别

本专题整合了c++中int和double的区别,阅读专题下面的文章了解更多详细内容。

764

2025.10.23

golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

4794

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

4951

2025.07.04

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

0

2026.10.22

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn