end;
这段代码并没有真正重载原始的MessageDlg 例程,实际上如果键入: MessageDlg ('Hello');
你将得到一个有意思的错误消息,告诉你缺少参数。调用本地例程而不是VCL的唯一途径是明确标示例程所在单元,这有悖于例程重载的思想: OverDefF.MessageDlg ('Hello');
确省参数
Delphi允许你给函数的参数设定确省值,这样调用函数时该参数可以加上,也可以省略。下例把应用程序全程对象的MessageBox 方法重新包装了一下,用PChar 替代字符串,并设定两个确省值:
procedure MessBox (Msg: string; Caption: string = 'Warning';
Flags: LongInt = mb_OK or mb_IconHand); begin
Application.MessageBox (PChar (Msg), PChar (Caption), Flags); end;
使用这一定义,你就可以用下面任一种方式调用过程: MessBox ('Something wrong here!');
MessBox ('Something wrong here!', 'Attention'); MessBox ('Hello', 'Message', mb_OK);
从图6.5中可以看到,Delphi的代码参数提示条会用不同的风格显示确省值参数,这样你就很容易确定哪个参数是可以省略的。
图 6.5: Delphi代码参数提示条用方括号标示确省值参数,调用时可以省略该参数 46
注意一点,Delphi 不产生任何支持确省参数的特殊代码,也不创建例程的多份拷贝,缺省参数是由编译器在编译时添加到调用例程的代码中。
使用确省参数有一重要限定:你不能“跳过”参数,如省略第二个参数后,不能把第三个参数传给函数:
MessBox ('Hello', mb_OK); // error
确省参数使用主要规则:调用时你只能从最后一个参数开始进行省略,换句话说,如果你要省略一个参数,你必须省略它后面所有的参数。
确省参数的使用规则还包括: ? ? ?
带确省值的参数必须放在参数表的最后面。
确省值必须是常量。显然,这限制了确省参数的数据类型,例如动态数组和界面类型的确省参数值只能是 nil;至于记录类型,则根本不能用作确省参数。 确省参数必须通过值参或常参传递。引用参数 var不能有缺省值。
如果同时使用确省参数和重载可能会出现问题,因为这两种功能可能发生冲突。例如把以前ShowMsg 过程改成:
procedure ShowMsg (Str: string; I: Integer = 0); overload; begin
MessageDlg (Str + ': ' + IntToStr (I), mtInformation, [mbOK], 0);
47
end;
编译时编译器不会提出警告,因为这是合法的定义。 然而编译调用语句: ShowMsg ('Hello');
编译器会显示 Ambiguous overloaded call to 'ShowMsg'.( 不明确重载调用ShowMsg)。注意,这条错误信息指向新定义的重载例程代码行之前。实际上,用一个字符串参数无法调用ShowMsg 过程,因为编译器搞不清楚你是要调用只带字符串参数的ShowMsg 过程,还是带字符串及整型确省参数的过程。遇到这种问题时,编译器不得不停下来,要求你明确自己的意图。
字符串类型
在Borland公司的Turbo Pascal和16位Delphi中,传统的字符串类型是一个字符序列,序列的头部是一个长度字节,指示当前字符串的长度。由于只用一个字节来表示字符串的长度,所以字符串不能超过255个字符。这一长度限制为字符串操作带来不便,因为每个字符串必须定长(确省最大值为255),当然你也可以声明更短的字符串以节约存储空间。
字符串类型与数组类型相似。实际上一个字符串差不多就是一个字符类型的数组,因为用[]符号,你就能访问字符串中的字符,这一事实充分说明了上述观点。
为克服传统Pascal 字符串的局限性,32位Delphi增加了对长字符串的支持。这样共有三种字符串类型:
?
ShortString 短字符串类型也就是前面所述的传统 Pascal 字符串类型。这类字符串最多只能有255个字符,与16位Delphi中的字符串相同。短字符串中的每个字符都属于ANSIChar 类型(标准字符类型)。 ?
ANSIString长字符串类型就是新增的可变长字符串类型。这类字符串的内存动态分配,引用计数,并使用了更新前拷贝(copy-on-write)技术。这类字符串长度没有限制(可以存储多达20亿个字符!),其字符类型也是ANSIChar 类型。 ?
WideString 长字符串类型与ANSIString 类型相似,只是它基于WideChar 字符类型,WideChar 字符为双字节Unicode 字符。
使用长字符串
如果只简单地用String定义字符串,那么该字符串可能是短字符串也可能是ANSI长字符串,这取决于$H 编译指令的值,$H+(确省)代表长字符串(ANSIString 类型)。长字符串是Delphi 库中控件使用的字符串。
Delphi 长字符串基于引用计数机制,通过引用计数追踪内存中引用同一字符串的字符串变量,当字符串不再使用时,也就是说引用计数为零时,释放内存。
48
如果你要增加字符串的长度,而该字符串邻近又没有空闲的内存,即在同一存储单元字符串已没有扩展的余地,这时字符串必须被完整地拷贝到另一个存储单元。当这种情况发生时,Delphi运行时间支持程序会以完全透明的方式为字符串重新分配内存。为了有效地分配所需的存储空间,你可以用SetLength 过程设定字符串的最大长度值: SetLength (String1, 200);
SetLength 过程只是完成一个内存请求,并没有实际分配内存。它只是把将来所需的内存预留出来,实际上并没有使用这段内存。这一技术源于Windows 操作系统,现被Delphi用来动态分配内存。例如,当你请求一个很大的数组时,系统会将数组内存预留出来,但并没有把内存分配给数组。
一般不需要设置字符串的长度,不过当需要把长字符串作为参数传递给API 函数时(经过类型转换后),你必须用SetLength 为该字符串预留内存空间。
看一看内存中的字符串
为了帮你更好地理解字符串的内存管理细节,在程序中声明了两个全程字符串:Str1 和 Str2,当按下第一个按钮时,程序把一个字符串常量赋给第一个变量,然后把第一个变量赋给第二个:
Str1 := 'Hello'; Str2 := Str1;
除了字符串操作外,程序还用下面的StringStatus 函数在一个列表框中显示字符串的内部状态:
function StringStatus (const Str: string): string; begin
Result := 'Address: ' + IntToStr (Integer (Str)) + ', Length: ' + IntToStr (Length (Str)) +
', References: ' + IntToStr (PInteger (Integer (Str) - 8)^) + ', Value: ' + Str; end;
在StringStatus 函数中,用常量参数传递字符串至关重要。用拷贝方式(值参)传递会引起副作用,因为函数执行过程中会产生一个对字符串的额外引用;与此相反,通过引用(var)或常量(const)参数传递不会产生这种情况。由于本例不希望字符串被修改,因此选用常量参数。
为获取字符串内存地址(有利于识别串的实际内容也有助于观察两个不同的串变量是否引用了同一内存区),通过类型映射把字符串类型强行转换为整型。字符串实际上是引用,也就是指针:字符串变量保存的是字符串的实际内存地址。
49
通过运行这个例子,你会看到两个串内容相同、内存位置相同、引用记数为2,如图7.1中列表框上部所示。现在,如果你改变其中一个字符串的值,那么更新后字符串的内存地址将会改变。这是copy-on-write技术的结果。
图 7.1: 例StrRef显示两个串的内部状态,包括当前引用计数
第二个按钮(Change)的OnClick 事件代码如下,结果如图7.1列表框第二部分所示: procedure TFormStrRef.BtnChangeClick(Sender: TObject); begin
Str1 [2] := 'a';
ListBox1.Items.Add ('Str1 [2] := ''a''');
ListBox1.Items.Add ('Str1 - ' + StringStatus (Str1)); ListBox1.Items.Add ('Str2 - ' + StringStatus (Str2)); end;
注意,BtnChangeClick 只能在执行完BtnAssignClick 后才能执行。为此,程序启动后第二个按钮不能用(按钮的Enabled 属性设成False);第一个方法结束后激活第二个按钮。你可以自由地扩展这个例子,用StringStatus 函数探究其它情况下长字符串的特性。
Delphi 字符串与 Windows PChar字符串
长字符串为零终止串,这意味着长字符串完全与Windows使用的C语言零终止串兼容,这给长字符串使用带来了便利。一个零终止串是一个字符序列,该序列以一个零字节(或null)结尾。零终止串在Delphi中可用下标从零开始的字符数组表示,C语言就是用这种数组类型定义字符串,因此零终止字符数组在Windows API 函数(基于C语言)中很常见。由于Pascal长字符串与C语言的零终止字符串完全兼容,因此当需要把字符串传递给Windows API 函数时,你可以直接把长字符串映射为PChar 类型。
50