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

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

小静君_3855

小静君_3855

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

|

1018人浏览过

|

来源于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 所添加的移动记录。

AI舌诊面诊问诊医疗系统+诊疗商城系统源码
AI舌诊面诊问诊医疗系统+诊疗商城系统源码

AI 舌诊面诊问诊医疗系统 + 诊疗商城系统源码是一套结合 AI 辅助诊断与商品转化的健康类程序,基于浩创云、ThinkPHP(PHP)开发,支持微信服务号与 H5 端,适合做中医舌诊面诊问答、健康建议推送以及配套诊疗商品的运营。

下载

此外,原始代码中 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工具

更多
咔片AIPPT

一款在线AI演示文稿制作工具,可根据主题和内容需求辅助生成PPT结构与页面,提高演示材料制作效率。

Loomy
Loomy Hot

一款AI工具,主要用于科大讯飞发布的桌面级 AI 助理,比 OpenClaw 更易用、更安全!,适合需要提升相关任务效率的用户。

豆包大模型

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

AionClaw
AionClaw Hot

AionClaw是一款面向办公、创作和编程任务的AI桌面智能体。

UP简历
UP简历 Hot

一款AI办公效率工具,主要用于基于AI技术的免费在线简历制作工具,适合需要提升相关任务效率的用户。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

火山引擎

火山引擎是一款面向企业的云计算与AI服务平台。

WorkBuddy

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

DeepSeek

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

相关专题

更多
string转int
string转int

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

6459

2023.08.02

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

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

4774

2025.06.09

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

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

4911

2025.07.04

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

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

4774

2025.06.09

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

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

4911

2025.07.04

string转int
string转int

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

6459

2023.08.02

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

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

3145

2024.08.29

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

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

4048

2025.08.29

剧云故事大纲与分场规划教程
剧云故事大纲与分场规划教程

本专题讲解剧云梗概与多级大纲功能,覆盖全局、分集、分场结构生成,以及主线冲突、剧情节点和场次顺序调整。

0

2026.10.22

热门下载

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

精品课程

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

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