不杠哦,Go里面就是没有引用传递
先拿C++来说,解释一下值传递、指针传递和引用传递的含义
值传递:
形参是实参的拷贝,改变形参的值并不会影响外部实参的值,是将实参的值拷贝到另外的内存地址中才修改。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。当函数内部需要修改参数,并且不希望这个改变影响调用者时,采用值传递。
指针传递:
形参为指向实参地址的指针,当对形参的指向 *** 作时,就相当于对实参本身进行的 *** 作;指针传递参数本质上是值传递的方式,它所传递的是一个地址值。指针是一个实体,指向一块内存,它的内容是所指内存的地址;而引用则是某块内存的别名。
引用传递:
形参相当于是实参的“别名”,引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量,而指针可以改变其指向的对象)。对形参的 *** 作其实就是对实参的 *** 作,被调函数对形参做的任何 *** 作都影响了主调函数中的实参变量。
看下C++中的三种传递之间的关系:
#include
using namespace std;
//1.值传递
void change1(int a)
{
cout << "change1 形参地址:" << &a << endl;
a = a + 1;
}
//2.指针传递
void change2(int *a)
{
cout << "change2 形参指针地址:" << &a << endl;
cout << "change2 指针指向的地址:" << a << endl;
cout << "change2 指针指向的地址中的值:" << *a << endl;
*a = *a + 1;
}
//3.引用传递
void change3(int &b)
{
cout << "change3 形参地址: " << &b << endl;
cout << "change3 形参地址中的内容: " << b << endl;
b = b + 1;
}
int main()
{
int a = 10;
cout << "实参地址: " << &a << " 值:" << a << endl;
cout << "值传递:"<< endl;
change1(a);
cout << "after change1, a = "<< a << endl;
cout << "指针传递:" << endl;
int *p = &a;
cout << "before change2, 实参指针地址: " << &p << endl;
change2(p);
cout << "after change2, a = " << a << endl;
cout << "引用传递:" << endl;
change3(a);
cout << "after change3, a = " << a << endl;
while(1){}
return 0;
}
输出:
值传递不用解释,就是实参的一份拷贝,值相等而已,形参和实参完全是两个不同的地址
指针传递本质也是值传递,拷贝的是指针本身:
而引用传递则是直接把实参的别名传递进去了,形参和实参都是同一个地址
C++中有引用传递,也有引用类型
int c = 6;
int &p = c;//p是c的一个别名
go语言中也有引用类型: 指针、slice、map 和 channel。但是,go语言中没有&T
类型, 有形如var p = &c
的这种表示 , p是指针,等效于var p *int= &c
下面通过测试说明golang中只有值传递和指针传递,两者可以归结为值传递
测试:
package main
import (
"fmt"
)
type myStruct struct{
str string
}
func change1(a int) {
fmt.Printf("change1 形参地址:%v,值:%v\n", &a, a)
a = a + 1
}
func change2(a *myStruct) {
fmt.Printf("change2 形参指针地址:%p\n", &a)
fmt.Printf("change2 形参指针指向的地址:%p\n", a)
fmt.Printf("change2 形参指针指向的地址的内容:%v\n", *a)
a.str = "change2"
}
func change3(a *int){
*a = *a + 1
}
func main() {
var a1 = 10
var a2 = myStruct{
str:"hello",
}
fmt.Printf("main a1实参地址:%p, 值:%v\n", &a1, a1)
fmt.Println("值传递:")
change1(a1)
fmt.Printf("after change1, a1 = %v\n", a1)
fmt.Printf("main a2实参地址:%p,值:%v\n", &a2, a2)
p := &a2
fmt.Printf("main a2实参指针地址:%p,值:%p\n", &p, p)
fmt.Println("引用传递:")
change2(p)
fmt.Printf("after change2, a2 = %v\n", a2)
change3(&a1)
fmt.Printf("after change3, a1 = %v\n", a1)
}
输出
关于slice、map、chan等类型的参数传递
但是上述文章中关于slice的描述有不对的地方,简单来说就是,slice本身是个结构体,但它内部第一个元素是一个指针类型,指向底层的具体数组,slice在传递时,形参是拷贝的实参这个slice,但他们底层指向的数组是一样的,拷贝slice时,其内部指针的值也被拷贝了,也就是说指针的内容一样,都是指向同一个数组
type slice struct {
array unsafe.Pointer
len int
cap int
}
文章中作者做的实验个人感觉应该把代码改成这样才合适:
func main() {
var args = []int64{1,2,3}
fmt.Printf("切片args的地址: %p \n",&args)
fmt.Printf("切片args第一个元素的地址: %p \n",&args[0])
modifiedNumber(args)
fmt.Println(args)
}
func modifiedNumber(args []int64) {
fmt.Printf("形参切片的地址 %p \n",&args)
fmt.Printf("形参切片args第一个元素的地址: %p \n",&args[0])
args[0] = 10
}
输出:
而map和chan使用make函数返回的实际上是 *hmap
和*hchan
指针类型,也就是指针传递。
说实话指针这块在当时学c语言的时候就没整明白…
type A struct {
Ptr1 *B
Ptr2 *B
Val B
Val2 string
Val3 int
}
type B struct {
Str string
}
func main() {
a := A{
Ptr1: &B{"ptr-str-1"},
Ptr2: &B{"ptr-str-2"},
Val: B{"val-str"},
Val2: "main",
Val3: 0,
}
fmt.Printf("main a addr: %p\n", &a)
fmt.Printf("main Ptr1 addr: %p, point to addr: %p, value: %v\n", &a.Ptr1, a.Ptr1, *a.Ptr1)
fmt.Printf("main Ptr2 addr: %p, point to addr: %p, value: %v\n", &a.Ptr2, a.Ptr2, *a.Ptr2)
fmt.Printf("main Val addr: %p, value: %v\n", &a.Val, a.Val)
fmt.Printf("main Val2 addr: %p, value: %v\n", &a.Val2, a.Val2)
fmt.Printf("main Val3 addr: %p, value: %v\n\n", &a.Val3, a.Val3)
demo1(a)
fmt.Println("after demo1:")
fmt.Printf("main Ptr1 addr: %p, point to addr: %p, value: %v\n", &a.Ptr1, a.Ptr1, *a.Ptr1)
fmt.Printf("main Ptr2 addr: %p, point to addr: %p, value: %v\n", &a.Ptr2, a.Ptr2, *a.Ptr2)
fmt.Printf("main Val addr: %p, value: %v\n", &a.Val, a.Val)
fmt.Printf("main Val2 addr: %p, value: %v\n", &a.Val2, a.Val2)
fmt.Printf("main Val3 addr: %p, value: %v\n\n", &a.Val3, a.Val3)
demo2(&a)
fmt.Println("after demo2:")
fmt.Printf("main Ptr1 addr: %p, point to addr: %p, value: %v\n", &a.Ptr1, a.Ptr1, *a.Ptr1)
fmt.Printf("main Ptr2 addr: %p, point to addr: %p, value: %v\n", &a.Ptr2, a.Ptr2, *a.Ptr2)
fmt.Printf("main Val addr: %p, value: %v\n", &a.Val, a.Val)
fmt.Printf("main Val2 addr: %p, value: %v\n", &a.Val2, a.Val2)
fmt.Printf("main Val3 addr: %p, value: %v\n\n", &a.Val3, a.Val3)
}
func demo1(a A) {
fmt.Printf("demo1 a addr:%p\n", &a)
// Update a value of a pointer and changes will persist
a.Ptr1.Str = "demo1-ptr-str1"
fmt.Printf("demo1 Ptr1 addr: %p, point to addr: %p, value: %v\n", &a.Ptr1, a.Ptr1, *a.Ptr1)
// Use an entirely new B object and changes won't persist
a.Ptr2 = &B{"demo1-ptr-str-2"}
fmt.Printf("demo1 Ptr2 addr: %p, point to addr: %p, value: %v\n", &a.Ptr2, a.Ptr2, *a.Ptr2)
//a.Val.Str = "new-val-str"
a.Val = B{"demo1-val-str"}
fmt.Printf("demo1 Val addr: %p, value: %v\n", &a.Val, a.Val)
fmt.Printf("demo1 Val2 before addr: %p, value: %v\n", &a.Val2, a.Val2)
a.Val2 = "demo1"
fmt.Printf("demo1 Val2 after addr: %p, value: %v\n", &a.Val2, a.Val2)
fmt.Printf("demo1 Val3 before addr: %p, value: %v\n", &a.Val3, a.Val3)
a.Val3 = 1
fmt.Printf("demo1 Val3 after addr: %p, value: %v\n", &a.Val3, a.Val3)
}
func demo2(a *A) {
fmt.Printf("demo2 a addr:%p\n", &a)
fmt.Printf("demo2 a point to addr:%p\n", a)
//在go语言中通过指针去访问指针所对应的地址处的值时,*允许不写。
a.Ptr1.Str = "demo2-ptr-str1"
fmt.Printf("demo1 Ptr1 addr: %p, point to addr: %p, value: %v\n", &a.Ptr1, a.Ptr1, *a.Ptr1)
a.Ptr2 = &B{"demo2-ptr-str-2"}
fmt.Printf("demo2 Ptr2 addr: %p, point to addr: %p, value: %v\n", &a.Ptr2, a.Ptr2, *a.Ptr2)
//a.Val.Str = "new-val-str"
a.Val = B{"demo2-val-str"}
fmt.Printf("demo2 Val addr: %p, value: %v\n", &a.Val, a.Val)
a.Val2 = "demo2"
fmt.Printf("demo2 Val2 addr: %p, value: %v\n", &a.Val2, a.Val2)
a.Val3 = 2
fmt.Printf("demo2 Val3 addr: %p, value: %v\n", &a.Val3, a.Val3)
}
输出
这种是针对复杂聚合类型中的参数传递,demo1可以说是值传递,demo2是指针传递,画图分析如下:
其实就是完整的拷贝了一份原结构体对象,只是原对象里面有指针对象,拷贝的时候一起拷贝了,值是一样的,也就指向了同一个地方,那么内部对该指向的地址的内容进行修改的话,肯定会影响外部;但是该指针可以修改成指向其他地方,此时和外部实参就没有关系了
在函数内对结构体内参数修改之后:【发生改动的地方用红色标出】
这里指针传递其实就是传递了指向实参对象的一个指针的值,那么对该指针指向地址的内容进行修改就相当于对原对象进行修改,而在go语言中通过指针去访问指针所对应的地址处的值时,*
允许不写。
修改后
ok啦!应该算是完全搞懂啦!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)