我不经常与Pascal合作,因此,如果这个问题很简单,我深表歉意。我正在研究一个二进制文件程序,该程序将一组定制记录写入二进制文件。
最终我希望它能够将不同自定义记录类型的多个数组写入一个二进制文件。
由于这个原因,我以为我会先写一个整数,即下一个数组的总字节数。然后我写数组本身。然后,我可以读取第一个整数类型的块-告诉我要直接读入数组的下一个块的大小。
例如-编写二进制文件时,我将执行以下操作:
assignfile(f,MasterFileName);
{$I-}
reset(f,1);
{$I+}
n := IOResult;
if n<> 0 then
begin
{$I-}
rewrite(f);
{$I+}
end;
n:= IOResult;
If n <> 0 then
begin
writeln('Error creating file: ', n);
end
else
begin
SetLength(MyArray, 2);
MyArray[0].ID := 101;
MyArray[0].Att1 := 'Hi';
MyArray[0].Att2 := 'MyArray 0 - Att2';
MyArray[0].Value := 1;
MyArray[1].ID := 102;
MyArray[1].Att1:= 'Hi again';
MyArray[1].Att2:= MyArray 1 - Att2';
MyArray[1].Value:= 5;
SizeOfArray := sizeOf(MyArray);
writeln('Size of character array: ', SizeOfArray);
writeln('Size of integer var: ', sizeof(SizeOfArray));
blockwrite(f,sizeOfArray,sizeof(SizeOfArray),actual);
blockwrite(f,MyArray,SizeOfArray,actual);
Close(f);
然后您可以使用以下内容重新读取文件:
Assign(f, MasterFileName);
Reset(f,1);
blockread(f,SizeOfArray,sizeof(SizeOfArray),actual);
blockread(f,MyArray,SizeOfArray,actual);
Close(f);
这样的想法是,在读取完这些块之后,您可以记录一个新的整数,然后保存一个新的数组,等等。
它读取记录中的整数部分,但不读取字符串。记录将是这样的:
TMyType = record
ID : Integer;
att1 : string;
att2 : String;
Value : Integer;
end;
任何帮助都感激不尽!
答案 0 :(得分:1)
TMyType = record
ID : Integer;
att1 : string; // <- your problem
将该字段att1声明为字符串,意味着记录包含指向实际字符串数据的指针(att1实际上是一个指针)。编译器管理此指针和关联数据的内存,并且字符串可以是任何(合理的)长度。
对您来说,快速解决方案是声明att1之类的内容,例如string[64]
:一个字符串,最大长度为64个字符。这将消除指针,并使用记录的内存(att1字段本身,现在是一个特殊的静态数组)作为字符串字符的缓冲区。当然,声明字符串的最大长度可能会有些危险:如果您尝试为字符串分配的字符串太长,则会被截断。
要真正完整:它取决于编译器;有些人有一个使您的声明“ string”可用的开关,使其成为“ string [255]”的别名。这不是默认值。还请考虑使用string [...]会更快并且浪费内存。
答案 1 :(得分:1)
您有一些错误。
MyArray
是动态数组,是引用类型(指针),因此SizeOf(MyArray)
是指针的大小,而不是数组的大小。要获取数组的长度,请使用Length(MyArray)
。
但是更大的问题是保存长字符串(AnsiString
– string
映射到的常见类型–,WideString
s,UnicodeString
s)。这些也是引用类型,因此您不能只将它们与记录一起保存。您将必须一一保存记录的各个部分,而对于字符串,则必须使用类似以下的函数:
procedure SaveStr(var F: File; const S: AnsiString);
var
Actual: Integer;
Len: Integer;
begin
Len := Length(S);
BlockWrite(F, Len, SizeOf(Len), Actual);
if Len > 0 then
begin
BlockWrite(F, S[1], Len * SizeOf(AnsiChar), Actual);
end;
end;
当然,您通常应检查Actual
并进行适当的错误处理,但为简单起见,我将其省略。
回读类似:首先读取长度,然后使用SetLength
将字符串设置为该大小,然后读取其余部分。
现在,您可以执行以下操作:
Len := Length(MyArray);
BlockWrite(F, Len, SizeOf(Len), Actual);
for I := Low(MyArray) to High(MyArray) do
begin
BlockWrite(F, MyArray[I].ID, SizeOf(Integer), Actual);
SaveStr(F, MyArray[I].att1);
SaveStr(F, MyArray[I].att2);
BlockWrite(F, MyArray[I].Value, SizeOf(Integer), Actual);
end;
// etc...
请注意,我目前无法测试代码,因此可能会有一些小错误。如果有必要,我将在以后访问编译器时尝试。
正如Marco van de Voort所说,您可能需要做:
rewrite(f, 1);
而不是简单的
rewrite(f);
但是,正如我回答的那样,请使用流。它们更易于使用(IMO),并提供更一致的界面,无论您尝试写或读的是什么。有许多用于各种I / O的流,并且所有流都源自同一基本抽象TStream类(并因此与之兼容)。