设为首页 加入收藏

TOP

说说不知道的Golang中参数传递(一)
2019-01-16 12:08:36 】 浏览:383
Tags:说说 知道 Golang 参数 传递

本文由云+社区发表

导言

几乎每一个C++开发人员,都被面试过有关于函数参数是值传递还是引用传递的问题,其实不止于C++,任何一个语言中,我们都需要关心函数在参数传递时的行为。在golang中存在着map、channel和slice这三种内建数据类型,它们极大的方便着我们的日常coding。然而,当这三种数据结构作为参数传递的时的行为是如何呢?本文将从这三个内建结构展开,来介绍golang中参数传递的一些细节问题。

背景

首先,我们直接的来看一个简短的示例,下面几段代码的输出是什么呢?

//demo1
package main

import "fmt"

func test_string(s string){
    fmt.Printf("inner: %v, %v\n",s, &s)
    s = "b"
    fmt.Printf("inner: %v, %v\n",s, &s)
}

func main() {
    s := "a"
    fmt.Printf("outer: %v, %v\n",s, &s)
    test_string(s)
    fmt.Printf("outer: %v, %v\n",s, &s)
}

上文的代码段,尝试在函数test_string()内部修改一个字符串的数值,通过运行结果,我们可以清楚的看到函数test_string()中入参的指针地址发生了变化,且函数外部变量未被内部的修改所影响。因此,很直接的一个结论呼之欲出:golang中函数的参数传递采用的是:值传递

//output
outer: a, 0x40e128
inner: a, 0x40e140
inner: b, 0x40e140
outer: a, 0x40e128

那么是不是到这儿就回答完,本文就结束了呢?当然不是,请再请看看下面的例子:当我们使用的参数不再是string,而改为map类型传入时,输出结果又是什么呢?

//demo2
package main

import "fmt"

func test_map(m map[string]string){
    fmt.Printf("inner: %v, %p\n",m, m)
    m["a"]="11"
    fmt.Printf("inner: %v, %p\n",m, m)
}

func main() {

    m := map[string]string{
        "a":"1",
        "b":"2",
        "c":"3",
    }
    
    fmt.Printf("outer: %v, %p\n",m, m)
    test_map(m)
    fmt.Printf("outer: %v, %p\n",m, m)
}

根据我们前文得出的结论,按照值传递的特性,我们毫无疑问的猜想:函数外两次输出的结果应该是相同的,同时地址应该不同。然而,事实却正是相反:

//output
outer: map[a:1 b:2 c:3], 0x442260
inner: map[a:1 b:2 c:3], 0x442260
inner: map[a:11 b:2 c:3], 0x442260
outer: map[b:2 c:3 a:11], 0x442260

没错,在函数test_map()中对map的修改再函数外部生效了,而且函数内外打印的map变量地址竟然一样。做技术开发的人都知道,在源代码世界中,如果地址一样,那就必然是同一个东西,也就是说:这俨然成为了一个引用传递的特性了。

两个示例代码的结果竟然截然相反,如果上述的内容让你产生了疑惑,并且你希望彻底的了解这过程中发生了什么。那么请阅读完下面的内容,跟随作者一起从源码透过现象看本质。本文接下来的内容,将对golang中的map、channel和slice三种内建数据结构在作为函数参数传递时的行为进行分析,从而完整的解析golang中函数传递的行为。

迷惑人心的Map

Golang中的map,实际上就是一个hashtable,在这儿我们不需要了解其详细的实现结构。回顾一下上文的例子我们首先通过make()函数(运算符:=是make()的语法糖,相同的作用)初始化了一个map变量,然后将变量传递到test_map()中操作。

众所周知,在任何语言中,传递指针类型的参数才可以实现在函数内部直接修改内容,如果传递的是值本身的,会有一次拷贝发生(此时函数内外,该变量的地址会发生变化,通过第一个示例可以看出),因此,在函数内部的修改对原外部变量是无效的。但是,demo2示例中的变量却完全没有拷贝发生的迹象,那么,我们是否可以大胆的猜测,通过make()函数创建出来的map变量会不会实际上是一个指针类型呢?这时候,我们便需要来看一下源代码了:

// 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
    }
    ...

上面是golang中的make()函数在map中通过makemap()函数来实现的代码段,可以看到,与我们猜测一致的是:makemap()返回的是一个hmap类型的指针hmap。也就是说:test_map(map)实际上等同于test_map(hmap)。因此,在golang中,当map作为形参时,虽然是值传递,但是由于make()返回的是一个指针类型,所以我们可以在函数哪修改map的数值并影响到函数外。

我们也可以通过一个不是很恰当的反例来证明这点:

//demo3
package main

import "fmt"

func test_map2(m map[string]string){
    fmt.Printf("inner: %v, %p\n",m, m)
    m = make(map[string]string, 0)
    m["a"]="11"
    fmt.Printf("inner: %v, %p\n",m, m)
}

func main() {
    var m map[string]string//未初始化
    
    fmt.Printf(&qu
首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Golang并行判断素数 下一篇[日常] Go-逐行读取文本信息

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目