Delphi之数组
Object Pascal中可以建立丰富的数据类型。数组毫无疑问也是众多自定义数据类型中的一种。
Type
TA = array[0..9] of Integer;
...
var
A : TA;
和下面这段代码通常效果是相同的(不同的地方在类型篇再说)
var
A : Array [0..9] of Integer;
这相当于C中的
int A[10];
或Basic中的
Dim A(9) as Long或 Dim A(0 to 9) as Long
下面将分几个方面讲OP的数组:
多维数组:
多维数组的本质其实就是数组之数组。
type
TA = array[0..9] of array [0..9] of Integer;
TB = array[0..9, 0..9] of Integer;
TCA = array[0..9] of Integer;
TC = array[0..9] of TCA;
在这里TA,TB,TC是等价的。在使用时是没有分别的。例如X[0][0]:=1;Tx[0,0]:=1;(X是TA、TB、TC类型)
都是合法的。因为这几种类型都是在内存中开辟一块100x100xSizeOf(Integer)的区域,你根本无法区
分他是如何申请的,也没有必要去区分。
多维数组如何取得维数呢?
前面已经说过多维数组就是数组之数组,所以可以利用下面的方法来取得多维数组的维数:
var
k:array[2..10,3..20]of integer;
begin
showmessage(Inttostr(Low(k))+' '+Inttostr(High(k)));
showmessage(Inttostr(Low(k[Low(K)]))+' '+Inttostr(High(k[Low(K)])));//k[n]是array[3..20] of integer;的数组
end;
动态数组:
OP中动态数组的声明是
type
TA=Array of Integer;
动态数组应用中十分广泛。现在有一种趋势就是在数据结构中用动态数组代替链表
(到底哪个好哪个坏自有评价我们在这里不予以讨论)。
可能你会说动态数组根本不必要使用,我就从来没有用过。 我不信你没有用过动态数组!
String类型你用过吧,它近似可以说是动态数组的一种。
动态数组的内存是在使用分配长度时才予以分配的,他的下界只能0(AnsiString字符串例外,下界是1,原因后面再说),
动态数组的生存期是自管理的使用后一般不用释放,如果要强行释放就用把Nil附给他。
使用动态数组往往爱犯的错误:
1)和静态数组概念混淆:
动态数组的地址并不是他第一个元素的地址,而在静态数组中我们大家常常使用这样的语句:
var
A, B : array[0..19] of Integer;
...
CopyMemory(@A, @B, 20*SizeOf(Integer));
或者CopyMemory(@A[0], @B[0], 20*SizeOf(Integer));
都是正确的,因为静态数组中数组的首地址就是他第一个元素的地址。
但是在动态数组中只有第二种写法会得到正确结果。即只能写成:
var
A, B : array of Integer;
...
SetLength(A, 20);
SetLength(B, 20);
CopyMemory(@A[0], @B[0], 20*SizeOf(Integer));
2)数组的附值:
静态数组中的附值很简单
var
A, B : array[0..19] of Integer;
...
A := B;
即可对数组进行赋值;在动态数组中就要倍加小心,请看
var
A, B: array of Integer;
begin
Setlength(A, 10);
SetLength(B, 10);
B[1] := 50;
A := B;
B[1] := 100;
end;
a[1]是多少呢?按照常理A[1]的值应该是附值前的50,但恰恰相反A[1]的值是附值后B[1]再次被赋的值,
100动态数组中A := B;仅仅是将动态数组A 的指针指向动态数组B,而并不是像我们希望的那样为A开辟一
块空间。如果非要为A开辟一块空间就要用Copy来复制B的数据。
AnsiString也是动态数组,所以同样的情况也存在于String类型。
特殊的数组:
本来字符串想单独说一说,但是却因为它具有太多的动态数组特性所以不单独说了。
再一个,这里的代码和说的以后你很可能是用不到的,但是能加深你对ObjectPascal的理解。
在设计时你会知道它是如何工作的,通过这些你会更有效率的使用它。
大多数字符串与其说是数组倒不如说他是个结构。但是我们用他的数组特性更多一些。
1.ShorString:是为了与老的Pascal兼容而保留的类型。最大255个字符。
Type
TShortString
Length:Byte;
Data:array[1..Length] of Char;
end;
从结构上可以看出ShortString最大只能保存255个字符。我们可以做个实验
var
k:ShortString;
begin
k:='I am a Delphi fan!';
k[0]:=Char(13);//k.Length被置为13
showmessage(k);
end;
///两种方法效果相同
var
k:ShortString;
p:^Byte;
begin
k:='I am a Delphi fan!';
p:=@k[1];
Dec(p,1);
p^:=13;//k.Length被置为13
showmessage(k);
end;
运行一下看看会出什么结果,呵呵。
为什么Short的数组下限是0呢?想一想Byte和Char的关系我不想多说.
2.AnsiString:标准的字符串,以Nil结尾。
Type
TAnsiString=record
allocSiz: LongWord;//分配的大小
ReferencCount:LongWord; //引用次数
Length:LongWord; //字符串长度
Data:array[1..(Length+1)] of AnsiChar;
end;
有人要问为什么长度是1..(Length+1)而不是Length呢?
因为后面还要有一位NULL字符,表示结束。可见AnsiString和C/C++的兼容性也是很好的。
如下代码:
var
k: AnsiString;
p:^LongWord;
begin
k := 'I am a Delphi fan!';
p:=@K[1];
Dec(p,1);
P^:=13; //k.Length被置为13
showmessage(k);
end;
由此我们会知道ANsiString的效率是很高的,他的信息存储于字符串头中。例如我们要取字符串的长度只须读出
TAnsiString.Length的内容即可。而其c/c++的字符串就不行,取字符串的长度要从头读起直到遇到NULL记录下
读过的字符个数就是长度。可是如果字符串很长效率的差异就显示出来了,比如一个很大的文件?
前面讲过动态数组赋值的问题,它并不为动态数组单独开辟一块空间,而是简单的把指针指向所赋的数组。只有被赋值的
数组在改变时才真正分配给他空间。这样做的好处是在赋值的时候会很快。如果被赋值的数组没有改变那就比直接分配空间
快上许多。另外如果被赋值数组发生改变重新分配空间,只是花费和直接分配空间相同的时间而已。
有些人会想那么如下代码:
var
k,j:AnsiString;
begin
k:'123';
j:='456';
k:=j;
end;
k的空间已经分配,既然k的指针指向了j,那么k原来的空间岂不是浪费,造成了内存垃圾?
前面说过AnsiString也是动态数组的一种,符合动态数组生存期自管理。
但实际的情况是:当把一个指针指向一个字符创空间时,他的引用次数位就加1,
当有指针指向从这个字符串离开时引用次数位就减1,所以当引用次数位为0是就意味着没有人使用了
就自动释放这段空间。这就是我们所说的生存期自管理技术。
Type
//实际分配大小对我们来说意义不大,所以这里我们先不取他
PAnsiString=^TAnsiString;
TAnsiString= record
ReferencCount:LongWord; //引用次数
Length:LongWord; //字符串长度
end;
var
k,j: AnsiString;
P:^LongWord;
MyAnsiString:PAnsiString;
begin
k:='I love Delphi!';计数器应该为1,因为刚刚分配内存,只有一个使用者
P:=@K[1];
Dec(P,2);
MyAnsiString:=@(p^);
showmessage('现在计数器:'+Inttostr(MyAnsiString^.ReferencCount));
showmessage('长度为:'+Inttostr(MyAnsiString^.Length));
j:=K;//计数器应该加1,因为j的指针指向了他,使用者又多了一个
showmessage('现在计数器:'+Inttostr(MyAnsiString^.ReferencCount));
showmessage('长度为:'+Inttostr(MyAnsiString^.Length));
j:='123';//计数器应该减1,因为j的指针指不再指向他,使用者少了一个
showmessage('现在计数器:'+Inttostr(MyAnsiString^.ReferencCount));
showmessage('长度为:'+Inttostr(MyAnsiString^.Length));
k:=j;//k的指针指向j的内容所在,那么这片数据的计数器再减去一个,即为0。
//然而因该该内存区域已经被自动释放所以这里再去读数的话就是一些随机数据
showmessage('现在计数器:'+Inttostr(MyAnsiString^.ReferencCount));
showmessage('长度为:'+Inttostr(MyAnsiString^.Length));
end;
动态数组的回收机制原理相同,现在明白了为什么动态数组再回收时要给他赋成Nil了吧!变体型等生存期自管理的
类型都是用这种机制来实现的.
为什么说字符串是动态数组的一种呢?因为以上方法同样适用于动态数组。我们用动态数组自己实现一个
AnsiString看看行不行呢?
比如:
Type
PAnsiString=^TAnsiString;
TAnsiString= record
ReferencCount:LongWord; //引用次数
Length:LongWord; //字符串长度
end;
var
k,j: array of Integer;
P:^LongWord;
MyAnsiString:PAnsiString;
begin
SetLength(k,10);//计数器应该为1,因为刚刚分配内存,只有一个使用者k
SetLength(j,10);
P:=@K[0];
Dec(P,2);
MyAnsiString:=@(p^);
showmessage('现在计数器:'+Inttostr(MyAnsiString^.ReferencCount));
showmessage('长度为:'+Inttostr(MyAnsiString^.Length));
j:=K;//计数器应该加1,因为j的指针指向了他,使用者又多了一个
showmessage('现在计数器:'+Inttostr(MyAnsiString^.ReferencCount));
showmessage('长度为:'+Inttostr(MyAnsiString^.Length));
SetLength(j,100);//计数器应该减1,因为j的指针指不再指向他,使用者少了一个
showmessage('现在计数器:'+Inttostr(MyAnsiString^.ReferencCount));
showmessage('长度为:'+Inttostr(MyAnsiString^.Length));
SetLength(k,100);//k内存被重新分配,那么这片数据的计数器再减去一个,即为0。
//然而因没有使用者该内存区域已经被自动释放所以这里再去读数的话就是一些随机数据
showmessage('现在计数器:'+Inttostr(MyAnsiString^.ReferencCount));
showmessage('长度为:'+Inttostr(MyAnsiString^.Length));
end;
理论上来说能模拟实现AnsiString的一些功能,通过这些可以看到AnsiString不过是动态数组的一种形式。
3.WideString:宽字符串,由WideChar组成,以Nil结尾.
Type
TWideString=record
allocSiz: LongWord;//分配的大小
Length:LongWord; //字符串长度
Data:array[1..(Length+1)] of WideChar;
end;
有结构可以看出,WideString和AnsiString很类似,但是他没有回收机制。不能进行生存期自管理。
但是在处理上还是较C/C++效率高上许多。
4.Pchar:与其说成是字符串到不如说他是指针更准确一些。
Type
TPchar=^Char;
Pchar是为了和C/++兼容的实际上它是指向字符的指针。从这个字符开始到后面第一个NULL字符为止都是
Pchar字符串的内容,也就是说PChar是它内容的首地址,因为Pchar并非结构而是一种指针所以Pchar的
下限是0.
var
A:Pchar;
...
A:='1234567890';
A[0]:='a';//这一句是合法的
看看如下代码就明白了:
var
k:array of char;
p:Pchar;
i:Integer;
begin
SetLength(k,20);
FillMemory(@k[0],Length(k),Byte('A'));
i:=3; //i只要比20小,大于等于0,随你调整看看不同的结果。
k[i]:=#0;
p:=@k[0];
showmessage(p);
end;
其它:数组在所有的编程语言中都是一个很重要的数据类型(不过Java的数组使用类来实现的)。
是应用很广泛的一种技术。 如果你见过下列语法:
TA=Class(TXXX)
...
public
procedure Exec; Overload;
procedure Exec(Value:Boolean); Overload;
end;
一定会知道这是应用了这是重载。但是有没有想过在对象的内部机制中重载是如何实现的呢?
在内部实际上它是一个函数的数组(内部由表来实现)。当调用时会根据参数的不同来决定调用哪一个。
附:
几条数组常用的函数
High(),Low()取数组的上下界