关于动态数组与 SetLength() 函数(50分)

3

3h

Unregistered / Unconfirmed
GUEST, unregistred user!
有如下声明一个动态数组类型:
TRe = Record
a : Integer;
b : String;
c : Boolean;
e : Real;
end;
TMoreRe = Array of TRe;
声明变量:
FR : TRe;
FMM : TMoreRe;
由于数组是动态的,所以开始时
SetLength(FMM, 0);
实际上它的数组项也是不确定的,这也是使用动态数组的重要原因。
现在的问题是,在我取得某一个 FR 之后要加入 FMM 中,即使将
FMM 的边界确定也无法得到应该放 FR 的地方:
SetLength(FMM, N+1);
FL := Low(FMM);
FH := High(FMM);
究竟新的空间在 FMM[Low] 还是在 FMM[High]?用循环判断是否为
NIL 的方法是否有保障?还有其它什么方法?

我查过帮助,帮助中对 SETLENGTH 没有什么说明,很简单,根本
无法帮到我什么。
看离线包也没什么结果。
 
呵呵, 新空间当然是High(FMM)了, 比如, N=0
SetLength(FMM, N+1)=SetLength(FMM, 1)
数组长1
FL=0
FH=0
如果N=10
SetLength(FMM, N+1)=SetLength(FMM, 11)
数组长11
FL=0
FH=10

 
为什么是“<Font color="red"><b>当然</b></font>”呢?放在 LOW 或者插在中间也是可以的话?
 
如果要将fr加入fmm:
setlength(fmm, length(fmm)+1);
move(@fr, @(fmm[high(fmm)]), sizeof(TRe));
 
果然是用另一只眼看事物。
MOVE的用法我想去学习一下,而且正为加入节点的事操心呢。:)
 
增加容易, 插入比较麻烦.
得分三步走. 1. setlength扩大数组, 2. 用move移动插入点及以后的数据, 3.在插入点添入
新数据.
procedure InsertIntoArray(arr: array of fmm
NewItem: fr
Index: Integer);
begin
if index < 0 then index := 0;
if index > length(arr) then index := length(arr);
setlength(arr, length(arr)+1);
move(@(arr[index]), @(arr[index+1]), sizeof(fr)*(high(arr)-index));
move(@fr, @(arr[index]), sizeof(fr));
end;
 
分数我要呀,刚才机器被人借了,又不高兴玩VFP,所以翻译了文章,正好有用:

Delphi 4 语言增强

by Dr. Bob

Translated by CJ


--------------------------------------------------------------------------------

Inprise 公司的 Borland Delphi 4 在 Object Pascal 语言方面进行了一系列增强。像往常一样,在这篇文章里,我会解释一个十分艰巨的改进,那是从开放试参数(Open Parameters)和长字符串(Long String)回到基本的数组的动态数组(Dymantic Array)。

Dynamic Arrays
在 Delphi 4,我们除了可以像往常一样说明静态数组外:

X: Array[1..42] of String;//传统的静态数组


我们现在可以说明动态数组。动态数组指定类型信息(维数和元素类型)但不是元素的数量,因此:


X: Array of String;

M: Array of Array of Integer;

定义了两个动态数组。X是字符串类型的一维数组,M是一个整数类型的二维数组 (像一个 矩阵)。

动态数组没有一个固定大大小或者长度。取而代之,当执行SetLength过程或者给其赋值时,会为这个动态数组(重新)分配内存。因此,以上对X和M的定义并没有分配内存。在内存中建立数组时,调用SetLength过程。例如对于以上的说明:


SetLength(X, 42);

分配了42个字符串的数组,下标从0 到 41。动态数组总是整数为下标的且总是从0开始。

在调用SetLength以后, 以前动态数组中的内容(如果有)也被随之复制i(所以,即便我们经常增加或减小数组的长度,数据也不会丢失)。使用以上知识,我们可以写一个小,当然是低效率的程序,用来读多行文本文件的内容,并且只分配需要数量的字符串。


{$R+}
{$APPTYPE CONSOLE}
var
X: Array of String;
i: Integer;
begin
while not eof do
begin
SetLength(X,Length(X)+1)
// very inefficient...
readln(X[High(X)])
end;
for i:=0 to High(X) do writeln(X)
end.

动态数组变量是隐含的指针,并且被长字符串那样的reference-counting技术所管理。释放动态数组,赋给动态数组一个Nil值,或使用Fiinalize过程。这两种方法清除数组,前提是没有其它引用存在。


{$R+}
program Delphi4;
{$APPTYPE CONSOLE}
uses
Dialogs;
var
X,Y: Array of String;
i: Integer;
begin
SetLength(X, 7);
Y := X;
X[3] := 'Dynamic Arrays in Delphi 4';
SetLength(X, 42);
Y := X;
SetLength(Y, 4);
ShowMessage(Y[3]);
X := nil;
Finalize(Y);
end.


警告: 我们不能对一个动态数组变量使用使用操作符^ 用在New和Dispose过程。

如果X和Y是同一动态数组类型的变量,X:=Y分配给X和Y同样的长度,并把X指向Y的同一个数组。不想字符串,数组在它们被写入前是不会自动被复制的,但是,它们保持指向相同处—共享内存区!例如,在以下代码被执行:


var
X,Y: array of String;
begin
SetLength(X, 1);
X[0] := 'Hello, world';
Y := X;
Y[0] := 'Answer';
end;

X[0]的值是 'Answer'。

长字符串会在我们改变其中一个时,他们“分裂开来”(建立独立的拷贝),而动态数组保持指向相同的区域。这也许会有些遗憾,不过,至少我们不会像长字符串那样在性能上有所损失...
当然,由于动态数组的内容会在我们调用SetLength时被复制,它也会(在调用SetLength时)建立一个独立的拷贝。

对动态数组下标赋值(例如 X[42] := 'Answer')不会从新分配内存(如果要那样,我们需要调用SetLength)。下标越界在编译时不会被发现,但是会在运行期间产生异常。 (使用 $R+ 编译指令)。

当动态数组变量比较时,其引用被比较,不是其数组的值。所以,以下代码执行后:


var
X, Y: array of String;
begin
SetLength(X,1);
SetLength(Y,1);
X[0] := 'Hello, world!';
Y[0] := 'Hello, world!';
end;

X = Y 返回 False 但是 X[0] = Y[0] 返回 True。

截断动态数组,把自身传递给Copy函数,然后再把返回结果赋予自身。例如,如果X是一个动态数组, X := Copy(X, 0, 2)只保留前三个元素,其余均被截断。

一旦动态数组被分配内存,我们可以把它传递到标准函数Length,High,Low中。Length返回数组中元素的个数;High返回数组中可能最大的下标(Length - ); Low永远返回0。一个长度为0的数组High返回-1,所以,在那种情况下,High(X) <Low(X)。

为了展示多维动态数组M (参见本页最上面的说明部分)我们需要调用有两个整数类型参数的 SetLength:


SetLength(M,10,5);

分配了一个10*5的数组,M[9,4] 为其中的一个元素。

我们也可以建立非矩形的多维动态数组。 首先我们要调用SetLength,其参数为数组第一维的数字n,例如:


var
M: array of array of Integer;
begin
SetLength(M,10);

为M分配了10行,但是没有分配列。然后,我们可以每次分配一列(给其不同的长度)。例如:


SetLength(M[2], 42);

使第三列有42个整数类型长。在这里(即使其它列还未被分配)我们可以为第三列赋值。例如:M[2,41] := 7。


--------------------------------------------------------------------------------

This webpage &amp;#169
1999 by webmaster drs. Robert E. Swart (aka Dr.Bob - www.drbob42.com). All Rights Reserved.
 
象你这样的问题用TList对象解决不是很好吗?
何必一定要动态数组搞得这么复杂。
 
呵呵, 看来是我会错意了, 根据CJ的说明,也可以这样,

procedure InsertIntoArray(arr: array of fmm
NewItem: fr
Index: Integer);
begin
if index < 0 then index := 0;
if index > length(arr) then index := length(arr);
setlength(arr, length(arr)+1);
arr[length(arr)]:=fr;
arr:=copy(arr,0,index-1)+copy(arr,length(arr),length(arr))+copy(arr,index+1,length(arr)-1);
end;

 
cytown并没有会错意,我的原意的确是那样。 :)

下面让我们来讨论一上,象LOG所说,用LIST。
据我所知,象 TRe 这样的记录,甚至更复杂的,用LIST好还是用动态数组好呢?
我是指从效率和编程两方面来考虑。
 
对你的例子,我倾向于用list管理,
1、可以当成数组用
2、可以动态变化
3、定位方便
4、减少内存的复制过程
 
效率来说当然是叔祖好了, 但占内存会很多.
方便吗, 都差不多.
 
实际上实现技术应该是一样的吧。
另:内存应该是动态分配的,其内容会被复制,所以,新的内存在HIGH的说法不妥当
不过,的确,如果要加数据,应该往HIGH里加
 
另一只眼的 @ 有问题,去了两个 @ Move才能成功,WHY?
估计类本身就是指针吧。
 
哦, 明白了.
@只能用于简单变量.
 
呵呵,COPY行吗?
 
新问题来了请看程序:
procedure TForm1.Button4Click(Sender: TObject);
Var
fr : ^TfileReco;
ni : tlistitem;
i : Integer;
begin
hhhftp1.FindFiles;
showmessage(inttostr(hhhftp1.FilesCount));
listview1.Items.Clear;
if hhhftp1.FilesCount <= 0 then
exit;
for i := low(hhhftp1.FileList) to high(hhhftp1.FileList) do
begin
fr := @(hhhftp1.FileList);
ni := listview1.Items.Add;
ni.Caption := fr^.SFileName;
ni.ImageIndex := -1;
ni.SubItems.Add(inttostr(fr^.FileSize));
ni.SubItems.Add(getmod(fr^.FileDateTime));
ni.SubItems.Add(inttostr(fr^.FileAttrib));
fr := Nil;
end;
end;

这个HHHFTP是我刚学写的一个控件,其中:
type
TFileReco = Record
SFileName, SFileFullName : String;
FileSize : Integer;
FileAttrib : Integer;
FileDateTime : TFileTime;
IsDir : Boolean;
end;

TFilesReco = Array of TFileReco;

public
Procedure FindFiles;
Property FileList: TFilesReco Read FFiles;
还有:
Procedure THHHFTP.FindFiles;
Function W32FDtoReco(Const W32FD: TWin32FindData) : TFileReco;
Var
Reco : TFileReco;
FN : String;
begin
With Reco do
begin
FN := CustomToFileName(W32FD.cFileName);
SFileName := FN;
SFileFullName := 'FULL:' + FN
// @#$@#$@#$
FileDateTime := W32FD.ftLastWriteTime;
FileAttrib := W32FD.dwFileAttributes;
FileSize := (W32FD.nFileSizeHigh * MAXDWORD) + W32FD.nFileSizeLow;
IsDir := ((FileAttrib and faDirectory) <> 0);
end;
Result := Reco;
end;
Function AddToArray(Item: TFileReco
Var List: TFilesReco) : Integer;
Var
RHigh : Integer;
begin
Result := Length(List) + 1;
SetLength(List, Result);
RHigh := High(List);
Move(Item, List[RHigh], SizeOf(TFileReco));
end;
Var
FindData : TWin32FindData;
FindHandle : HInternet;
begin
// SetLength(FFiles, 0);
FFiles := Nil;
FFilesCount := 0;
FindHandle := FTPFindFirstFile(FFTPHandle, '*.*', FindData, 0, 0);
If FindHandle = nil then
Exit;
FFilesCount := AddToArray(W32FDtoReco(FindData), FFiles);
While InternetFindNextFile(FindHandle, @FindData) do
FFilesCount := AddToArray(W32FDtoReco(FindData), FFiles);
InternetCloseHandle(FindHandle);
end;

现在的问题是:<font color="red">
1、第一次按BTN4时没事,再按一次会提示“非法指针操作”,WHY?
2、为什么在AddToArray函数中的ITEM中的文件名不能正确地复制给LIST?</font>

这个问题我愿意加分200,就不知道如何加进去,YYSUN又不在,大家可有什么良策?
 
1. 进入时button.enabled := false
然后再进行其他操作, 推出时button.enabled :=true;
我估计是你第一次操作还未完成时就又按了button.

2. TFileReco = Record
SFileName, SFileFullName : String;
^^^^^^^^^^
....
end;
你需要记住一点, SFileName, SFileFullName的具体内容并不在record中, record中的
只是一个指向具体字符串的指针. 因此你用move, 无法复制具体的字符串, 只是把这个指针复制
到新的record中. 一旦原record不再使用而释放了, 那么新record中的字符串当然也没有了.
 
1)
尽管我觉得并不是BTN重按的原因,但我依然愿意试一试。不过手头没有DELPHI,等回家再说;

2)
看来只能用 List[RHigh]:=Item 了,这样应该没问题吧。Let me TRY! TRY! TRY!
 
2. 有一个简单的方法:
TFileReco = Record
SFileName, SFileFullName : String[254];
.....

空间浪费了点, 如果字串确定不会很大可以用string[32] or string[64];
这样, 这个string就保存在record中了.
 

Similar threads

回复
0
查看
681
不得闲
S
回复
0
查看
953
SUNSTONE的Delphi笔记
S
顶部