Go
stringslicemap
GoGoGo
1. 函数传参为数组
package main

import "fmt"

func main() {
	srcArray := [3]string{"a", "b", "c"}
	fmt.Printf("srcArray address is %p\n", &srcArray) //	srcArray address is 0xc00005a150
	modify(srcArray)
	fmt.Printf("srcArray is %v\n", srcArray) //	srcArray is [a b c]

}

func modify(modifyArr [3]string) [3]string {
	fmt.Printf("modifyArr address is %p\n", &modifyArr) //	modifyArr address is 0xc00005a180
	modifyArr[1] = "x"
	fmt.Printf("modifyArr is %v\n", modifyArr) //	modifyArr is [a x c]
	return modifyArr
}
0xc00005a1500xc00005a180

所有传给函数的参数值都会被复制,函数在其内部使用的并不是参数值的原值,而是它的副本。由于数组是值类型,所以每一次复制都会拷贝它,以及它的所有元素值

2. 函数传参为切片
package main

import "fmt"

func main() {
	srcSlice := []string{"a", "b", "c"}
	fmt.Printf("srcSlice address is %p\n", srcSlice) //	srcSlice address is 0xc00005a150
	modify(srcSlice)
	fmt.Printf("srcSlice is %v\n", srcSlice) // modifySlice is [a x c]

}

func modify(modifySlice []string) []string {
	fmt.Printf("modifySlice address is %p\n", modifySlice) // modifySlice address is 0xc00005a150
	modifySlice[1] = "x"
	fmt.Printf("modifySlice is %v\n", modifySlice) // srcSlice is [a x c]
	return modifySlice
}
0xc00005a150
srcSlice&&

对于引用类型,比如:切片、字典、通道,像上面那样复制它们的值,只会拷贝它们本身而已,并不会拷贝它们引用的底层数据。也就是说,这时只是浅表复制,而不是深层复制。以切片值为例,如此复制的时候,只是拷贝了它指向底层数组中某一个元素的指针,以及它的长度值和容量值,而它的底层数组并不会被拷贝。

3. 函数传参为字典map
Gomapmakeruntime.makemap
makemapmakemapGo
makemap*hmapmap*hmap
src/runtime/map.go
// makemap implements Go map creation for make(map[k]v, hint).
func makemap(t *maptype, hint int, h *hmap) *hmap{
  //省略无关代码
}
map
package main

import "fmt"

func main() {
	srcMap := map[string]int{"a": 1, "b": 2, "c": 3}
	fmt.Printf("srcMap address is %p\n", srcMap) //	srcMap address is 0xc00005a150
	modify(srcMap)
	fmt.Printf("srcMap is %#v\n", srcMap) // srcMap is map[string]int{"a":1, "b":2, "c":100}

}

func modify(modifyMap map[string]int) map[string]int {
	fmt.Printf("modifyMap address is %p\n", modifyMap) // modifyMap address is 0xc00005a150
	modifyMap["c"] = 100
	fmt.Printf("modifyMap is %#v\n", modifyMap) // modifyMap is map[string]int{"a":1, "b":2, "c":100}
	return modifyMap
}
srcMapmodifyMap&&
map
4. 函数传参为 channel
channel
chan*hchanmap
func makechan(t *chantype, size int64) *hchan {
    //省略无关代码
}
GomapchanmapchanGoslice
5. 函数传参为
package main

import "fmt"

type Student struct {
	name string
	age  int
}

func main() {
	s := Student{name: "wohu", age: 20}
	fmt.Printf("s address is %p\n", &s) // s address is 0xc00000c060
	modify(s)
	fmt.Printf("s is %v\n", s) // s is {wohu 20}

}

func modify(stu Student) Student {
	fmt.Printf("stu address is %p\n", &stu) // stu address is 0xc00000c080
	stu.age = 30
	fmt.Printf("stu is %v\n", stu) // stu is {wohu 30}
	return stu
}
modifystumainstumodifystumain
Go
modifymodifystuGostupnameage
struct

指针类型的变量保存的值就是数据对应的内存地址,所以在函数参数传递是传值的原则下,拷贝的值也是内存地址。现在对以上示例稍做修改,修改后的代码如下:

package main

import "fmt"

type Student struct {
	name string
	age  int
}

func main() {
	s := Student{name: "wohu", age: 20}
	fmt.Printf("s address is %p\n", &s) // s address is 0xc00000c060
	modify(&s)
	fmt.Printf("s is %v\n", s) // s is {wohu 30}
}

func modify(stu *Student) *Student {
	fmt.Printf("stu address is %p\n", stu) // stu address is 0xc00000c060
	stu.age = 30
	fmt.Printf("stu is %v\n", *stu) // stu is &{wohu 30}
	return stu
}

所以指针类型的参数是永远可以修改原数据的,因为在参数传递时,传递的是内存地址。

注意:值传递的是指针,也是内存地址。通过内存地址可以找到原数据的那块内存,所以修改它也就等于修改了原数据。

stustudentGostudent&stu*student
Gomapchan
6. 其它示例

直接上代码


package main

import "fmt"

func main() {
	// 示例1。
	array1 := [3]string{"a", "b", "c"}
	fmt.Printf("The array: %v\n", array1)
	array2 := modifyArray(array1)
	fmt.Printf("The modified array: %v\n", array2)
	fmt.Printf("The original array: %v\n", array1)
	fmt.Println()

	// 示例2。
	slice1 := []string{"x", "y", "z"}
	fmt.Printf("The slice: %v\n", slice1)
	slice2 := modifySlice(slice1)
	fmt.Printf("The modified slice: %v\n", slice2)
	fmt.Printf("The original slice: %v\n", slice1)
	fmt.Println()

	// 示例3。
	complexArray1 := [3][]string{
		[]string{"d", "e", "f"},
		[]string{"g", "h", "i"},
		[]string{"j", "k", "l"},
	}
	fmt.Printf("The complex array: %v\n", complexArray1)
	complexArray2 := modifyComplexArray(complexArray1)
	fmt.Printf("The modified complex array: %v\n", complexArray2)
	fmt.Printf("The original complex array: %v\n", complexArray1)
}

// 示例1。
func modifyArray(a [3]string) [3]string {
	a[1] = "x"
	return a
}

// 示例2。
func modifySlice(a []string) []string {
	a[1] = "i"
	return a
}

// 示例3。
func modifyComplexArray(a [3][]string) [3][]string {
	a[1][1] = "s"
	a[2] = []string{"o", "p", "q"}
	return a
}
  • 如果是进行一层修改,即数组的某个完整元素进行修改(指针变化),那么原有数组不变;
  • 如果是进行二层修改,即数组中某个元素切片内的某个元素再进行修改(指针未改变),那么原有数据也会跟着改变,传参可以理解是浅copy,参数本身的指针是不同,但是元素指针相同,对元素指针所指向目的的操作会影响传参过程中的原始数据;
7. 函数传参为地址

当变量被当做参数传入调用函数时,是值传递,也称变量的一个拷贝传递。如果传递过来的值是指针,就相当于把变量的地址作为参数传递到函数内,那么在函数内对这个指针所指向的内容进行修改,将会改变这个变量的值。如下边示例代码:

package main
import (
    "fmt"
)
func demo(str *string) {
    *str = "world"
}
func main() {
    var str = "hello"
    demo(&str)
    fmt.Println("str value is:", str)
}

输出结果:

str value is: world
str str
package main
import (
    "fmt"
)
var world = "hello wolrd"
func demo(str *string) {
    str = &world
    fmt.Println("str in demo func is:", *str)
}
func main() {
    var str = "hello"
    demo(&str)
    fmt.Println("str in main func is:", str)
}

输出结果:

str in demo func is: hello wolrd
str in main func is: hello
strdemoworldstr

所以,在函数调用中,变量被拷贝了一份传入函数,函数调用结束后,拷贝的值被丢弃。

strdemodemostr
8. 综合示例
package main

import "fmt"

// 用于测试值传递效果的结构体,结构体是拥有多个字段的复杂结构。
type Data struct {
	complax  []int      // complax 为整型切片类型,切片是一种动态类型,内部以指针存在。
	instance InnerData  // instance 成员以 InnerData 类型作为 Data 的成员 。
	ptr      *InnerData // 将 ptr 声明为 InnerData 的指针类型
}

// 代表各种结构体字段
type InnerData struct {
	a int
}

// 值传递测试函数,该函数的参数和返回值都是 Data 类型。
// 在调用中, Data 的内存会被复制后传入函数,当函数返回时,又会将返回值复制一次,
// 赋给函数返回值的接收变量。
func passByValue(inFunc Data) Data {
	// 输出参数的成员情况
	fmt.Printf("inFunc value: %+v\n", inFunc)
	// 打印inFunc的指针
	fmt.Printf("inFunc ptr: %p\n", &inFunc)
	// 将传入的变量作为返回值返回,返回的过程将发生值复制。
	return inFunc
}

func main() {
	// 准备传入函数的结构
	in := Data{
		complax: []int{1, 2, 3},
		instance: InnerData{
			5,
		},
		ptr: &InnerData{1},
	}
	// 输入结构的成员情况
	fmt.Printf("in value: %+v\n", in)
	// 输入结构的指针地址
	fmt.Printf("in ptr: %p\n", &in)
	// 传入结构体,返回同类型的结构体
	out := passByValue(in)
	// 输出结构的成员情况
	fmt.Printf("out value: %+v\n", out)
	// 输出结构的指针地址
	fmt.Printf("out ptr: %p\n", &out)
}

输出结果:

in value: {complax:[1 2 3] instance:{a:5} ptr:0xc0000180e8}
in ptr: 0xc000078150
inFunc value: {complax:[1 2 3] instance:{a:5} ptr:0xc0000180e8}
inFunc ptr: 0xc0000781e0
out value: {complax:[1 2 3] instance:{a:5} ptr:0xc0000180e8}
out ptr: 0xc0000781b0

从运行结果中发现:

DataDataDataDataDataptr

参考:
https://juejin.cn/post/6844903618890432520
https://www.zhihu.com/question/312356800/answer/739572672
https://segmentfault.com/q/1010000019965306/a-1020000019996800
https://www.flysnow.org/2018/02/24/golang-function-parameters-passed-by-value.html

https://blog.csdn.net/wohu1104/article/details/109661126