前言
哈喽,大家好,我是asong。今天女朋友问我,小松子,你知道Go语言参数传递是传值还是传引用吗?哎呀哈,我竟然被瞧不起了,我立马一顿操作,给他讲的明明白白的,小丫头片子,还是太嫩,大家且听我细细道来~~~。
实参与形参数
go
func printNumber(args ...int)
args
形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
举例如下:
func main() {
var args int64= 1
printNumber(args) // args就是实际参数
}
func printNumber(args ...int64) { //这里定义的args就是形式参数
for _,arg := range args{
fmt.Println(arg)
}
}
什么是值传递
int
什么是引用传递
C++
golang是值传递
我们先写一个简单的例子验证一下:
func main() {
var args int64= 1
modifiedNumber(args) // args就是实际参数
fmt.Printf("实际参数的地址 %p\n", &args)
fmt.Printf("改动后的值是 %d\n",args)
}
func modifiedNumber(args int64) { //这里定义的args就是形式参数
fmt.Printf("形参地址 %p \n",&args)
args = 10
}
运行结果:
形参地址 0xc0000b4010
实际参数的地址 0xc0000b4008
改动后的值是 1
gogo
func main() {
var args int64= 1
addr := &args
fmt.Printf("原始指针的内存地址是 %p\n", addr)
fmt.Printf("指针变量addr存放的地址 %p\n", &addr)
modifiedNumber(addr) // args就是实际参数
fmt.Printf("改动后的值是 %d\n",args)
}
func modifiedNumber(addr *int64) { //这里定义的args就是形式参数
fmt.Printf("形参地址 %p \n",&addr)
*addr = 10
}
运行结果:
原始指针的内存地址是 0xc0000b4008
指针变量addr存放的地址 0xc0000ae018
形参地址 0xc0000ae028
改动后的值是 10
所以通过输出我们可以看到,这是一个指针的拷贝,因为存放这两个指针的内存地址是不同的,虽然指针的值相同,但是是两个不同的指针。
args10xc0000b4008argsargsaddraddr0xc0000ae018addrmodifiedNumberaddr0xc0000ae0280xc0000ae0180xc0000ae0280xc0000b40080xc0000b4008argsargs
gomodifieNumberchanmapslice
slice
先看一段代码:
func main() {
var args = []int64{1,2,3}
fmt.Printf("切片args的地址: %p\n",args)
modifiedNumber(args)
fmt.Println(args)
}
func modifiedNumber(args []int64) {
fmt.Printf("形参切片的地址 %p \n",args)
args[0] = 10
}
运行结果:
切片args的地址: 0xc0000b8000
形参切片的地址 0xc0000b8000
[10 2 3]
&slice
func main() {
var args = []int64{1,2,3}
fmt.Printf("切片args的地址: %p \n",args)
fmt.Printf("切片args第一个元素的地址: %p \n",&args[0])
fmt.Printf("直接对切片args取地址%v \n",&args)
modifiedNumber(args)
fmt.Println(args)
}
func modifiedNumber(args []int64) {
fmt.Printf("形参切片的地址 %p \n",args)
fmt.Printf("形参切片args第一个元素的地址: %p \n",&args[0])
fmt.Printf("直接对形参切片args取地址%v \n",&args)
args[0] = 10
}
运行结果:
切片args的地址: 0xc000016140
切片args第一个元素的地址: 0xc000016140
直接对切片args取地址&[1 2 3]
形参切片的地址 0xc000016140
形参切片args第一个元素的地址: 0xc000016140
直接对形参切片args取地址&[1 2 3]
[10 2 3]
fmt.Printf
fmt包,print.go中的printValue这个方法,截取重点部分,因为`slice`也是引用类型,所以会进入这个`case`:
case reflect.Ptr:
// pointer to array or slice or struct? ok at top level
// but not embedded (avoid loops)
if depth == 0 && f.Pointer() != 0 {
switch a := f.Elem(); a.Kind() {
case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map:
p.buf.writeByte('&')
p.printValue(a, verb, depth+1)
return
}
}
fallthrough
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
p.fmtPointer(f, verb)
p.buf.writeByte('&')&&slicep.printValue(a, verb, depth+1)[]printValue
case reflect.Array, reflect.Slice:
//省略部分代码
} else {
p.buf.writeByte('[')
for i := 0; i < f.Len(); i++ {
if i > 0 {
p.buf.writeByte(' ')
}
p.printValue(f.Index(i), verb, depth+1)
}
p.buf.writeByte(']')
}
fmt.Printf("直接对切片args取地址%v \n",&args)直接对切片args取地址&[1 2 3]fallthroughfmt.Poniter
func (p *pp) fmtPointer(value reflect.Value, verb rune) {
var u uintptr
switch value.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
u = value.Pointer()
default:
p.badVerb(verb)
return
}
...... 省略部分代码
// If v's Kind is Slice, the returned pointer is to the first
// element of the slice. If the slice is nil the returned value
// is 0. If the slice is empty but non-nil the return value is non-zero.
func (v Value) Pointer() uintptr {
// TODO: deprecate
k := v.kind()
switch k {
case Chan, Map, Ptr, UnsafePointer:
return uintptr(v.pointer())
case Func:
if v.flag&flagMethod != 0 {
....... 省略部分代码
sliceslicefmt.Printf("切片args的地址: %p \n",args)fmt.Printf("形参切片的地址 %p \n",args)argsslicesliceslice
slice
//runtime/slice.go
type slice struct {
array unsafe.Pointer
len int
cap int
}
sliceslicefmt.Printfsliceslice
sliceslicefmt.printf
map也是值传递吗?
mapslicemap
func main() {
persons:=make(map[string]int)
persons["asong"]=8
addr:=&persons
fmt.Printf("原始map的内存地址是:%p\n",addr)
modifiedAge(persons)
fmt.Println("map值被修改了,新值为:",persons)
}
func modifiedAge(person map[string]int) {
fmt.Printf("函数里接收到map的内存地址是:%p\n",&person)
person["asong"]=9
}
看一眼运行结果:
原始map的内存地址是:0xc00000e028
函数里接收到map的内存地址是:0xc00000e038
map值被修改了,新值为: map[asong:9]
map
为了解决我们的疑惑,我们从源码入手,看一看什么原理:
//src/runtime/map.go
// makemap implements Go map creation for 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 h.buckets != nil, bucket pointed to can be used as the first bucket.
func makemap(t *maptype, hint int, h *hmap) *hmap {
mem, overflow := math.MulUintptr(uintptr(hint), t.bucket.size)
if overflow || mem > maxAlloc {
hint = 0
}
// initialize Hmap
if h == nil {
h = new(hmap)
}
h.hash0 = fastrand()
makehmap*hmapfunc modifiedAge(person map[string]int)func modifiedAge(person *hmap)makemap
chan是值传递吗?
老样子,先看一个例子:
func main() {
p:=make(chan bool)
fmt.Printf("原始chan的内存地址是:%p\n",&p)
go func(p chan bool){
fmt.Printf("函数里接收到chan的内存地址是:%p\n",&p)
//模拟耗时
time.Sleep(2*time.Second)
p<-true
}(p)
select {
case l := <- p:
fmt.Println(l)
}
}
再看一看运行结果:
原始chan的内存地址是:0xc00000e028
函数里接收到chan的内存地址是:0xc00000e038
true
mapchan
// src/runtime/chan.go
func makechan(t *chantype, size int) *hchan {
elem := t.elem
// compiler checks this but be safe.
if elem.size >= 1<<16 {
throw("makechan: invalid channel element type")
}
if hchanSize%maxAlign != 0 || elem.align > maxAlign {
throw("makechan: bad alignment")
}
mem, overflow := math.MulUintptr(elem.size, uintptr(size))
if overflow || mem > maxAlloc-hchanSize || size < 0 {
panic(plainError("makechan: size out of range"))
}
makehchan*hchanmapfun (p chan bool)fun (p *hchan)
gostructstruct
struct
struct
func main() {
per := Person{
Name: "asong",
Age: int64(8),
}
fmt.Printf("原始struct地址是:%p\n",&per)
modifiedAge(per)
fmt.Println(per)
}
func modifiedAge(per Person) {
fmt.Printf("函数里接收到struct的内存地址是:%p\n",&per)
per.Age = 10
}
PersonAge108
前文总结
兄弟们实锤了奥,go就是值传递,可以确认的是Go语言中所有的传参都是值传递(传值),都是一个副本,一个拷贝。因为拷贝的内容有时候是非引用类型(int、string、struct等这些),这样就在函数中就无法修改原内容数据;有的是引用类型(指针、map、slice、chan等这些),这样就可以修改原内容数据。
是否可以修改原内容数据,和传值、传引用没有必然的关系。在C++中,传引用肯定是可以修改原内容数据的,在Go语言里,虽然只有传值,但是我们也可以修改原内容数据,因为参数是引用类型。
有的小伙伴会在这里还是懵逼,因为你把引用类型和传引用当成一个概念了,这是两个概念,切记!!!
出个题考验你们一下
欢迎在评论区留下你的答案~~~
既然你们都知道了golang只有值传递,那么这段代码来帮我分析一下吧,这里的值能修改成功,为什么使用append不会发生扩容?
func main() {
array := []int{7,8,9}
fmt.Printf("main ap brfore: len: %d cap:%d data:%+v\n", len(array), cap(array), array)
ap(array)
fmt.Printf("main ap after: len: %d cap:%d data:%+v\n", len(array), cap(array), array)
}
func ap(array []int) {
fmt.Printf("ap brfore: len: %d cap:%d data:%+v\n", len(array), cap(array), array)
array[0] = 1
array = append(array, 10)
fmt.Printf("ap after: len: %d cap:%d data:%+v\n", len(array), cap(array), array)
}
后记
好啦,这一篇文章到这就结束了,我们下期见~~。希望对你们有用,又不对的地方欢迎指出,可添加我的golang交流群,我们一起学习交流。
结尾给大家发一个小福利吧,最近我在看[微服务架构设计模式]这一本书,讲的很好,自己也收集了一本PDF,有需要的小伙可以到自行下载。获取方式:关注公众号:[Golang梦工厂],后台回复:[微服务],即可获取。
我翻译了一份GIN中文文档,会定期进行维护,有需要的小伙伴后台回复[gin]即可下载。
翻译了一份Machinery中文文档,会定期进行维护,有需要的小伙伴们后台回复[machinery]即可获取。
golangvx
推荐往期文章: