一、为什么会出现这两种?
值类型与引用类型之间的区别一直是一个很抽象的话题, 本文就从一个不一样的视角去看待这个问题:
值类型和引用类型对于变量来说的, 它可以看成是变量的一种属性:
var a = 1; //变量a是一个值类型
var b = [1, 2, 3, 4]; //b是一个引用类型, 得到的是后面数组的地址值
int a = 2;
int[] b = [1, 2, 3, 4];
我们经常说传递值会发生复制, 而传递引用是新的指针的指向原来的指针值, 为什么需要这样做呢? 原因有两个:
- 占用内存比较小的数据确定的值,发生复制完全不影响效率。
- 占用内存比较大的数据(值组合类型),使用引用来指向, 在传递的时候, 新的指针指向原来的指针值,共享内存, 减少复制导致的性能损失。
二、如何区分?
所有从上面这两点来看, 引入引用类型只是为了效率的提升, 并无其它。所以在一些情况下, 这两者在概念上是完全不做区分的。
-
指针类型的解引用操作(dref),这使得你可以使用
*x = 2
这样的语句来改变引用指向的内容, 导致共享地址的其它引用看到新的内容。但是你不可以使用x=2这样的语句来得到新的值。所以你感觉到值类型的存在。var a : i32 = 3; var p : *i32 = &a; *p = 2; //改变引用指向的内容 var b : i32 = a; b = 2;
-
像struct这样的"值组合类型", 你可以通过
x.foo=2
这样的成员赋值改变引用数据, 比如class object的一部分, 使得共享地址的其它引用看到新的值, 你没法通过成员赋值让另外一个struct变量得到新的值, 所以你感觉到值类型的存在。Note: 使用struct这样声明的变量是类型, 然后使用这种类型去声明变量
struct Point { x: i32; }; Point p1 = {x : 2} p.x = 2; struct Point p2 = p1;
而在Java、Python中, 即使你将那些原始类型的变量看成是引用, 你确不可以对它进行引用类型所持有,而值类型没有的操作。
你对原始类型只能做这两件事情:
- 取出这个变量的值
- 为这个变量赋新的值
同时在Java中, 对结构体这样的引用类型你修改它的一部分, 另外一个结构体也会得到新的值。它的行为是一致的。
所以我们可以将这两者统一看成是引用类型, 只不过施加的对象不同罢了
var b = 1;
var b1 = b;
var a = new A();
var a1 = a;
三、值放在堆上和放在栈上?
对于这个问题一种常见的误导人的答案就是:
- 原始类型存储在栈上, 对象存储在堆上
- 少量数据存储在栈上, 大量的数据存储在堆上
这样的理解是有错误的, 这种技法就和只背公式一样, 甚至会出现错误。正确的理解方式应该从程序的执行角度去思考。