Golang参数传递


函数参数 有值传递 和 引用传递 二种方式
Go 内建数据类型 map channel和slice 三种

string类型传参

demo1
package main
import "fmt"
func test_string(s string){
    fmt.Printf("inner: %v, %v\n",s, &s) //a, 0x40e140
    s = "b"
    fmt.Printf("inner after change: %v, %v\n",s, &s) //b, 0x40e140
}
func main() {
    s := "a"
    fmt.Printf("outer: %v, %v\n",s, &s) //a, 0x40e128
    test_string(s)
    fmt.Printf("outer after change: %v, %v\n",s, &s) //a, 0x40e128
}
test_string()内部修改 字符串的数值
test_string()参指针地址变了 函数外部变量未受内部修改的影响

map类型传参

demo2
package main
import "fmt"
func test_map(m map[string]string){
    fmt.Printf("inner: %v, %p\n",m, m) //map[a:1 b:2 c:3], 0xc0000b8000
    m["c"]="11"
    fmt.Printf("inner: %v, %p\n",m, m)  //map[a:1 b:2 c:11], 0xc0000b8000
}
func main() {
    m := map[string]string{"a":"1","b":"2","c":"3",}
    fmt.Printf("outer: %v, %p\n",m, m) //map[a:1 b:2 c:3], 0xc0000b8000
    test_map(m)
    fmt.Printf("outer: %v, %p\n",m, m)  //map[a:1 b:2 c:11], 0xc0000b8000
}
test_map()对map修改 函数外生效 函数内外map地址一样
Go的 map 实际是 hashtable
运算符:=是make()的语法糖 相同的作用
初始化map变量 后将变量传递到test_map()
指针类型的参数在函数内部直接修改内容
值传递的是值本身 有一次拷贝 (函数内外 变量的地址发生变化)
函数内部的修改对外部变量无效
demo2 变量却没有拷贝 源代码
// 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 {
  if hint < 0 || hint > int(maxSliceCap(t.bucket.size)) {
  hint = 0
}
是go的make()函数在map中通过makemap()函数来实现的代码段
makemap()返回的是hmap类型的指针hmap
就是说 test_map(map)等同于test_map(hmap)
go中当map作为形参时 虽然是值传递 但是由于make()返回的是一个指针类型
所以函数修改map的数值并影响到函数外
例demo3
package main
import "fmt"
func test_map2(m map[string]string){
    fmt.Printf("inner: %v, %p\n",m, m)  //map[], 0x0
    m = make(map[string]string, 0)
    m["a"]="11"
    fmt.Printf("inner: %v, %p\n",m, m)  //map[a:11], 0xc00008c1b0
}
func main() {
    var m map[string]string  //未初始化
    fmt.Printf("outer: %v, %p\n",m, m) //map[], 0x0
    test_map2(m)
    fmt.Printf("outer: %v, %p\n",m, m)  //map[], 0x0
}
函数test_map2()外 对map变量m进行了声明而未初始化
函数test_map2()中map才初始化和赋值
对于map的更改 没有反馈到函数外

Channel类型传参

demo4
package main
import "fmt"
func test_chan2(ch chan string){
    fmt.Printf("inner: %v, %v\n",ch, len(ch)) //0x436100, 1
    ch<-"b"
    fmt.Printf("inner: %v, %v\n",ch, len(ch)) //0x436100, 2
}
func main() {
    ch := make(chan string, 10)
    ch<- "a"
    fmt.Printf("outer: %v, %v\n",ch, len(ch)) //0x436100, 1
    test_chan2(ch)
    fmt.Printf("outer: %v, %v\n",ch, len(ch)) //0x436100, 2
}
channel 在函数外 channel的size发生了变化
golang对于channel与map类似 其make()函数实现源代码
func makechan(t *chantype, size int) *hchan {
elem := t.elem
...
是make() chan的返回值为hchan类型的指针
当业务代码在函数内对channel操作的同时
也影响函数外的数值

Slice 类型传参

demo5
golang中slice行为
slice的make实现代码
func makeslice(et *_type, len, cap int) slice {
与map和channel不同
sclie的make函数返回的是个内建结构体类型slice的对象
并非指针类型 内建slice的数据结构
type slice struct {
array unsafe.Pointer
len   int
cap   int
}
采用slice在golang传递参数 在函数内对slice的操作是不应该影响到函数外的
package main
import "fmt"
func main() {
    sl := []string{"a","b","c",}
    fmt.Printf("main before test%v, %p\n",sl, sl) //main before test[a b c], 0xc000074150
    test_slice(sl)
    fmt.Printf("main after test%v, %p\n",sl, sl)  //main after  test[aa b c], 0xc000074150
}
func test_slice(sl []string){
    fmt.Printf("inner before modify %v, %p\n",sl, sl) //inner before modify [a b c], 0xc000074150
    sl[0] = "aa"
    //sl = append(sl, "d")
    fmt.Printf("inner after modify %v, %p\n",sl, sl)  //inner after modify [aa b c], 0xc000074150
}
在函数内部对slice中的第一个元素的数值修改成功的返回到了test_slice()函数外层
显示是同一个地址
makeslice()返回的是值类型
但是当该数值作为参数传递时
在函数内外的地址却未发生变化
内部slice中的第一个元素用来存放数据的结构是指针类型
指向了真的存放数据的指针
虽然指针拷贝了 但是指针所指向的地址却未更改
在函数内部修改了指针所指向的地方的内容
从而实现了对元素修改的目的
将demo5 注释的那行代码 打开
sl = append(sl, "d")
再重新运行 得到的结果有变化
fmt.Printf("%v, %p\n",sl, sl) //inner after modify [aa b c d], 0xc0000a6120
函数内 修改了slice中一个已有元素
向slice中append一个元素
函数外部修改的元素生效了 append元素消失了
是由于slice结构引起的
slice类型在make()的时候有len和cap的可选参数
数据部分由于是指针类型 在函数内部对slice数据的修改是生效的
因为值传递进去的是指向数据的指针
表示长度的len和容量的cap为int类型
那么在传递到函数内部的就仅只是一个副本
因此在函数内部通过append修改了len的数值
但却影响不到函数外部slice的len变量
从而append的影响便无法在函数外部看到
完整的slice信息
makeslice()出来 是 结构体对象 不是指针
函数内外打印的slice地址一致
函数体内对slice中元素的修改在函数外部生效
函数体内对slice进行append操作在外部没有生效
为什么函数内外对于这三个内建类型变量的地址打印却是一致的?
打印函数fmt.Printf()有操作
通过%p来打印地址信息 fmt包中fmtPointer()
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
    }
    ...
}
fmtPointer() 对于map channel和slice 都被当成了指针来处理
通过Pointer()函数获取对应的值的指针
channel和map是因为make函数返回的就已经是指针了
但是对于slice这个非指针 在value.Pointer()是如何处理的呢?
// 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:
          ...
        case Slice:
          return (*SliceHeader)(v.ptr).Data
    }
    ...
}
在Pointer()函数中 对于Slice类型的数据
返回的一直是指向第一个元素的地址
所以通过fmt.Printf()中%p来打印Slice的地址
其实打印的结果是内部存储数组元素的首地址
这也就解释了问题2中为什么地址会一致的原因了

golang 传参是值传递


然而golang隐藏了一些实现细节
在处理map channel和slice等内置结构的数据时
其实处理的是一个指针类型的数据
在函数内部可以修改(部分修改)数据的内容
但这些修改得以实现的原因是因为数据本身是指针类型
而不是因为golang采用了引用传递