这里有两个涉及相似词汇的二分法问题:值与引用类型,以及按值与按引用传递变量。
价值 v. 参考类型
第一个问题是值与引用类型。值类型通常通过复制传递。值类型有:
- 日期
- 字符
- U/Int(16/32/64)
- 十进制
- 单双
- 布尔值
- 结构
- 枚举
除了上面列出的类型之外,所有类型都是引用类型。当一个对象被传递时,实际上传递的是它的内存地址,这在 32 位平台上通常被认为是一个 int,在 64 位平台上是一个 long。
按值传递 v. 按引用
第二个问题是通过值与引用传递变量。
变量是内存中某个位置的一个插槽,可以容纳一些东西。对于值类型,它保存实际值。对于引用类型,它保存堆上对象的内存地址(或者是 Nothing)。
按价值
当您按值传递变量时,该变量的内存位置中的任何内容都会被复制。对于值类型,这意味着值本身被复制。对于引用类型,复制的是变量引用的对象的内存地址。
引用
请记住,变量只是内存中用于保存内容的插槽。当您通过引用传递变量时,您传递的是该插槽的地址(而不是该插槽中的数据)。
如果该变量是值类型,则该插槽本身就保存该值,因此传递的内容是指向该值的指针。
如果该变量是引用类型,则插槽持有指向对象在内存中的位置的指针,因此传递的东西是指向变量的指针(就像值类型一样),它本身包含另一个指针(不像值类型) 这导致保存变量所引用的对象的内存位置。
这允许一个函数修改另一个函数中的变量,如下所示:
Sub IPassByReference
Dim myVariable As Boolean = False
IReceiveByReference myVariable
Debug.Print(myVariable.ToString()) 'Always prints True
End Function
Sub IReceiveByReference(ByRef flag As Boolean)
flag = True 'the memory address of myVariable was passed.
End Function
让我们比较一下按值传递的情况:
Sub IPassByValue
Dim myVariable As Boolean = False
IReceiveByValue myVariable
Debug.Print(myVariable.ToString()) 'Always prints False
End Function
Sub IReceiveByValue(ByVal flag As Boolean)
flag = True 'the value of myVariable was passed.
End Function
在上面的示例中,Boolean 是一种值类型。如果它是一个对象,IReceiveByReference 将有权将 myVariable 指向一个全新的对象,因为它接收到 myVariable 的地址,而不是 myVariable 指向的对象的地址。相比之下,IReceiveByValue 只传递了 myVariable 的内容,因此它不能将 myVariable 更改为指向新对象。不过,它仍然可以通过设置其字段和属性并调用其方法来更改对象。
按引用返回?
尽管函数可以通过引用传递变量,但它们不能以这种方式返回它们。当一个函数返回时,它的局部变量不再存在(或者如果它们是堆分配的,则正在等待清理)。因此,函数总是按值返回;因为局部变量不再有有效的内存地址,所以没有任何变量引用返回。
总而言之,当你从函数返回一个对象时,唯一被复制的是对象的地址。当您从函数返回值类型时,会复制该值本身。
这意味着引用类型本质上是值类型,其中值是堆上对象的内存地址(或 Nothing)。