go 值类型与引用类型

背景

golang中都是值传递,粗看起来在func中改变值只会改变副本,不会改变原来的值。但是某些时候在func中改变了值,原来的值也会改变。比如

package main

import "fmt"

type testStruct struct {
	a int
	b map[int]string
}

func main() {
	// %p表示16进制输出
	var a int = 1
	p := &a
	fmt.Printf("%v\n", a)
	fmt.Printf("%p\n", &a)
	//引用类型直接%p取值,取的是引用类型的值,这里就是a的存储值1的内存地址
	fmt.Printf("%p\n", p)
	//引用类型前面加&并%p取值,取的是引用类型值的地址,p的值为a的内存地址,这个内存地址也要存储在内存,&p取的就是存储a地址这个值的内存地址
	fmt.Printf("%p\n", &p)

	array1 := [3]string{"a", "b", "c"}
	fmt.Printf("The array: %v\n", array1)
	fmt.Printf("The array pointer: %p\n", &array1)
	fmt.Printf("The array[0] pointer: %p\n", &array1[0])
	fmt.Printf("The array[1] pointer: %p\n", &array1[1])

	fmt.Println("----array modified----")
	array2 := modifyArray(array1)
	fmt.Printf("The modified array: %v\n", array2)
	fmt.Printf("The modified array pointer: %p\n", &array2)
	fmt.Printf("The modified array pointer: %p\n", &array2[0])

	fmt.Printf("The original array: %v\n", array1)
	fmt.Printf("The original array pointer: %p\n", &array1)
	fmt.Printf("The original array[0] pointer: %p\n", &array1[0])

	fmt.Println("-----")
	slice := []int{1, 2, 3}
	fmt.Printf("The slice : %v\n", slice)
	// 存储切片的底层数组的地址
	fmt.Printf("The slice pointer: %p\n", slice)
	// 存储切片底层地址值的地址
	fmt.Printf("The slice pointer: %p\n", &slice)
	fmt.Printf("The slice pointer[0]: %p\n", &slice[0])

	fmt.Println("----slice modified----")
	modifySlice := modifySlice(slice)
	fmt.Printf("The modified slice: %v\n", modifySlice)
	fmt.Printf("The modified slice pointer: %p\n", modifySlice)
	fmt.Printf("The modified slice[0] pointer: %p\n", &modifySlice[0])

	fmt.Printf("The original slice: %v\n", slice)
	fmt.Printf("The original slice pointer: %p\n", slice)
	fmt.Printf("The original slice[0] pointer: %p\n", &slice[0])


	fmt.Println("-----")
	st :=testStruct{
		a: 1,
		b: map[int]string{1:"str"},
	}
	fmt.Printf("The struct : %v\n", st)
	fmt.Printf("The struct pointer: %p\n", &st)
	fmt.Printf("The struct pointer.a : %p\n", &st.a)
	fmt.Printf("The struct pointer st.b : %p\n", st.b)
	fmt.Printf("The struct pointer &st.b : %p\n", &st.b)

	fmt.Println("----struct modified----")
	st1 := modifyStruct(st)
	fmt.Printf("The modified struct: %v\n", st1)
	fmt.Printf("The modified struct pointer.a : %p\n", &st1.a)
	fmt.Printf("The modified struct pointer st1.b : %p\n", st1.b)
	fmt.Printf("The modified struct pointer &st1.b : %p\n", &st1.b)
	fmt.Printf("The original struct: %v\n", st)
	fmt.Printf("The original struct pointer.a : %p\n", &st.a)
	fmt.Printf("The original struct pointer st.b : %p\n", st.b)
	fmt.Printf("The original struct pointer &st.b: %p\n", &st.b)

	fmt.Println("-----")
	st3 :=testStruct{
		a: 1,
		b: map[int]string{1:"str"},
	}
	fmt.Printf("The struct : %v\n", st3)
	fmt.Printf("The struct pointer: %p\n", &st3)
	fmt.Printf("The struct pointer.a : %p\n", &st3.a)
	fmt.Printf("The struct pointer st3.b : %p\n", st3.b)
	fmt.Printf("The struct pointer &st3.b : %p\n", &st3.b)
	fmt.Println("----struct pointer modified----")
	st4 := modifyStructPointer(&st3)
	fmt.Printf("The modified struct pointer: %v\n", st4)
	fmt.Printf("The modified struct pointer &st4.a : %p\n", &st4.a)
	fmt.Printf("The modified struct pointer st4.b : %p\n", st4.b)
	fmt.Printf("The modified struct pointer &st4.b : %p\n", &st4.b)
	fmt.Printf("The original structpointer: %v\n", st3)
	fmt.Printf("The original struct pointer &st3.a : %p\n", &st3.a)
	fmt.Printf("The original struct pointer st3.b : %p\n", st3.b)
	fmt.Printf("The original struct pointer &st3.b : %p\n", &st3.b)

	fmt.Println("---- interface ----")
	var in inter = modifySlice
	fmt.Printf("The interface: %p\n", in)
	fmt.Printf("The interface pointer: %p\n", &in)
}


func modifySlice(slice []int) []int {
	slice[0] = 3
	return slice
}

func modifyArray(a [3]string) [3]string {
	a[0] = "x"
	return a
}

func modifyStruct(s testStruct) testStruct {
	s.a = 2
	s.b[1] = "mod"
	return s
}

func modifyStructPointer(s *testStruct) *testStruct {
	s.a = 2
	s.b[1] = "mod"
	return s
}



//以下是运行结果
1
0xc000092000
0xc000092000
0xc00008c010
The array: [a b c]
The array pointer: 0xc000098180
The array[0] pointer: 0xc000098180
The array[1] pointer: 0xc000098190
----array modified----
The modified array: [x b c]
The modified array pointer: 0xc0000981e0
The modified array pointer: 0xc0000981e0
The original array: [a b c]
The original array pointer: 0xc000098180
The original array[0] pointer: 0xc000098180
-----
The slice : [1 2 3]
The slice pointer: 0xc0000c6000
The slice pointer: 0xc0000a6018
The slice pointer[0]: 0xc0000c6000
----slice modified----
The modified slice: [3 2 3]
The modified slice pointer: 0xc0000c6000
The modified slice[0] pointer: 0xc0000c6000
The original slice: [3 2 3]
The original slice pointer: 0xc0000c6000
The original slice[0] pointer: 0xc0000c6000
-----
The struct : {1 map[1:str]}
The struct pointer: 0xc000096220
The struct pointer.a : 0xc000096220
The struct pointer st.b : 0xc000098270
The struct pointer &st.b : 0xc000096228
----struct modified----
The modified struct: {2 map[1:mod]}
The modified struct pointer.a : 0xc000096250
The modified struct pointer st1.b : 0xc000098270
The modified struct pointer &st1.b : 0xc000096258
The original struct: {1 map[1:mod]}
The original struct pointer.a : 0xc000096220
The original struct pointer st.b : 0xc000098270
The original struct pointer &st.b: 0xc000096228
-----
The struct : {1 map[1:str]}
The struct pointer: 0xc0000962a0
The struct pointer.a : 0xc0000962a0
The struct pointer st3.b : 0xc000098330
The struct pointer &st3.b : 0xc0000962a8
----struct pointer modified----
The modified struct pointer: &{2 map[1:mod]}
The modified struct pointer &st4.a : 0xc0000962a0
The modified struct pointer st4.b : 0xc000098330
The modified struct pointer &st4.b : 0xc0000962a8
The original structpointer: {2 map[1:mod]}
The original struct pointer &st3.a : 0xc0000962a0
The original struct pointer st3.b : 0xc000098330
The original struct pointer &st3.b : 0xc0000962a8
---- interface ----
The interface: 0xc000148000
The interface pointer: 0xc000118310

数组类型在调用相应的func后,原来的没变,但是切片类型变了。为什么?
这要从golang中分为值类型和引用类型说起

值类型

值类型有:int系列、float系列、bool、string、数组、结构体、接口interface、指针、函数等
值类型的零值是一个有确定意义的默认值,比如int、float的零值是0,bool类型的零值是false,string类型零值为"",结构体类型的零值为结构体中类型相应的零值,
接口、指针、函数类型零值为nil。

另外接口类型赋值 值为null的函数,接口也不为nil

因为当我们给一个接口变量赋值的时候,该变量的动态类型会与它的动态值一起被存储在一个专用的数据结构中。我们就把这个专用的数据结构叫做iface吧,在Go语言的runtime包中它其实就叫这个名字。iface的实例会包含两个指针,一个是指向类型信息的指针,另一个是指向动态值的指针。这里的类型信息是由另一个专用数据结构的实例承载的,其中包含了动态值的类型,以及使它实现了接口的方法和调用它们的途径,等等。总之,接口变量被赋予动态值的时候,存储的是包含了这个动态值的副本的一个结构更加复杂的值。

注意这里零食为nil的也不一定是引用类型

值类型的特点是:变量直接存储值,内存通常在栈中分配

引用类型

引用类型有:slice切片、管道channel、map(只有三种,三种之外都是值类型)
零值是nil

引用类型的特点是:变量存储的是一个地址,这个地址对应的空间里才是真正存储的值,内存通常在堆中分配

为什么传引用类型到函数,函数中修改之后原值会变?

go的函数传值都是值传递,即函数的入参传递到函数中后,在函数中使用的都是入参的副本。
1.因此值类型传入函数中后在函数中修改的只是副本,修改后原值不变;
2.但是引用类型传入到函数中虽然修改的虽然也是副本,但是这个副本是浅拷贝(引用类型内部实现的地址的副本,这个地址的副本只是一个值,指向还是原来的位置),,因此在函数中修改引用类型的(内部)值或者修改值类型的内部引用类型的值(比如修改结构体中的引用类型)修改的是地址值指向的内存存储的值,因此会反应到原值上。

指针类型%p详解

%p只是代表以16进制显示,并不代表取指针。

// %p表示16进制输出
var a int = 1
p := &a
fmt.Printf("%v\n", a)
fmt.Printf("%p\n", &a)
//引用类型直接%p取值,取的是引用类型的值,这里就是a的存储值1的内存地址
fmt.Printf("%p\n", p)
//引用类型前面加&并%p取值,取的是引用类型值的地址,p的值为a的内存地址,这个内存地址也要存储在内存,&p取的就是存储a地址这个值的内存地址
fmt.Printf("%p\n", &p)

//结果
1
0xc000092000
0xc000092000
0xc00008c010

在这里插入图片描述
值类型变量和引用类型变量
值类型变量a,值为1,存储变量a的内存地址为0xc000092000
指针类型 &a,某个变量的内存地址,即a的地址0xc000092000。
引用类型p,某个变量的地址的别名。
值为某个变量的内存地址(p的值为变量a的内存地址0xc000092000)
其本身作为一个变量也有自己的存储内存地址0xc00008c010。
在golang中故意淡化了指针的概念,我们只需要关注值类型和引用类型就可以。你在官方介绍中也很少看到指针类型这一概念。

二,golang中的值类型变量和引用类型变量

值类型变量:除开slice,map,channel类型之外的变量都是值类型
引用类型变量:slice,map,channel这三种

三,值类型和引用类型的区别

我们以值类型struct和引用类型map的区别在哪里。
首先定义两种类型的结构及变量:

type Student1 struct{
    Age int32
    Name string
}
type Student2 map[string]int

func main(){
    var s1 Student1
    var s2 Student2
}

1,零值不同

指针类型的变量,零值都是nil。
值类型的变量,零值是其所在类型的零值。
int32类型的零值是0
string类型的零值是""
bool类型的零值是false
符合结构struct类型的零值是其每个成员的零值的组合

fmt.Println(s1) //{0 }
fmt.Println(s2) //map[] 
fmt.Println(s1 == nil) //panic,提示cannot convert nil to type Student1
fmt.Println(s2 == nil) //true

发现map的零值是nil,struct的零值是Student1{0, “”},不是nil,而且struct类型和nil是两种不同的类型,不能比较。

2,变量申明后是否需要初始化才能使用

指针类型的变量,需要初始化才能使用。(slice是一个特例,slice的零值是nil,但是可以直接append)
值类型的变量,不用初始化,可以直接使用

s1.Name = "minping"
s1.Age = 30
fmt.Println(s1) //{30 minping}
s2["minping"] = 30 // panic: assignment to entry in nil map
fmt.Println(s2)

发现struct声明后可以直接使用直接赋值,但是map就不行,赋值的时候提示我们nil map不能赋值,需要先初始化。

3,初始化方法不同

值类型的变量,其实不需要初始化就可以使用。如果有良好的代码习惯,使用前进行初始化也是非常提倡的。
基本类型的初始化非常简单:

var i int; i = 1;
var b bool; b = true
var s string; s = ""
符合类型struct的初始化有两种:
s1 = Student1{}
s1 = new(Student1)
s1 := Student1{} //{0 }

s1 := new(Student1) //&{0 }
引用类型的变量,初始化方式也不一样:
slice类型,用make,new,{}都可以
map类型,用make,new,{}都可以
channel类型,只能用make活着new初始化
//map可以用{},make,new三种方式初始化
s2 := Student2{} //map[]
s2 := make(Student2) //map[]
s2 := new(Student2) //&map[]

//slice可以用{},make,new,但是make的时候需要带len参数
type S3 []string
s3 := S3{} //[]
s3 := new(S3) //&[]
s3 := make(S3, 10) //[         ]

//channel只能用make或者new
type Student4 chan string
s4 := new(S4) //0xc000096000
s4 := make(S4) //0xc000082008
s4 := S4{} //编译器报错:invalid type for composite literal: Student4

四,make和new的区别

从上面初始化时用make和new的结果就可以看出来:

make返回的是对象。
对值类型对象的更改,不会影响原始对象的值
对引用类型对象的更改,会影响原始对象的值
new返回的是对象的指针,对指针所在对象的更改,会影响指针指向的原始对象的值。

五,golang没有引用传递,都是值传递

如果函数形参是值类型,则会对值类型做一份拷贝作为函数形参。在函数内对形参变量做的修改,不会影响函数外的那个被传入的变量。
如果函数形参是引用类型,则会对引用类型变量做一次拷贝。但是拷贝得到的引用类型变量的值,和被传入调用函数的原始引用类型变量的值,是一样的,即指向的是同一个变量的地址(参考前面值类型变量和引用类型变量图)。所以在函数里面的修改,会影响原始引用变量指向的变量的值。

示例

package main

import "fmt"

type testStruct struct {
	i int
	f float64
	b bool
	s string
	ip *int
	sl []string
	c chan string
	in inter
	m map[int]string
	fu fun
	st testStruct1
}

type testStruct1 struct{

}

type inter interface {

}

type fun func()

func main()  {
	var i int
	fmt.Printf("int类型零值%#v\n", i)
	var f float64
	fmt.Printf("float64类型零值%#v\n", f)
	var b bool
	fmt.Printf("bool类型零值%#v\n", b)
	var s string
	fmt.Printf("string类型零值%#v\n", s)
	var t testStruct
	fmt.Printf("结构体类型零值%#v\n", t)

	var ip *int
	fmt.Printf("int指针类型零值%#v\n", ip)

	var sl []string
	fmt.Printf("切片类型零值%#v\n", sl)

	var c chan string
	fmt.Printf("管道类型零值%#v\n", c)

	var in inter
	fmt.Printf("接口类型零值%#v\n", in)

	var m map[int]string
	fmt.Printf("map类型零值%#v\n", m)

	var fu fun
	fmt.Printf("函数类型零值%#v\n", fu)
}

//结果
int类型零值0
float64类型零值0
bool类型零值false
string类型零值""
结构体类型零值main.testStruct{i:0, f:0, b:false, s:"", ip:(*int)(nil), sl:[]string(nil), c:(chan string)(nil), in:main.inter(nil), m:map[int]string(nil), fu:(main.fun)(nil), st:main.testStruct1{}}
int指针类型零值(*int)(nil)
切片类型零值[]string(nil)
管道类型零值(chan string)(nil)
接口类型零值<nil>
map类型零值map[int]string(nil)
函数类型零值(main.fun)(nil)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值