Go怎么可能有引用?得了吧~ 有人要说了,那利用make()
函数执行后获得的slice、map、channel等类型,不都是获得的引用吗?c++
我要说:那能叫引用吗?你能肯定啥叫引用吗? 若是你有点迷糊,那么请听我往下讲:git
这一切要从变量提及。github
不管是引用变量仍是指针变量,都是变量;那么,什么叫变量? 其实变量本质就是一块内存。一般,咱们对计算机内存进行操做,最直接的方式就是:“计算机,在0x0201地址内存一个整数100,在0x00202地址存一个浮点数10.6,读取0x00203的数据...” 这种方式让机器来操做还行,若是直接写成代码让人看的话,这一堆“0x020一、0x0202...”难记的地址能把人给整崩溃了~ 因而,聪明的人们想出了一种方法:把一堆难记的地址用其余人类能够方便读懂的方式来间接表示。例如:将“0x0201”的地址命名为“id”,将“0x0202”命名为“score”...而后,代码编译期间,再将"name"等人类能读懂的文字转化为真实的内存地址;因而,变量诞生了~微信
因此,其实每一个变量都表明了一块内存,变量名是咱们给那块儿内存起的一个别名,内存中存的值就是咱们给变量赋的值。变量名在程序编译期间会直接转化为内存地址。markdown
引用是指向另一个变量的变量,或者说,叫一个已知变量的别名。数据结构
注意,引用和引用自己指向的变量对应的是同一块内存地址。引用自己也会在编译期间转化为真正的内存地址。固然咯,引用和它指向的变量在编译期间会转化为同一个内存地址。函数
指针自己也是一个变量,须要分配内存地址,可是内存地址中存的是另外一个变量的内存地址。有点绕口,请看图:oop
咱们先看看“正统”的引用的例子,在C++中(C中是没有引用的哈):ui
#include <stdio.h>
int main(void) {
int i = 3;
int *ptr = &i;
int &ref = i;
printf("%p %p %p\n", &i, ptr, &ref);
// 打印出:0x7ffeeac553a8 0x7ffeeac553a8 0x7ffeeac553a8
return 0;
}
复制代码
变量地址、引用地址、指针的值 均相同;符合常理spa
那咱们再试试Go中相似代码的例子:
package main
import "fmt"
func main() {
i := 3
ref := i
ptr := &i
fmt.Println(fmt.Sprintf("%p %p %p", &i, &ref, ptr))
// 打印出 0xc000118000 0xc000118008 0xc000118000
}
复制代码
变量i地址和指针ptr的值同样,这是符合预期的;可是:正如Go中没有特别的“引用符号”(C++中是int &ref = i;
)同样,上述go代码中的ref
压根就是个变量,根本不是引用。
但是,不少人不死心,是否是“实验对象”不对啊?代码中使用的是int整型,咱们换作slice
和map
试试?毕竟网上的"资料"都是这么写的: 例如如下截图(只看标红部分就好):
还有以下截图(只看标红部分就好):
ok,那咱们能够试试以下map的代码,看到底有没有引用:
package main
import "fmt"
func main(){
i := make(map[string]string)
i["key"]="value"
ref := i
fmt.Println(fmt.Sprintf("%p %p", &i, &ref))
// 打印出:0xc00010e018 0xc00010e020
}
复制代码
哈哈!不对呀,若是是引用的话,打印的地址应该相同才对,可是如今不相同!因此不存在? 别着急,紧接着看下面的例子:
package main
import "fmt"
func main(){
i := make(map[string]string)
i["key"]="value"
ref := i
ref["key"] = "value1"
fmt.Println(i["key"]) // 打印结果:value1
fmt.Println(ref["key"]) // 打印结果:value1
fmt.Println(fmt.Sprintf("%p %p", &i, &ref))
// 打印结果:0xc00000e028 0xc00000e030
}
复制代码
能猜出来打印了什么吗?变量地址是不对,可是,可是值竟然变了!ref变量能够“操控”i变量的内容!就和引用同样!
这就很奇怪了~ 咋回事儿呢?
咱们细细研究一下map
、slice
、channel
等具体实现(详情请看:个人其余文章 图解Go map底层实现、图解Go slice底层实现、图解Go channel底层实现)咱们发现,这些类型的底层实现都是会有一个指针指向另外的存储地址,因此,在make
函数建立了具体的类型实例后,实际上在内存空间中会开辟多个地址空间,而随着变量的赋值,指针引用的那个地址值也会跟着“复制”,于是其余变量能够改变原有变量的内容。
听着是否是有点绕?咱们来看看图:
首先实例化了map并赋值
而后又赋值给了另一个变量ref
因为对于指针变量的值而言,就是一个地址(程序实现上就是一串数字),因此,在赋值的时候,就“复制”了一串数字,可是,这串数字背后的含义确是另一个地址,而地址的内容,偏偏就是map
slice
channel
等数据结构真正底层存储的数据!
因此,两变量由于同一个指针变量指向的内存,而产生了相似于“引用”的效果。假如实例化的类型数据中,没有指针
属性,则不会产生这种“类引用”的效果: 例如以下代码:
package main
import "fmt"
func main(){
i := 3
ref := i
ref = 4
fmt.Println(i, ref) // 打印输出:3 4
fmt.Println(fmt.Sprintf("%p %p", &i, &ref))
// 打印输出:0xc000016070 0xc000016078
}
复制代码
能够将代码上述仔细看看能输出什么,不出意外的话你会发现:“类引用”效果消失了~
要想再次展示“类引用”效果,只要建立一个带有指针属性的类型便可,咱们本身实现均可以,无需依赖Go基础库中的map
、slice
、channel
等
package main
import "fmt"
type Instance struct {
Name string
Data *int
}
func (i Instance) Store(num int) {
*(i.Data) = num
}
func (i Instance) Show() int{
return *(i.Data)
}
func main(){
data := 5
i := Instance{
Name:"hello",
Data:&data,
}
ref := i
ref.Store(7)
fmt.Println(i.Show(), ref.Show())
// 打印出:7 7
fmt.Println(fmt.Sprintf("%p %p", &i, &ref))
// 打印出:0xc0000a6018 0xc0000a6030
}
复制代码
看看以上代码,是否是实现了“类引用”? 有人要说了map
展现key值,slice
展现某个下标的值,没有用方法呀? 这就不对了,其实map
的展现key的值mapData[key]
也好,更改值也好,slice
展现下标值sliceArray[0]
也好,更改值也好;背后底层实现也都是些“函数”和“方法”,只不过Go语言把这些函数和方法作成了语法糖,咱们无感知罢了~
好了,如今我再问你:还敢说Go语言有引用类型吗?是否是感受:也有、也没有了? 😝
互联网技术窝