当前位置:首页 > 文章列表 > Golang > Go问答 > 在 Go 中如何进行数组的复用和重置操作?

在 Go 中如何进行数组的复用和重置操作?

来源:stackoverflow 2024-03-18 22:27:29 0浏览 收藏

Go 语言中,数组可以按值传递或通过地址传递。按值传递的数组无法被函数修改,而通过地址传递的数组则可以。为了修改数组,可以传递数组的地址或引用数组的切片。数组的内容可以通过重新分配或使用 for 循环逐个元素地进行覆盖。Go 语言的编译器会进行逃逸分析,以确定变量是否需要在堆上分配,如果变量没有逃逸,则可以将其分配在堆栈上以提高性能。

问题内容

如何在 go 中清除并重用数组? 将所有值分配给默认值的手动 for 循环是唯一的解决方案吗?

package main

import (
    "fmt"
)

func main() {
    arr := [3]int{1,2,3}
    fmt.Println(arr) // Output: [1 2 3]

    // clearing an array - is there a faster/easier/less verbose way?
    for i := range arr {
        arr[i] = 0
    }
    
    fmt.Println(arr) // Output: [0 0 0]
}

解决方案


我从your comment看到你仍然不确定这里发生了什么:

我想分配一个数组一次,然后在 for 循环的迭代中重用它(当然,在使用前清除)。您的意思是 arr = [3]int{} 进行重新分配,而不是使用 for i := range arr {arr[i] = 0} 进行清除?

首先,让我们完全排除数组的问题。假设我们有这个循环:

for i := 0; i < 10; i++ {
    v := 3
    // ... code that uses v, but never shows &v
}

这是否会在循环的每次行程中创建一个变量 v,还是在循环外部创建一个变量 v 并在循环的每次行程中将 3 粘贴到顶部的变量中循环的? 语言本身无法为您提供这个问题的答案。该语言描述了程序的行为,即v每次都被初始化为3,但如果我们从不观察&v,也许&v每次都是相同

如果我们选择观察 &v,并在某个实现中实际运行该程序,我们将看到实现的答案。现在事情变得有趣了。 语言表示每个 v(在循环的新行程中分配的每个 v)独立于任何先前的 v。如果我们采用 &v,则 v 的每个实例至少有可能在循环的后续行程中仍然处于活动状态。因此,每个 v 不得干扰任何之前变量 v。编译器保证每次重新分配它的简单方法是使用重新分配。

当前的go编译器使用escape analysis来尝试检测某个变量的地址是否被占用。如果是这样,则该变量是堆分配的而不是堆栈分配的,并且运行时系统依赖(运行时)垃圾收集器来释放该变量。我们可以用一个简单的程序 on the Go playground 来演示这一点:

package main

import (
    "fmt"
    "runtime"
)

func main() {
    for i := 0; i < 10; i++ {
        if i > 5 {
            runtime.gc()
        }
        v := 3
        fmt.printf("v is at %p\n", &v)
    }
}

不能保证该程序的输出如下所示,但这是我运行它时得到的结果:

v is at 0xc00002c008
v is at 0xc00002c048
v is at 0xc00002c050
v is at 0xc00002c058
v is at 0xc00002c060
v is at 0xc00002c068
v is at 0xc000094040
v is at 0xc000094050
v is at 0xc000094040
v is at 0xc000094050

请注意,一旦我们强制垃圾收集器开始运行,当 i 的值从 6 到 10 时,v 的地址就开始重合(尽管是交替)。这是因为 v 确实每次都会重新分配,但是通过让 gc 运行,我们使一些先前分配的、不再使用的内存再次可用。 (确切地说,这种交替的原因有点神秘,但这种行为可能取决于许多因素,例如 go 版本、运行时启动分配、系统愿意使用多少个线程等等。) p>

这里我们展示的是,go 的逃逸分析认为 v 逃逸了,所以它每次都会分配一个新的。我们将 &v 传递给 fmt.printf,这就是让它逃脱的原因。未来的编译器可能会更聪明:它可能知道 fmt.printf 不会保存 &v 的值,因此在 fmt.printf 返回后该变量就死了,并且没有真正逃逸;在这种情况下,它可能每次都会重复使用 &v。但是,一旦我们添加一些重要的内容,v 就会真正逃脱,并且编译器将不得不返回单独分配每一个。

关键问题是可观察性

除非您在 go 中获取变量的地址(例如整个数组或其任何元素),否则您可以观察到的关于该事物的唯一信息就是它的类型和值。一般来说,这意味着您无法判断编译器是否创建了某个变量的新副本,或者重用了旧副本。

如果将数组传递给函数,go 将按值传递整个数组。这意味着该函数无法更改数组中的原始值。我们可以通过编写一个实际上改变值的函数来了解如何观察到这一点:

package main

import (
    "fmt"
)

func observe(arr [3]int) {
    fmt.printf("at start: arr = %v\n", arr)
    for i, v := range arr {
        arr[i] = v * 2
    }
    fmt.printf("at end: arr = %v\n", arr)
}

func main() {
    a := [3]int{1, 2, 3}
    for i := 0; i < 3; i++ {
        observe(a)
    }
}

playground link)。

go 中的数组是按值传递的,因此这不会更改 main 中的数组 a,即使它确实更改了 observe 中的数组 arr

不过,我们通常希望更改数组并保留这些更改。为此,我们必须:

现在我们可以看到值发生变化,即使我们从不查看各个地址,这些变化也会在函数调用之间保留。语言说我们必须能够看到这些变化,所以我们可以;该语言规定,当我们按值传递数组本身时,我们一定不能看到变化,所以我们不能。由编译器来想出某种方法来实现这一点。无论是涉及复制原始数组还是其他一些神秘的魔法,都取决于编译器——尽管 go 试图成为一种简单的语言,其中“魔法”是显而易见的,并且明显的方法是根据情况进行复制或不复制.

这一切的要点

除了担心可观察到的影响(即,我们是否首先计算出正确的答案)之外,进行所有这些实验的目的是证明编译器可以 做任何它想做的事,只要它能产生正确的可观察到的效果。

您可以尝试让编译器变得更容易,例如,通过分配单个数组,通过地址(上例中的 &a)或切片(上例中的 a[:] )使用它,然后清除它1但这可能不会比直接写出你认为最清晰的内容更快,甚至可能。先把它写清楚,然后再计时。如果太慢,请尝试协助编译器,然后重新计时。您的协助可能会使事情变得更糟或没有效果:如果是这样,请不要打扰。如果它让事情变得更好,就保留它。

1知道您使用的编译器会进行转义分析,如果您想帮助它,您可以使用标志来运行它,让它告诉您哪些变量已转义。这些通常是优化的机会:如果您能找到一种方法来防止变量逃逸,编译器可以将其分配在堆栈上,而不是堆上,这可能会节省大量有用的时间。但是,如果您的时间一开始并没有真正花在分配器上,那么这实际上不会有任何帮助,因此分析通常是第一步

考虑到这一点变量 arr 已经实例化为 [3]int 类型,我记得一些覆盖其内容的选项:

arr = [3]int{}

arr = make([]int, 3)

两者都用 0 的值覆盖切片。

请记住,每次我们使用此语法 var := type{} 都会将给定 type 的新对象实例化为变量。因此,如果您获得相同的变量并再次实例化它,您将把它的内容覆盖为新的内容。

在 go 中,语言将切片视为类型对象,而不是像 intrunebyte 等原始类型。

今天关于《在 Go 中如何进行数组的复用和重置操作?》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!

版本声明
本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
解决win10中bitlocker选项不见的方法解决win10中bitlocker选项不见的方法
上一篇
解决win10中bitlocker选项不见的方法
在 Golang 中单线程机制的探究
下一篇
在 Golang 中单线程机制的探究
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    509次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    497次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • AI边界平台:智能对话、写作、画图,一站式解决方案
    边界AI平台
    探索AI边界平台,领先的智能AI对话、写作与画图生成工具。高效便捷,满足多样化需求。立即体验!
    213次使用
  • 讯飞AI大学堂免费AI认证证书:大模型工程师认证,提升您的职场竞争力
    免费AI认证证书
    科大讯飞AI大学堂推出免费大模型工程师认证,助力您掌握AI技能,提升职场竞争力。体系化学习,实战项目,权威认证,助您成为企业级大模型应用人才。
    238次使用
  • 茅茅虫AIGC检测:精准识别AI生成内容,保障学术诚信
    茅茅虫AIGC检测
    茅茅虫AIGC检测,湖南茅茅虫科技有限公司倾力打造,运用NLP技术精准识别AI生成文本,提供论文、专著等学术文本的AIGC检测服务。支持多种格式,生成可视化报告,保障您的学术诚信和内容质量。
    356次使用
  • 赛林匹克平台:科技赛事聚合,赋能AI、算力、量子计算创新
    赛林匹克平台(Challympics)
    探索赛林匹克平台Challympics,一个聚焦人工智能、算力算法、量子计算等前沿技术的赛事聚合平台。连接产学研用,助力科技创新与产业升级。
    440次使用
  • SEO  笔格AIPPT:AI智能PPT制作,免费生成,高效演示
    笔格AIPPT
    SEO 笔格AIPPT是135编辑器推出的AI智能PPT制作平台,依托DeepSeek大模型,实现智能大纲生成、一键PPT生成、AI文字优化、图像生成等功能。免费试用,提升PPT制作效率,适用于商务演示、教育培训等多种场景。
    377次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码