变异记录中的短字符串?

时间:2011-03-31 11:16:23

标签: string delphi properties record variant

我希望能够访问短字符串的部分作为记录的一部分

这样的东西
TMyRecord = record
    case Boolean of
        True: 
        (
            EntireString: String[20];
        );
        False
        (
            StringStart: String[8];
            StringMiddle: String[4];
            StringEnd: String[8];
        );
end;

这是可能的还是我必须单独声明每个字符

TMyRecord = record
    private
        Chars: Array[1..20] of Char;
        Function GetStringStart:String;
        Procedure SetStringStart(Value: String);       
    public
        Property StringStart: String read GetStringStart write SetStringStart; // Can I have properties on a record?
end;

Function GetStringStart: String;
begin
    Result := Chars[1] + Char[2]....;
end;

Procedure SetStringStart(Value: String);   
begin
    for i := 1 to 8 do
    begin
        Chars[i] := Value[i];
    end;
end; 

这可能/值得努力吗?

4 个答案:

答案 0 :(得分:4)

Delphi短字符串不仅包含字符串内容。数据结构中的初始字节包含字符串的长度。这就是短字符串限制为255个字符的原因。

因此,您不能按照提议的方式在变量数组中使用短字符串。

你可以做的是根据getter和setter方法调整你的第二种方法,使其更具可读性。

例如:

function TMyRecord.GetStringStart: string;
begin
  SetString(Result, @Chars[1], 8);
end;

您可能会考虑使用字符串而不是char数组,但如果不确切知道您的基本问题是什么,就很难100%确定该建议。

最后想一想,为什么不解决这个问题呢?存储3个字符串:StartStringMiddleStringEndString。然后使用名为EntireString的getter和setter支持一个属性。当您阅读EntireString时,它会将它从3个单独的部分拼凑在一起,当您写入时,它会拉出各个部分。我怀疑这样会更容易。

答案 1 :(得分:4)

您的第一个示例不考虑长度字节。内存布局如下所示:

case True:
L12345678901234567890
^....................

case False:
L12345678L1234L12345678
^........^....^........

(L =长度字节)。

根据您的要求(例如:部分字符串总是 8,4和8个字符吗?)我会尝试使用系统存储部分字符串并使EntireString成为属性.Copy,StrUtils.LeftStr等。

答案 2 :(得分:3)

ShortString有一个隐含的长度,所以你的第一个例子会将子串的长度部分映射到主字符串的顶部。

您的第二个样本是开始的方式,请注意以下几点:

  • 记录中的属性是可能的
  • 你应该考虑每个子字符串的长度(或者它总是一个20个字符的固定数组?)

修改

这完全取决于你想要的原因,混合字符数组和字符串会让你遇到麻烦,因为字符串可能比数组长度短。

小例子:

program VariantRecordsWithCharactersAndStrings;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  Math;

const
  Size20 = 20;
  Size8 = 8;
  Size4 = 4;
type
  TChar20 = array[0..Size20-1] of Char;
  TChar8 = array[0..Size8-1] of Char;
  TChar4 = array[0..Size4-1] of Char;
  TMyRecord = record
    class var FillCharValue: Byte;
    function GetEntireString: string;
    function GetStringStart: string;
    function GetStringMiddle: string;
    function GetStringEnd: string;
    procedure SetEntireString(const Value: string);
    procedure SetStringStart(const Value: string);
    procedure SetStringMiddle(const Value: string);
    procedure SetStringEnd(const Value: string);
    property EntireString: string read GetEntireString write SetEntireString;
    property StringStart: string read GetStringStart write SetStringStart;
    property StringMiddle: string read GetStringMiddle write SetStringMiddle;
    property StringEnd: string read GetStringEnd write SetStringEnd;
    procedure SetCharArray(const CharArrayPointer: PChar; const CharArraySize: Integer; const Value: string);
    case Boolean of
      True:
      (
          CharFull: TChar20;
      );
      False:
      (
          CharStart: TChar8;
          CharMiddle: TChar4;
          CharEnd: TChar8;
      );
  end;

function TMyRecord.GetEntireString: string;
begin
  Result := CharFull;
end;

function TMyRecord.GetStringStart: string;
begin
  Result := CharStart;
end;

function TMyRecord.GetStringMiddle: string;
begin
  Result := CharMiddle;
end;

function TMyRecord.GetStringEnd: string;
begin
  Result := CharEnd;
end;

procedure TMyRecord.SetEntireString(const Value: string);
begin
  SetCharArray(CharFull, SizeOf(CharFull), Value);
end;

procedure TMyRecord.SetCharArray(const CharArrayPointer: PChar; const CharArraySize: Integer; const Value: string);
begin
  FillChar(CharArrayPointer^, CharArraySize, FillCharValue);
  Move(Value[1], CharArrayPointer^, Min(CharArraySize, SizeOf(Char)*Length(Value)));
end;

procedure TMyRecord.SetStringStart(const Value: string);
begin
  SetCharArray(CharStart, SizeOf(CharStart), Value);
end;

procedure TMyRecord.SetStringMiddle(const Value: string);
begin
  SetCharArray(CharMiddle, SizeOf(CharMiddle), Value);
end;

procedure TMyRecord.SetStringEnd(const Value: string);
begin
  SetCharArray(CharEnd, SizeOf(CharEnd), Value);
end;

var
  MyRecord: TMyRecord;

procedure Dump();
begin
  Writeln(MyRecord.EntireString);
  Writeln(MyRecord.StringStart);
  Writeln(MyRecord.StringMiddle);
  Writeln(MyRecord.StringEnd);
end;

procedure TestWithFillCharValue(const FillCharValue: Byte);
begin
  Writeln('Testing with FillCharValue ', FillCharValue);
  TMyRecord.FillCharValue := FillCharValue;
  MyRecord.EntireString := '123456789001234567890';
  Dump();
  MyRecord.StringStart := 'AAA';
  MyRecord.StringMiddle := 'BBB';
  MyRecord.StringEnd := 'CCC';
  Dump();
end;

begin
  try
    TestWithFillCharValue(0); // this will truncated all the sub arrays when you pass strings that are too short
    TestWithFillCharValue(20); // when using Unicode, this fails even more horribly
    Write('Press <Enter>');
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

这个课程或多或少地符合你的要求:

  • 它有重叠的数据结构
  • 分配数组时:没问题
  • 分配字符串时:请注意字符串变短

答案 3 :(得分:1)

正如其他所述,它不起作用,因为变体大小的记录会在StringStart/StringMiddle/StringEnd类型的中间为EntireString添加一些长度。

您将*char类型的C与pascal shortstring类型混淆。位置[0]有一个隐藏的字符,即shortstring长度。

您可以使用常规字符串类型,然后故意拆分:

procedure StringSplit(const EntireString: string; out StringStart, StringMiddle, StringEnd: string);
begin
  if length(EntireString)<>20 then
    exit;
  StringStart := copy(EntireString,1,8);
  StringMiddle := copy(EntireString,9,4);
  StringEnd := copy(EntireString,13,8);
end;

请注意,out参数类型会在调用函数之前将所有输出String *变量设置为''。

此版本预计会输入20个字符长的整个字符串。

你可以使用短串,但是如果要避免隐藏副本来自/ string[255](当你使用shortstring类型并使用{{}时),可以使用精确长度的自定义类型1}} n&lt; 255):

string[n]