说说不知道的Golang中参数传递 (2)

由于在函数test_map2()外仅仅对map变量m进行了声明而未初始化,在函数test_map2()中才对map进行了初始化和赋值操纵,这时候,我们看到对于map的更改便无法反馈到函数外了。

//output outer: map[], 0x0 inner: map[], 0x0 inner: map[a:11], 0x442260 outer: map[], 0x0 跟风的Channel

在介绍完map类型作为参数传递时的行为后,我们再来看看golang的特殊类型:channel的行为。还是通过一段代码来来入手:

//demo4 package main import "fmt" func test_chan2(ch chan string){ fmt.Printf("inner: %v, %v\n",ch, len(ch)) ch<-"b" fmt.Printf("inner: %v, %v\n",ch, len(ch)) } func main() { ch := make(chan string, 10) ch<- "a" fmt.Printf("outer: %v, %v\n",ch, len(ch)) test_chan2(ch) fmt.Printf("outer: %v, %v\n",ch, len(ch)) }

结果如下,我们看到,在函数内往channel中塞入数值,在函数外可以看到channel的size发生了变化:

//output outer: 0x436100, 1 inner: 0x436100, 1 inner: 0x436100, 2 outer: 0x436100, 2

在golang中,对于channel有着与map类似的结果,其make()函数实现源代码如下:

func makechan(t *chantype, size int) *hchan { elem := t.elem ...

也就是make() chan的返回值为一个hchan类型的指针,因此当我们的业务代码在函数内对channel操作的同时,也会影响到函数外的数值。

与众不同的Slice

对于golang中slice的行为,可以总结一句话:与众不同。首先,我们来看下golang中对于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的操作是不应该影响到函数外的。那么,对于下面的这段示例代码,运行的结果又是什么呢?

//demo5 package main import "fmt" func main() { sl := []string{ "a", "b", "c", } fmt.Printf("%v, %p\n",sl, sl) test_slice(sl) fmt.Printf("%v, %p\n",sl, sl) } func test_slice(sl []string){ fmt.Printf("%v, %p\n",sl, sl) sl[0] = "aa" //sl = append(sl, "d") fmt.Printf("%v, %p\n",sl, sl) }

通过运行结果,我们看到,在函数内部对slice中的第一个元素的数值修改成功的返回到了test_slice()函数外层!与此同时,通过打印地址,我们发现也显示了是同一个地址。到了这儿,似乎又一个奇怪的现象出现了:makeslice()返回的是值类型,但是当该数值作为参数传递时,在函数内外的地址却未发生变化,俨然一副指针类型。

//output [a b c], 0x442260 [a b c], 0x442260 [aa b c], 0x442260 [aa b c], 0x442260

这时候,我们还是回归源码,回顾一下上面列出的golang内部slice结构体的特点。没错,细心地读者可能已经发现,内部slice中的第一个元素用来存放数据的结构是个指针类型,一个指向了真正的存放数据的指针!因此,虽然指针拷贝了,但是指针所指向的地址却未更改,而我们在函数内部修改了指针所指向的地方的内容,从而实现了对元素修改的目的了。

让我们再进阶一下上面的示例,将注释的那行代码打开:

sl = append(sl, "d")

再重新运行上面的代码,得到的结果又有了新的变化:

//output [a b c], 0x442280 [a b c], 0x442280 [aa b c d], 0x442280 [aa b c], 0x442280

函数内我们修改了slice中一个已有元素,同时向slice中append了另一个元素,结果在函数外部:

修改的元素生效了;

append的元素却消失了。

其实这就是由于slice的结构引起的了。我们都知道slice类型在make()的时候有个len和cap的可选参数,在上面的内部slice结构中第二和第三个成员变量就是代表着这俩个参数的含义。我们已知原因,数据部分由于是指针类型,这就决定了在函数内部对slice数据的修改是可以生效的,因为值传递进去的是指向数据的指针。而同一时刻,表示长度的len和容量的cap均为int类型,那么在传递到函数内部的就仅仅只是一个副本,因此在函数内部通过append修改了len的数值,但却影响不到函数外部slice的len变量,从而,append的影响便无法在函数外部看到了。

解释到这儿,基本说清了golang中map、channel和slice在函数传递时的行为和原因了,但是,喜欢提问的读者可能一直觉得有哪儿是怪怪的,这个时候我们来完整的整理一下已经的关于slice的信息和行为:

makeslice()出来的一定是个结构体对象,而不是指针;

函数内外打印的slice地址一致;

函数体内对slice中元素的修改在函数外部生效了;

函数体内对slice进行append操作在外部没有生效;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zygjfj.html