0

0

Go 语言中切片(Vector)的赋值与复制:理解值传递与共享状态

心靈之曲

心靈之曲

发布时间:2025-07-10 20:06:01

|

998人浏览过

|

来源于php中文网

原创

Go 语言中切片(Vector)的赋值与复制:理解值传递与共享状态

Go 语言中切片(或旧版 container/vector)的赋值操作是值传递,但其内部结构包含指向底层数组的指针。这意味着直接赋值只会复制切片头信息,导致新旧切片共享同一底层数据,而非创建独立副本。为避免意外的数据修改,需要明确执行深拷贝操作,以确保数据独立性。

Go 语言中的值传递与切片(Slice)结构

在 go 语言中,所有的数据传递都是值传递。这意味着当你将一个变量赋值给另一个变量,或者将一个变量作为函数参数传递时,go 都会创建一个该变量的副本。对于基本类型(如 int, string, bool 等),这很容易理解,因为副本拥有独立的数据。

然而,对于复合类型,尤其是切片(Slice),其行为可能会让人感到困惑。一个 Go 切片([]T)并不是底层数组本身,而是一个包含三个字段的结构体:

  1. 指向底层数组的指针 (Pointer):指向切片数据存储的内存地址。
  2. 长度 (Length):切片中当前元素的数量。
  3. 容量 (Capacity):从切片起点到底层数组末尾的元素数量。

当你在 Go 中对一个切片进行赋值操作时,例如 sliceB = sliceA,复制的实际上是这个三字段的切片头信息。这意味着 sliceA 和 sliceB 现在都包含一个指向同一个底层数组的指针。因此,如果你通过 sliceB 修改了底层数组中的某个元素,sliceA 也会看到这个改变,反之亦然。这便是所谓的“共享状态”或“浅拷贝”问题。

问题剖析:为什么会出现共享状态?

在提供的 PegPuzzle 示例代码中,问题出在 NewChildPegPuzzle 函数的这一行:

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

这里,movesAlreadyDone 是一个 *vector.Vector 类型(即指向 vector.Vector 结构体的指针)。当执行 retVal.movesAlreadyDone = parent.movesAlreadyDone; 时,复制的是 parent.movesAlreadyDone 这个指针的值。结果是,retVal.movesAlreadyDone 和 parent.movesAlreadyDone 都指向内存中的同一个 vector.Vector 实例

因此,当你在 cp1 上调用 doMove 时:

cp1.doMove(Move{1,1,2,3});

cp1.movesAlreadyDone.Push(move) 会修改这个共享的 vector.Vector 实例。随后,当你创建 cp2 并执行其 doMove 时:

cp2 = NewChildPegPuzzle(p); // 此时 cp2.movesAlreadyDone 依然指向 p.movesAlreadyDone 所指向的同一个 vector 实例
cp2.doMove(Move{3,2,5,1});

cp2.movesAlreadyDone.Push(move) 同样修改的是那个共享的 vector.Vector 实例。这就是为什么 cp2 在打印时会同时显示 cp1 和 cp2 所添加的移动记录。

GradPen论文
GradPen论文

GradPen是一款AI论文智能助手,深度融合DeepSeek,为您的学术之路保驾护航,祝您写作顺利!

下载

此外,原始代码中 InitPegPuzzle 的初始化方式也存在问题:vector.New(0) 在较新版本的 Go 中已被移除。正确的初始化方式是使用 new(vector.Vector)。

正确的切片(Vector)复制方法

为了避免这种共享状态,我们需要执行“深拷贝”,即创建一个全新的 vector.Vector 实例,并将源 vector 的所有元素复制到新的实例中。

对于 container/vector 库,可以使用 InsertVector 方法来实现深拷贝。这个方法可以将另一个 vector 的内容插入到当前 vector 的指定位置。通过将其插入到新 vector 的起始位置,可以实现完整的复制。

以下是修正后的 PegPuzzle 相关代码:

package main

import (
    "fmt"
    "container/vector" // 注意:此库已在Go 1.0版本后废弃,不推荐在新项目中使用
)

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

type PegPuzzle struct {
    movesAlreadyDone *vector.Vector // 指向 vector.Vector 实例的指针
}

// InitPegPuzzle 初始化 PegPuzzle,创建新的 vector.Vector 实例
func (p *PegPuzzle) InitPegPuzzle(){
    // 修正:使用 new(vector.Vector) 来创建新的 vector 实例
    p.movesAlreadyDone = new(vector.Vector)
}

// NewChildPegPuzzle 创建一个子 PegPuzzle,并深拷贝父级的 movesAlreadyDone
func NewChildPegPuzzle(parent *PegPuzzle) *PegPuzzle{
    retVal := new(PegPuzzle)
    retVal.InitPegPuzzle() // 初始化子 PegPuzzle 的 movesAlreadyDone 为一个独立的空 vector

    // 深拷贝:将父级 vector 的所有元素插入到子级 vector 中
    // InsertVector(index, otherVector) 将 otherVector 的内容插入到当前 vector 的 index 位置
    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)
    p.InitPegPuzzle() // 初始化主谜题的 vector

    cp1 := NewChildPegPuzzle(p) // cp1 获得 p 的 movesAlreadyDone 的深拷贝
    cp1.doMove(Move{1,1,2,3})
    cp1.printPuzzleInfo() // 此时 cp1 应该只包含 {1,1,2,3}

    cp2 := NewChildPegPuzzle(p) // cp2 再次获得 p 的 movesAlreadyDone 的深拷贝 (此时 p 的 vector 仍为空)
    cp2.doMove(Move{3,2,5,1})
    cp2.printPuzzleInfo() // 此时 cp2 应该只包含 {3,2,5,1}

    // 验证原始 p 的 movesAlreadyDone 是否仍为空
    p.printPuzzleInfo() // 此时 p 应该仍为空
}

通过上述修正,NewChildPegPuzzle 函数会为 retVal.movesAlreadyDone 创建一个全新的 vector.Vector 实例,然后将 parent.movesAlreadyDone 中的所有元素复制到这个新实例中。这样,cp1 和 cp2 就拥有了独立的 movesAlreadyDone 列表,彼此的操作不会相互影响。

注意事项与最佳实践

  1. container/vector 库已废弃:需要特别强调的是,container/vector 库自 Go 1.0 版本发布后就已经被废弃,不推荐在新的 Go 项目中使用。Go 语言的内置切片([]T)提供了更强大、更高效且更符合 Go 惯用法的动态数组功能。
  2. 现代 Go 切片 ([]T) 的复制
    • 使用 copy() 函数:这是最常见的深拷贝方式,用于将一个切片的内容复制到另一个切片。
      sourceSlice := []int{1, 2, 3}
      destSlice := make([]int, len(sourceSlice)) // 创建一个新切片,大小与源切片相同
      copy(destSlice, sourceSlice)               // 复制元素
    • 使用 append 到新切片:适用于创建新切片并追加元素,也可以实现拷贝。
      sourceSlice := []int{1, 2, 3}
      destSlice := append([]int{}, sourceSlice...) // 创建一个空切片,然后追加源切片的所有元素
    • 切片表达式:虽然 newSlice := oldSlice[:] 看起来像拷贝,但它创建的仍然是共享底层数组的浅拷贝。只有当你修改底层数组时,两个切片都会受影响。
  3. 理解指针语义:Go 语言中对于复合类型的赋值和传递,理解其是复制“值”(即结构体头信息或指针)而非“数据本体”至关重要。当需要独立的数据副本时,务必进行显式的深拷贝操作。

总结

Go 语言的切片赋值和传递遵循值传递原则,但由于切片本身的结构包含指向底层数据的指针,直接赋值会导致多个切片共享同一底层数据。为了实现真正的独立副本,必须执行深拷贝操作,例如使用 container/vector 库的 InsertVector 方法,或者对于现代 Go 的内置切片 ([]T),使用 copy() 函数或 append 技巧。理解这一机制是编写健壮、可预测的 Go 程序的关键。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
string转int
string转int

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

442

2023.08.02

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

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

220

2025.06.09

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

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

192

2025.07.04

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

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

220

2025.06.09

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

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

192

2025.07.04

string转int
string转int

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

442

2023.08.02

int占多少字节
int占多少字节

int占4个字节,意味着一个int变量可以存储范围在-2,147,483,648到2,147,483,647之间的整数值,在某些情况下也可能是2个字节或8个字节,int是一种常用的数据类型,用于表示整数,需要根据具体情况选择合适的数据类型,以确保程序的正确性和性能。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

544

2024.08.29

c++怎么把double转成int
c++怎么把double转成int

本专题整合了 c++ double相关教程,阅读专题下面的文章了解更多详细内容。

73

2025.08.29

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
10分钟--Midjourney创作自己的漫画
10分钟--Midjourney创作自己的漫画

共1课时 | 0.1万人学习

Midjourney 关键词系列整合
Midjourney 关键词系列整合

共13课时 | 0.9万人学习

AI绘画教程
AI绘画教程

共2课时 | 0.2万人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号