Golang中数组是值传递还是引用传递?
Golang中数组是值传递还是引用传递? 我有以下代码:
package main
import "fmt"
func main() {
arr := []string{"hi", "bye"}
fmt.Printf("arr: %v\n", arr)
check(arr)
fmt.Printf("arr: %v\n", arr)
}
func check(c []string) {
for index, _ := range c {
c[index] = "ok"
}
}
它给出了以下输出:
arr: [hi bye]
arr: [ok ok]
这意味着数组是作为引用传递的,而不是作为值传递的,我的理解正确吗?是否有关于此的官方文档?
在 Go 中,除了数组之外,还有其他东西是作为引用传递的吗?
更多关于Golang中数组是值传递还是引用传递?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
你好。 在这两段代码中,传递给函数的原始切片的值都被改变了!
更多关于Golang中数组是值传递还是引用传递?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
Go语言默认是值传递。 不存在像C++那样的引用传递。
切片在传递时,会传递指向底层数组的指针。
func main() {
fmt.Println("hello world")
}
切片是按值传递的。当你在 check 函数中追加新值时,它不会反映在 main 函数中。
请查看这里:https://play.golang.org/p/cTB7C6ws7Uo
当你将切片的地址传递给 check 函数时,它会在 main 函数中反映出来。
请查看这里:https://play.golang.org/p/x50H9C7Rw2X
您定义的 arr 不是一个数组,而是一个切片。数组看起来应该是这样的:
arr := [2]string{"hi", "bye"}
这意味着,一旦你实例化了数组 arr,你就不能改变它的大小。你可以改变数组元素的值,但不能增加或减少数组中的元素数量。切片则允许你增加和减少元素数量、复制切片等。切片是对底层数组的一个引用。在 Go 语言中,数组不常用,但切片很常用。
显式声明的函数参数总是按值传递。数组、切片、结构体等都是按值传递的。实现“按引用传递”的方式是传递一个指针(指针本身仍然是按值传递的)。然后你可以对该指针进行解引用。
切片虽然也是按值传递,但它内部包装了一个指向底层数组的指针。因此,即使函数是按值传递切片,由于可以通过该指针修改切片,其行为看起来就像是“按引用传递”一样。
https://play.golang.org/p/BYvLdnGWxup
感谢各位的回复,在你们的支持和基于此进行的额外搜索后,我找到了答案:
切片在传递时,会传递指向底层数组的指针。切片值仅包含一个指向实际存储元素的数组的指针。切片值不包含其元素(这与数组不同)。切片值是一个头部,描述了后备数组的一个连续部分。当你将切片传递给函数时,会复制这个头部(包括指针),该指针将指向同一个后备数组。修改切片的元素意味着修改后备数组的元素,因此所有共享同一后备数组的切片都会“观察到”这个变化。 切片是一个指向底层数组的小型结构体。这个小型结构体会被复制,但它仍然指向同一个底层数组。包含切片元素的内存块是通过“引用”传递的。而保存容量、元素数量和指向元素的指针的切片信息三元组则是通过值传递的。
处理传递给函数的切片的最佳方式(如果函数内会操作切片的元素,而我们不希望这种操作反映到元素内存块上)是使用 copy(s, *c) 来复制它们,如下所示:
package main
import "fmt"
type Team []Person
type Person struct {
Name string
Age int
}
func main() {
team := Team{
Person{"Hasan", 34}, Person{"Karam", 32},
}
fmt.Printf("original before clonning: %v\n", team)
team_cloned := team.Clone()
fmt.Printf("original after clonning: %v\n", team)
fmt.Printf("clones slice: %v\n", team_cloned)
}
func (c *Team) Clone() Team {
var s = make(Team, len(*c))
copy(s, *c)
for index, _ := range s {
s[index].Name = "change name"
}
return s
}
但要小心,如果这个切片包含一个子切片,则需要进一步复制,因为我们仍然会让子切片的元素指向同一个内存块元素,示例如下:
type Inventories []Inventory
type Inventory struct { //instead of: map[string]map[string]Pairs
Warehouse string
Item string
Batches Lots
}
type Lots []Lot
type Lot struct {
Date time.Time
Key string
Value float64
}
func main() {
ins := Inventory{
Warehouse: "DMM",
Item: "Gloves",
Batches: Lots{
Lot{mustTime(time.Parse(custom, "1/7/2020")), "Jan", 50},
Lot{mustTime(time.Parse(custom, "2/1/2020")), "Feb", 70},
},
}
inv2 := CloneFrom(c Inventories)
}
func (i *Inventories) CloneFrom(c Inventories) {
inv := new(Inventories)
for _, v := range c {
batches := Lots{}
for _, b := range v.Batches {
batches = append(batches, Lot{
Date: b.Date,
Key: b.Key,
Value: b.Value,
})
}
*inv = append(*inv, Inventory{
Warehouse: v.Warehouse,
Item: v.Item,
Batches: batches,
})
}
(*i).ReplaceBy(inv)
}
func (i *Inventories) ReplaceBy(x *Inventories) {
*i = *x
}
在Go语言中,你提到的[]string实际上是一个切片(slice),而不是数组(array)。这是一个重要的区别。
切片是引用类型,它包含一个指向底层数组的指针、长度和容量。当切片作为参数传递时,虽然切片描述符本身是按值传递的,但由于它包含指向底层数组的指针,所以对切片元素的修改会影响原始数据。
数组是值类型,当数组作为参数传递时,会创建整个数组的副本。让我们通过示例来对比:
package main
import "fmt"
func main() {
// 示例1:切片(你的代码)
slice := []string{"hi", "bye"}
fmt.Printf("原始切片: %v\n", slice)
modifySlice(slice)
fmt.Printf("修改后切片: %v\n", slice)
// 示例2:数组
array := [2]string{"hi", "bye"}
fmt.Printf("\n原始数组: %v\n", array)
modifyArray(array)
fmt.Printf("修改后数组: %v\n", array)
// 示例3:数组指针
array2 := [2]string{"hi", "bye"}
fmt.Printf("\n原始数组2: %v\n", array2)
modifyArrayByPointer(&array2)
fmt.Printf("修改后数组2: %v\n", array2)
}
func modifySlice(s []string) {
for i := range s {
s[i] = "ok"
}
}
func modifyArray(a [2]string) {
for i := range a {
a[i] = "ok"
}
}
func modifyArrayByPointer(a *[2]string) {
for i := range a {
a[i] = "ok"
}
}
输出:
原始切片: [hi bye]
修改后切片: [ok ok]
原始数组: [hi bye]
修改后数组: [hi bye]
原始数组2: [hi bye]
修改后数组2: [ok ok]
官方文档参考:
- Go语言规范中关于切片类型的描述
- Go语言规范中关于数组类型的描述
- Go博客文章Go Slices: usage and internals
Go语言中的引用类型:
- 切片(slice) - 包含指向底层数组的指针
- 映射(map) - 引用类型,传递的是描述符的副本
- 通道(channel) - 引用类型
- 函数(function) - 可以作为值传递
- 接口(interface) - 包含类型信息和值指针
值类型:
- 数组(array) - 完整复制
- 结构体(struct) - 默认值传递,除非使用指针
- 基本类型 (int, float, bool, string等) - 值传递
package main
import "fmt"
func main() {
// 映射示例
m := map[string]int{"a": 1, "b": 2}
fmt.Printf("原始map: %v\n", m)
modifyMap(m)
fmt.Printf("修改后map: %v\n", m)
// 通道示例
ch := make(chan int, 2)
ch <- 1
ch <- 2
modifyChannel(ch)
fmt.Printf("通道内容: %d, %d\n", <-ch, <-ch)
}
func modifyMap(m map[string]int) {
m["a"] = 100
m["c"] = 3
}
func modifyChannel(ch chan int) {
// 通道是引用类型,传递的是通道的引用
}
关键点:在Go中,所有参数传递都是按值传递,但对于切片、映射、通道这些引用类型,传递的是它们描述符(包含指针)的副本,因此可以修改底层数据。

