27

Golang参数传递问题

 4 years ago
source link: https://studygolang.com/articles/28076
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

首先说结论:在Go语言里,所有的参数传递都是值传递(传值),都是一个副本,一个拷贝,因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据。

非引用类型(值类型):int,float,bool,string,以及数组和struct

特点:变量直接存储值,内存通常在栈中分配,栈在函数调用完会被释放

引用类型:指针,slice,map,chan,接口,函数等

特点:变量存储的是一个地址,这个地址存储最终的值。内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,通过GC回收

array

(1)使用值传递在函数间传递大数组

//声明一个需要8 MB的数组
var array [1e6]int
// 将数组传递给函数 foo 
foo(array)
// 函数 foo 接受一个 100 万个整型值的数组 ,实质是创建一个原数组的拷贝
func foo(array [1e6]int) {
     ... 
}

(2)使用指针在函数间传递大数组

//分配一个需要8 MB的数组
var array [1e6]int
// 将数组的地址传递给函数 foo 
foo(&array)
// 函数 foo 接受一个指向 100 万个整型值的数组的指针 ,实质是创建一个指向原数组的指针
func foo(array *[1e6]int) {
       ...
 }

slice

在函数间传递切片 ,也是以值的方式传递,传递的是slice的一个拷贝,但由于slice这个结构体自身尺寸较小,在 64 位架构的机器上,一个切片需要 24 字节的内存:指针字段需要 8 字节,长度和容量字段分别需要 8 字节,因此在函数间复制和传递切片成本很低。我们可以通过切片内部指向底层数据的指针来修改原数据。

// 分配包含 100 万个整型值的切片
slice := make([]int, 1e6)
// 将 slice 传递到函数 foo slice = foo(slice)
// 函数 foo 接收一个整型切片,并返回这个切片 
func foo(slice []int) []int {
      ...
     return slice
}
Rbmie2F.png!web

函数调用之后两个切片指向同一个底层数组

map

在函数间传递映射时也是值传递,只不过我们在创建map时,实际上是返回了一个指针类型:

// makemap implements a Go map creation make(map[k]v, hint)
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h != nil, the map can be created directly in h.
// If bucket != nil, bucket can be used as the first bucket.
func makemap(t *maptype, hint int64, h *hmap, bucket unsafe.Pointer) *hma{
//省略无关代码
}

在map作为参数传递时,使用了这个指针的拷贝进行传递,属于值传递,因此我们可以通过map来修改原有数据,本质上是通过指针来操作。

package main

import "fmt"

func main() {
    persons := make(map[string]int)
    persons["张三"] = 19

    mp := &persons

    fmt.Printf("原始map的内存地址是:%p\n", mp)
    modify(persons)
    fmt.Println("map值被修改了,新值为:", persons)
}

func modify(p map[string]int) {
    fmt.Printf("函数里接收到map的内存地址是:%p\n", &p)
    p["张三"] = 20
}
//输出:
//原始map的内存地址是:0xc00007c010
//函数里接收到map的内存地址是:0xc000084008
//map值被修改了,新值为: map[张三:20]

结构体

结构体本身属于值类型,在函数间作为参数传递时为该结构体的一个拷贝,属于值传递,因此也无法修改原数据,同样的,我们可以通过指针来实现修改原数据的目的

(1)使用值传递在函数间传递结构体

package main

import "fmt"

func main() {
    p:=Person{"张三"}
    fmt.Printf("原始Person的内存地址是:%p\n",&p)
    modify(p)
    fmt.Println(p)
}

type Person struct {
    Name string
}

func modify(p Person) {
    fmt.Printf("函数里接收到Person的内存地址是:%p\n",&p)
    p.Name = "李四"
}
//输出
//原始Person的内存地址是:0xc00008e040
//函数里接收到Person的内存地址是:0xc00008e050
//{张三}

(2)使用指针在函数间传递结构体

package main

import "fmt"

func main() {
    p := Person{"张三"}
    modify(&p)
    fmt.Println(p)
}

type Person struct {
    Name string
}

func modify(p *Person) {
    p.Name = "李四"
}
//输出;{李四}

channel

channel本质上和map一样,可以通过源码看到:

func makechan(t *chantype, size int64) *hchan {
    //省略无关代码
}

欢迎关注我们的微信公众号,每天学习Go知识

FveQFjN.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK