在 Go 中如何进行数组的复用和重置操作?
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)
}
}
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 中,语言将切片视为类型对象,而不是像 int 、 rune 、 byte 等原始类型。
今天关于《在 Go 中如何进行数组的复用和重置操作?》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!
解决win10中bitlocker选项不见的方法
- 上一篇
- 解决win10中bitlocker选项不见的方法
- 下一篇
- 在 Golang 中单线程机制的探究
-
- Golang · Go问答 | 1年前 |
- 在读取缓冲通道中的内容之前退出
- 139浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 戈兰岛的全球 GOPRIVATE 设置
- 204浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将结构作为参数传递给 xml-rpc
- 325浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何用golang获得小数点以下两位长度?
- 478浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何通过 client-go 和 golang 检索 Kubernetes 指标
- 486浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将多个“参数”映射到单个可变参数的习惯用法
- 439浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 将 HTTP 响应正文写入文件后出现 EOF 错误
- 357浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 结构中映射的匿名列表的“复合文字中缺少类型”
- 352浏览 收藏
-
- Golang · Go问答 | 1年前 |
- NATS Jetstream 的性能
- 101浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何将复杂的字符串输入转换为mapstring?
- 440浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 相当于GoLang中Java将Object作为方法参数传递
- 212浏览 收藏
-
- Golang · Go问答 | 1年前 |
- 如何确保所有 goroutine 在没有 time.Sleep 的情况下终止?
- 143浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ChatExcel酷表
- ChatExcel酷表是由北京大学团队打造的Excel聊天机器人,用自然语言操控表格,简化数据处理,告别繁琐操作,提升工作效率!适用于学生、上班族及政府人员。
- 3204次使用
-
- Any绘本
- 探索Any绘本(anypicturebook.com/zh),一款开源免费的AI绘本创作工具,基于Google Gemini与Flux AI模型,让您轻松创作个性化绘本。适用于家庭、教育、创作等多种场景,零门槛,高自由度,技术透明,本地可控。
- 3417次使用
-
- 可赞AI
- 可赞AI,AI驱动的办公可视化智能工具,助您轻松实现文本与可视化元素高效转化。无论是智能文档生成、多格式文本解析,还是一键生成专业图表、脑图、知识卡片,可赞AI都能让信息处理更清晰高效。覆盖数据汇报、会议纪要、内容营销等全场景,大幅提升办公效率,降低专业门槛,是您提升工作效率的得力助手。
- 3446次使用
-
- 星月写作
- 星月写作是国内首款聚焦中文网络小说创作的AI辅助工具,解决网文作者从构思到变现的全流程痛点。AI扫榜、专属模板、全链路适配,助力新人快速上手,资深作者效率倍增。
- 4555次使用
-
- MagicLight
- MagicLight.ai是全球首款叙事驱动型AI动画视频创作平台,专注于解决从故事想法到完整动画的全流程痛点。它通过自研AI模型,保障角色、风格、场景高度一致性,让零动画经验者也能高效产出专业级叙事内容。广泛适用于独立创作者、动画工作室、教育机构及企业营销,助您轻松实现创意落地与商业化。
- 3824次使用
-
- GoLand调式动态执行代码
- 2023-01-13 502浏览
-
- 用Nginx反向代理部署go写的网站。
- 2023-01-17 502浏览
-
- Golang取得代码运行时间的问题
- 2023-02-24 501浏览
-
- 请问 go 代码如何实现在代码改动后不需要Ctrl+c,然后重新 go run *.go 文件?
- 2023-01-08 501浏览
-
- 如何从同一个 io.Reader 读取多次
- 2023-04-11 501浏览

