level 11
3.0、数组
什么是数组?在计算机中,数组表示一组数据,而非一组数字。比如类型是实数的时候,可以用数组表示一组实数;是字符的时候,可以用数组表示一组字符。
3.0.0、数组的声明
Delphi的数组声明位置与变量相同,类型使用“array of T”,其中T为一种类型。在array之后还可以使用中括号注明数组的下标范围。
比如:
// 双斜线表示此行之后的部分为注释。
a: array [0 ..10] of Integer; // 下标从0到10的整型数组。
b: array ofInteger; // 下标从零开始的动态整型数组。
3.0.1静态数组和动态数组
在刚才的示例中,我们注意到数组的下标可以是声明为固定范围,也可以是从零开始的动态范围。这两类数组在声明和使用上具体如何操作呢?
静态数组指下标范围、在内存中占用字节的范围均不变的数组。
对于静态数组,在array之后使用中括号声明数组范围,符号“..”(连续的两个点)表示连续变化到,习惯上前后用空格隔开。比如:
a: array [0 ..10] of Integer; // 下标从0到10的整型数组。
b: array [1 ..20] of Single; // 下标从1到20的单精度实型数组。
c: array [100 ..110] of Char; // 下标从100到110的字符型数组。
在函数中完整的写法如下:
function AFunction(x:Single): Single;
var
a: array [1 .. 10] of Single;
begin
end;
如果静态数组的下标超过了其范围,将会报错。
动态数组指下标范围、在内存中占用字节的范围均不固定的数组。
对于动态数组,则无需声明下标范围,但是下标必须从零开始。在动态数组使用前必须注明长度。使用过程SetLength(S, N);设定数组(或者字符串,但是字符串通常长度自动设定)S的长度为N。使用函数Length(S)获取数组或者字符串的长度。
如果设置长度前,数组不是空数组,那么下标在新长度范围内的数据将会自动保留。如果动态数组超出了范围,同样会报错。这个比静态数组更难避免,所以使用的时候需要小心。
3.0.2、数组的引用
如果需要获取数组中某一个数据,那么使用中括号注明下标,例如:
a[0], b[2],c[11]等。
当然,中括号中可以使变量、表达式甚至函数,例如:
a[i], b[i + 1],c[AFunction(i)]等,但是要注意,下标的数据类型必须是整数,比如这个就不可以:
a[0.5], b[i / 2]等。
3.0.3、数组的赋值
如果需要更改数组中某一个数据,直接使用赋值即可,例如:
a[0] := 0;
3.1、泛型列表
数组的功能并不强大(相比于泛型列表而言),比如说,如果想要在第一个位置插入一个数据,那么需要修改长度,然后由后往前将每一个数据向后复制。这很麻烦,效率也不一定高(远通常的赋值远比不上直接移动指针或者按照流在内存上进行移位,这些暂且不表)。
在较新版本的Delphi中,提供了一个新的单元,名称为:System.Generics.Collections,这个单元内提供了一系列栈、队列、链表、泛型列表等实用而且强大的工具。更为重要的是,这些工具的效率是不低的,至少不会比一般人设计的通用实现方案效率低(专有的可能进行专有的优化,通常不做考虑)。因此,在新的版本下,我们一般使用泛型列表代替动态数组。当然,静态数组没有必要使用其他方案代替。
3.1.0、何为泛型
所谓泛型,通俗来讲就是不具体的数据类型,在官方的定义中,常使用T代表泛型。在泛型列表中,可以分别定义整数、实数等数据的泛型列表,如下:
a: TList<Integer>;// 整数型列表
b:TList<Single>; // 单精度实数型列表
c:TList<TButton>; // 按钮控件型列表
可以看见,可以对从基本数据类型到控件类都使用泛型列表来储存,这就完全具有了取代动态数组的基础。
3.1.1、泛型列表的使用
Delphi已经提供了大量可用的方法,下面便一一讲解。
引用:使用中括号注明下标,与数组一致,下标必须从零开始。
修改:直接赋值,与数组一致。
获取长度:由于列表不是数组,不能够使用Length函数,而是需要通过使用内部方法Count。例如获取列表List1的长度,需要使用List1.Count,当然可以通过给这个赋值来修改其长度,但是不推荐这样做。
增加:在末尾增加一个数据,使用List.Add(a);其中a为需要添加的数据。
插入:在规定位置插入一个数据,使用List.Insert(a, i);其中a为需要插入的数据,i为插入后该数据所在的索引。也就是说,当i为0时,插入在列表开头。
删除:在规定位置删除一个数据,使用List.Delete(i);其中i为需要删除数据的位置。
通常只需要使用到这些。
由于泛型列表是类,所以在使用前需要创建,使用完后需要释放(相当于删除)。在使用前使用如下方法创建:(以整数型列表为例)
L :=TList<Integer>.Create;
使用结束后:
L.Free;
如果是局部变量,推荐使用如下方法释放更好:
FreeAndNil(L);
如果在使用前没有创建类,那么会报错。如果使用后没有释放类,那么会占据内存且被占据的内存无法被使用(俗称泄露),释放泄露内存的行为简称排泄(虽然不雅,但是很形象地说明了其重要性)。
如果一个应用泄露过多,那么就会导致占用资源过多的问题,如果泄露不可控,严重的情况下回导致死机。
练习:排序算法
要求:在一个输入框中输入一系列整数,并且使用逗号分隔,然后将排序后的结果输出到另一个输入框中。
提示:
一般将输出结果用的输入框设置为只读。
算法讲解:想知道更多更详细地,可以去查。
这里使用插入式排序(非常适用于泛型列表,而且简单易懂)
所谓插入式排序,只要经常打扑克牌的人就会自己摸索出来。打牌的时候如果需要排序,当然可以在抓牌的时候每抓一张便放入到合适的位置。
假定我们使用有小到大的排序。
第一步应该是获取数字,也就是将字符串形式的一系列整数分割,然后放入一个泛型列表中。
最简单的办法当然是一个一个依次判定,类似的在第二章的求和小程序中有讲解(看源代码)
我们这一次使用StringHelper来实现这个功能。
StringHelper是为了使Firemonkey下跨平台的字符串更易于使用而生的,常用的方法有:
String.SubString(n,c); // 返回从第n位起(0开始)c个字符构成的字符串,如果省略c这个参数,则直接到字符串尾部。
String.Reserve;// 返回翻转后法字符串
String.Chars[i];// 索引第i个字符,只读,0开始。
String.Replace(Old,New); // 返回替换后的字符串。
String.IndexOf(s);// 返回字符或者字符串首次出现的位置,有重载支持自某一位开始查找
详见源代码。
2017年01月26日 08点01分