5.1.5 开放式数组形参
对于数组的使用有一个非常特殊的场景,即用于向函数传递一个灵活的参数列表。除了直接传递数组外,本节和下一节还将解释两种特殊的语法约定。在上一段代码中的 Format 函数就是一个使用了这种约定的函数,它的第二个参数接受一个就地定义的数组值。
与 C 语言(以及其他一些基于 C 语法的语言)不同,在传统的 Pascal 语言中,函数或过程总是有固定数量的参数。然而,在 Object Pascal 中,有一种方法可以使用就地定义的数组向例程传递不同数量的参数,这种技术被称为开放数组参数。
注解:从历史上看,开放数组参数早于动态数组,但如今这两种功能的工作方式非常相似,几乎无法区分。这就是为什么我在讨论动态数组后才介绍开放数组参数。
定义开放式数组形参与定义类型化动态数组类型的基本相同,但以const指示符作为前缀。 这意味着您可以指定参数的类型,但不需要指出数组要有多少个该类型的元素。 下面是这样一个定义的例子,摘自OpenArray的示例:
function Sum(const A: array of Integer): Integer;
varI: Integer;
beginResult := 0;for I := Low(A) to High(A) doResult := Result + A[I];
end;
您可以通过传递一个整数常量表达式的数组(其中也可以包含变量,作为用于计算表达式各部分的值)来调用该函数:
X := Sum([10, Y, 27 * I]);
给定一个整数动态数组,可以直接将其传递给需要相同基本类型(本例中为整数)的开放数组形参的例程。下面是一个将完整数组作为参数传递的示例:
varList: array of Integer;X, I: Integer;
begin// 初始化数组SetLength(List, 10);for I := Low(List) to High(List) doList[I] := I * 2;// 使用我们的函数获取列表元素的总和X := Sum(List);
这适用于动态数组。如果你有一个与基本类型相匹配的静态数组,你也可以将它传递给期待一个开放数组形参的函数,或者你可以调用 Slice 函数只传递现有数组的一部分(如它的第二个参数所示)。下面的代码段(也是 OpenArray 示例的一部分)展示了如何将静态数组或其一部分传递给 Sum 函数:
varList: array[1..10] of Integer;X, I: Integer;
begin// 初始化数组for I := Low(List) to High(List) doList[I] := I * 2;// 使用我们的函数获取列表元素的总和X := Sum(List);Show(X.ToString);// 对数组的一部分求和X := Sum(Slice(List, 5));Show(X.ToString);
可变类型的开放式数组形参
除了有类型的开放数组参数外,Object Pascal
语言还允许定义可变类型或无类型的开放式数组。这种特殊类型的数组的元素数量未定义,但这些元素的数据类型也是未定义的,同时还可以传递不同类型的元素。这是该语言中类型不完全安全的领域之一。。
从技术上讲,您可以定义一个类型为array of const
的参数,将一个具有未定义数量且元素类型不同的数组传递给函数。例如,这是Format函数的定义(我们将在第6章中了解如何使用该函数,同时已在一些演示中使用过):
function Format(const Format: string;const Args: array of const): string;
第二个参数是一个开放式数组,它接收未定义数量的值。实际上,您可以用以下方式调用此函数:
N := 20;
S := 'Total:';
Show(Format('Total: %d', [N]));
Show(Format('Int: %d, Float: %f', [N, 12.4]));
Show(Format('%s %d', [S, N * 2]));
请注意,您可以以常量、变量值或表达式的形式传递参数。声明此类函数很简单,但如何编写代码呢?如何知道参数的类型?可变类型开放式数组参数的值与 TVarRec 类型元素兼容。
注解: 不要混淆 TVarRec 记录和变量类型使用的 TVarData 记录。这两种结构的目的不同,不兼容。甚至可能的类型列表也是不同的,因为 TVarRec 可以保存 Object Pascal 数据类型,而 TVarData 可以保存 Windows OLE 数据类型。本章稍后将介绍变体(Variants)。
以下是可变类型的开放式数组值和 TVarRec 记录支持的数据类型:
vtInteger vtBoolean vtChar
vtExtended vtString vtPointer
vtPChar vtObject vtClass
vtWideChar vtPWideChar vtAnsiString
vtCurrency vtVariant vtInterface
vtWideString vtInt64 vtUnicodeString
记录结构有一个类型字段(VType)和变体(variant)字段,您可以使用以访问实际数据。这是该结构高级用法,稍后将更多介绍。
一种常见的方法是使用case语句处理您在此类调用中接收的不同类型的参数。在SumAll
函数示例中,我希望能够对不同类型的值求和,将字符串转换为整数,将字符转换为相应的序数值,并将布尔值True转换为1。这段代码确实相当高级(并使用了指针解引用),所以如果现在不完全理解它也没关系:
function SumAll(const Args: array of const): Extended;
varI: Integer;
beginResult := 0;for I := Low(Args) to High(Args) docase Args[I].VType ofvtInteger:Result := Result + Args[I].VInteger;vtBoolean:if Args[I].VBoolean thenResult := Result + 1;vtExtended:Result := Result + Args[I].VExtended^;vtWideChar:Result := Result + Ord(Args[I].VWideChar);vtCurrency:Result := Result + Args[I].VCurrency^;end; // Case
end;
我已将此函数添加到OpenArray示例中,并在以下方式调用它:
varX: Extended;Y: Integer;
beginY := 10;X := SumAll([Y * Y, 'k', True, 10.34]);Show('SumAll: ' + X.ToString);
end;
此调用的输出将Y的平方、K的序数值(为107)、布尔值的1以及扩展数的总和相加,结果为:
SumAll: 218.34