13.2 对象引用模型
正如我们在第7章中所看到的,语言中的对象是作为引用实现的。类类型的变量只是指向堆上存储对象数据的内存位置的指针。实际上还有一些额外的信息,例如类引用、访问对象的虚拟方法表的方法,但这超出了本章的范围(我将在第13章的“这个指针是对象引用吗?”部分中简要介绍)。
我们还看到,将一个对象分配给另一个对象只会复制引用,因此您将在内存中拥有两个指向单个对象的引用。要拥有两个完全独立的对象,您需要创建第二个对象并将第一个对象的数据复制到它上面(这是一项不可自动执行的操作,因为其实现细节可能因实际数据结构而异)。 在编码术语中,如果您编写以下代码,则不会创建第二个对象,而是创建对现有对象的新引用:
var
Button2: TButton;
begin
Button2 := Button1;
换句话说,内存中只有一个对象,Button1和Button2变量都引用它,如图13.3所示。
图13.3 复制对象引用
13.2.1 传递对象作为参数
当您将对象作为参数传递给函数或方法时,类似的情况也会发生。一般来说,您只是复制对同一对象的引用,然后在方法或函数中访问该对象、对该对象执行操作并修改其数据,而不管参数是否作为const
参数传递。 例如,编写这样一个过程并按以下方式调用这个过程,您将修改Button1
或AButton
的标题:
procedure ChangeCaption(AButton: TButton; Text: string);
begin
AButton.Text := Text;
end;
// 调用...
ChangeCaption(Button1, ‘Hello’)
如果您需要创建一个新对象,该怎么办呢?首先,您需要创建这个对象,然后复制每个属性。一些类,特别是大多数派生自TPersistent
而不是TComponent
的类,定义了Assign方法以复制对象的数据。例如,您可以编写:
ListBox1.Items.Assign(Memo1.Lines);
即使您直接对这些属性赋值,Object Pascal也会为您执行类似的代码。与列表框的items
属性相关联的SetItems
方法实际调用TStringList
类的Assign
方法,列表框的items实际上是TStringList
类。
因此,让我们尝试总结一下各种参数传递修饰符应用于对象的作用:
-
如果没有修饰符,则可以对对象和引用它的变量执行任何操作。您可以修改原始对象,但如果将新对象赋值给参数,则此新对象与原始对象和引用原始对象的变量无关。
-
如果有
const
修饰符,则可以更改值和调用对象的方法,但不能将新对象赋值给参数。请注意,将对象作为const
传递没有性能优势。 -
如果使用
var
修饰符,则可以更改对象中的任何内容,还可以在调用位置用一个新对象替换原来的对象,就像使用其他var
参数一样。限制条件是,必须传递变量的引用(而不是一般表达式),而且引用类型必须与参数类型完全匹配。 -
最后,还有一种相当不为人知的将对象作为参数传递的方法,称为常量引用,写法是
[ref] const
。当参数以常量引用方式传递时,其行为与引用传递(var)
类似,但它允许更灵活地传递参数类型,不要求类型完全匹配(就像允许传递子类对象一样)。