# 11. 值与引用
# 表11-1
- 值与引用 | |存储内容|逻辑指代|实际数据|价值属性|对象赋值|实现多态|控制| | --------|:-----|:----|:----|:----|:----|:----|:----| |值|数据|直接|数据|在线|内在价值|赋值|否|否|无| |引用|地址|间接|离线|使用价值|克隆|能|能|无|
# 11.1 语法类型——体用之分
# 值与引用
- 值和引用因其天生的对立性,提供了一个二分法(dichotomy)
它们把数据分为两类:
值——具有某种类型的数据;引用——可用来获取特定数据的值。
把变量分成两类:
值变量(value variable)——表示值的变量;
引用变量(reference variable)——表示引用的变量。
把数据类型分成两类:
值类型(value type)——能直接访问的数据类型;
引用类型——借助引用才能被访问的数据类型。
把对象分成两类
值类型对象、引用类型对象。
值重在价值,引用重在使用价值。
- 区别
值类型的不仅是引用类型的构建基础,而且还有一些优点
访问数据时,少一次内存寻址;创建单个实例时,少占一个引用的空间、少做一次内存分配;
创建数组时,二者的差异更加明显。
值类型对象可能分配在栈中,而引用类型对象必须分配在堆中。
采用值类型有可能利用栈的优势,进一步提高程序的性能、减少程序员的负担。
引用因其间接性和抽象性带来更大的灵活性,主要表现在:
可以避免一些不必要的值拷贝,从而提高效率;
当一个对象须要在多处共享时,更离不开引用;
类似链表、二叉树等这样的递归结构,没有引用也难以实现;
引用允许为空值;
引用不仅是堆分配的必要工具,同时还是实现多态的前提条件。
# 内存分配机制
- 静态分配(static allocation)、栈分配、堆分配
静态分配发生在编译器,全局变量、静态常量、常熟变量等;
栈分配和堆分配都发生在运行期,
但前者一般在编译器就可以确定待分配内存空间的大小和生命周期,后者可以推迟到运行期。
前者主要用于存储局部变量或则自动变量(区别 P320),堆内存用于存储由new 运算符、malloc 函数等动态分配而得的空间。
值变量和引用变量的区别不在于它们存放的地点——栈或堆上(不同的语言有不同的实现),而在于它们存放的内容——数据或地址,
在于他们存放目标数据的方法——在线或离线。
(在线指变量的目标数据在空间上内嵌(embedded)与包含该变量的对象或环境中。)
# 语言实现
C++和C#提供了按值传递和按引用传递两种机制。
同时支持按值和按引用传递,他们的指针用法也让值和引用的角色更加清晰。
Java是按值传递对象引用,而不是按引用传递对象。
为什么Java不像C++和C#那样支持按引用传递呢?
一方面,可以保证语言的简单性。
另一方面,按引用传递的最大好处是处理产生临时的复制对象,在时空效率上通常优于值传递。
但Java不同在于,所有对象都是引用类型的,对象本身并不会被复制,因此没有按引用传递的机制也无大碍。
- 赋值方式
当一个对象给一个变量赋值或作为参数按值传递时,在C++中复制的是对象的值,在Java中复制的却是该对象的引用。
因此,C++有专门的赋值运算符和复制构造函数,而Java则没有。
Java要达到复制对象值的目的,不能隐式地通过变量赋值或参数传递,只能显式地重新构造对象或通过克隆、序列化等手段。
这即是常说的值语义(value semeantics)和引用语义的区别。
Java中的对象都是通过引用来传递的,在C++中一个对象必须通过指针或引用才能表现多态特征,而C#中的值类型干脆不允许被继承。
原因在于,对一个不通过引用而被直接操作的对象来说,
多态是不必要的——它的具体类型在编译期间及已经确认,动态绑定多此一举。
多态也是不可能的——分配给它的空间无法容纳通常更大的子类型对象。
- Java:
在Java里面参数传递都是按值传递”这句话的意思是:
按值传递是传递的值的拷贝,按引用传递其实传递的是引用的地址值,所以统称按值传递。
在Java里面只有基本类型和按照下面这种定义方式的String是按值传递,其它的都是按引用传递。
就是直接使用双引号定义字符串方式:String str = “Java私塾”;
1.对象就是传引用
2.原始类型就是传值
3.String,Integer, Double等immutable类型因为没有提供自身修改的函数,
每次操作都是新生成一个对象,所以要特殊对待。可以认为是传值。
Integer
Integer 和 String 一样。保存value的类变量是Final属性,无法被修改,只能被重新赋值/生成新的对象。
当Integer 做为方法参数传递进方法内时,
对其的赋值都会导致原Integer的引用被 指向了方法内的栈地址,失去了对原类变量地址的指向。
对赋值后的Integer对象做得任何操作,都不会影响原来对象。
- C++:
- 值传递:
实参x,y的地址分别是0xffbef938, 0xffbef934, 值分别是1,2。
形参a,b的地址分别是0xffbef918,0xffbef914, 虽然它们存储的值和x,y一样,都是1,2,但是这只是拷贝过来的。
(传递的是实参变量值,形参保存一样的值)
- 指针传递:
实参x,y, 形参a,b的地址同上,但是a,b的内容分别为0xffbef938(x的地址),0xffbef934(y的地址),
*a也就是0xffbef938内存中存放的内容,即x的值1。
简单地说,a是一个指向外部实参地址的指针,*a是指针的内容,如果改变了*a也必然导致外部实参的改变。
(传递的是实参变量地址,形参保存实参变量地址)
- 引用传递:
然而与指针传递不同的是,形参a,b的地址也与x,y相同,即0xffbef938, 0xffbef934。
这样一来,交换a,b就相当于交换x,y。
(传递的是实参变量地址,形参变量与实参是同一个变量(拥有不同的变量名))
- PHP:
在php当中,普通得数据类型的参数传递是通过copy赋值,而object对象则会传递对象的引用,
在函数的调用或者直接赋值时会按照这个规则进行。
php中array()传递属于普通数据类型(与我们在C/C++中的传递数组时是传递指针有所不同),
因此数组不能够使用clone将内容复制,而是直接赋值时就会复制整个数组,
但是该数组是否会完全把内容复制,还要根据数组的item内容确定:
当数组的item是普通数据类型时(数值,数组etc.),会直接整个数组包括内容都被复制;
当数组的item是object时,只会复制数组的引用,而指向的object的内容是不会被复制的。
因此对象数组需要使用clone对每个item复制方可对整个数组复制。