值传递与引用传递,值类型与引用类型,深拷贝与浅拷贝

一、值传递和引用传递

        用白话来说值传递的定义:在进行函数调用的时候,传递的参数是直接复制一份原值到函数所使用的栈上(不考虑内存逃逸之类的情景)。如果在函数内部改变参数的值,那影响的仅仅是栈内的那一份副本,对函数外的原值不会有任何影响。如Java, Golang,Python之类的语言,只有值传递的形式。

        引用传递的定义:在进行函数调用的时候,传递到栈内的是参数的内存地址。如果在函数内部改变了值,那就会对函数外的参数值产生影响。以C++为例,在进行引用传递的时候,其函数声明必须在变量前加&符,使用的时候直接传递变量名即可。但是实际上,在栈内操作的是内存空间内的值。例子如下图所示:

样例函数

        看到此处,一定会有小伙伴问,我在函数参数内传递的如果是指针,那这种情况应该怎么归类?其实这种情况依然是值传递。要解释这个概念,就得了解语言的内存分配。接下来,我们就可以谈一谈Java和Golang里面的数据类型分类了。

二、值类型和引用类型

          在Java中,只支持值传递。我们先看看,Java内的8种内置基础类型:byte, short, int, long, float, double, boolean, char。这些都是值类型,其在传参的时候,都是通过复制一份值到新分配内存。在进行变量间的赋值时,从效果上来讲也是一样的(会有优化,同个值的变量指向同个值)。

        而Java中的引用类型就包含了所有对象。

        下面即为 int num = 5; Object obj = new XXX();简化的内存分配图,可以帮助小伙伴们理解。

内存分配简化图


         在Golang中,只支持值传递。Golang的内置的基本类型有:11个内置的整数数字类型:int8, uint8, int16, uint16, int32, uint32, int64, uint64, int, uint和uintptr; 浮点数类型:float32和float64; 复数类型:complex64和complex128; 字符串类型:string; 布尔类型:bool。方法或者函数在参数当中传递这些基础类型都是通过复制的方式进行的。赋值也是通过拷贝一份新的放到新开辟的内存地址当中实现。

基础类型值传递

        另外,数组也和上述内置基础类型的值传递一样的,都是复制一份内容放到新开辟的内存当中。所以,也就有在函数或者方法在传递参数的时候不要传递数组的说法。

        在了解了上述的传递之后。我们需要再讨论map, slice, chan这些常用类型的组织结构,以便我们能够更好的理解这些类型在值传递的模式下是如何工作的。我们使用的变量其实在运行时代表的是一些结构体,在进行复制的时候也是复制的描述这些类型的结构体。具体的运行时结构体如下图所示:

slice/map/chan运行时结构体

        在上图中可以发现,每个结构体其实都是描述对应类型的元数据,真正存储数据的地方由一个指针代表,所以,我们在复制后对其内的值进行修改也会影响原来的值。

三、深拷贝和浅拷贝

        在了解了前面的内容后,我们就可以聊聊深拷贝和浅拷贝。深拷贝简单来讲,就是在内存当中开辟一系列完全和原址独立的空间,把对应的值都复制一份,新旧值相互独立不影响。浅拷贝则是和原值存在关联(仅仅拷贝元数据),底层存放原始数据的内存空间还是一个。

        在两种语言当中,可以很容易的理解,基础的数据类型不存在浅拷贝一说。对于较复杂的结构,其因为存在了一层元数据描述结构,所以,在进行拷贝的时候我们仅仅拷贝到的是元数据。例如:Java的所有对象,Golang的map, slice, chan都是如此。对于深拷贝,Java的解决方案是重载对象的Clone()方法。在Golang当中使用copy函数可以实现深拷贝。

你可能感兴趣的:(值传递与引用传递,值类型与引用类型,深拷贝与浅拷贝)