Golang-值传递

866 阅读3分钟

值传递

Golang中参数的传递全部为值传递,即传递的参数变量的一个副本,一副拷贝。

比如我们传递一个int类型的参数,传递的其实是这个参数的一个副本;传递一个指针类型的参数,其实传递的是这个该指针的一份拷贝,而不是这个指针指向的值。

示例

package main
import (
	"fmt"
)
func main() {
	i:=10
	ip:=&i
	fmt.Printf("原始指针的内存地址是:%p\n",&ip)
	modify(ip)
	fmt.Println("int值被修改了,新值为:",i)
}
func modify(ip *int){
	 fmt.Printf("函数里接收到的指针的内存地址是:%p\n",&ip)
 	*ip=1
 }

输出:

原始指针的内存地址是:0xc0000a0018
函数里接收到的指针的内存地址是:0xc0000a0028
int值被修改了,新值为: 1

引用类型

Golang的引用类型包括Slice、Map和Channel。除了指针类型以外,当参数为Slice、Map、Channel时,函数内的修改仍然会影响函数外的值。

示例

package main
import (
	"fmt"
)
func main() {
	s := []int{1,2,3}
	modify(s)
	fmt.Println(s)
}

 func modify(s []int){
         s[0] = 0
 }

输出:

[0 2 3]

Program exited.

原理

Slice

在golang中,Slice 的数据结构定义如下:

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

传递slice的时候,这个结构体是值传递的,传递完成后,内存中有两个slice结构体,它们引用同一块slice数组

示例

package main

import (
	"fmt"
	"reflect"
	"unsafe"
)

func main() {
 s := []string{"a", "b"}

 fmt.Printf("%p\n", (*reflect.SliceHeader)(unsafe.Pointer(&s)))
 sliceData(s)
}
func sliceData(s []string) {
    fmt.Printf("%p\n", (*reflect.SliceHeader)(unsafe.Pointer(&s)))
}

输出:

0xc00000c030
0xc00000c048

SliceHeader

SliceHeader是Slice运行时的具体表现,Slice在运行中可以通过unsafe.Pointer进行强制类型转换,转换为reflect.SliceHeader,SliceHeader的结构定义如下:

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

Map

对于map,make函数返回的是一个hmap类型的指针*hmap。因此在传递时,传递的是这个指针的复制。

// 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) *hmap {
    //省略无关代码
}

Chan

与map同理。

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

使用

扩容

如果因为容量大小触发了Slice的扩容,函数内外的引用就不一样了。

package main
import (
	"fmt"
)
func main() {
	s := []int{1,2,3}
	modify(s)
	fmt.Println(s)
}
func modify(s []int){
	s = append(s, 4)
	s[0] = 0
	fmt.Println(s)
 }

输出:

[0 2 3 4]
[1 2 3]

如何在扩容情况下修改原切片

除了返回新切片外,还可以传递切片的指针

func myAppend(list *[]string, value string) {
    *list = append(*list, value)
}

利用slice传递数组

数组作为参数的时候,会将其复制一份,如果它非常大,「会造成大量的内存浪费」,利用[:]将数组作为切片传递。

package main
import (
	"fmt"
)
func main() {
	a := [3]int{1,2,3}
	print(a[:])
}
func print(a []int){
         fmt.Println(a)
 }

www.flysnow.org/2018/02/24/…

zhuanlan.zhihu.com/p/401758344