2

レコードの一部として短い文字列のセクションにアクセスできるようにしたい

何かのようなもの

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

4 に答える 4

4

最初のサンプルでは、​​長さのバイトは考慮されていません。メモリ レイアウトは次のようになります。

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

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

(L = 長さバイト)。

要件に応じて (例: 部分文字列は常にEntireString8、4、8 文字ですか?) System.Copy、StrUtils.LeftStr などを使用して、部分文字列を格納し、プロパティを作成してみます。

于 2011-03-31T11:34:39.483 に答える
4

Delphi の短い文字列には、文字列の内容だけではありません。データ構造の最初のバイトには、文字列の長さが含まれます。これが、短い文字列が 255 文字に制限されている理由です。

したがって、提案した方法でバリアント配列に短い文字列を使用することはできません。

あなたができることは、getter メソッドと setter メソッドに基づいて 2 番目のアプローチを適応させ、もう少し読みやすくすることです。

例えば:

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

char 配列ではなく文字列を使用することを検討することもできますが、根本的な問題が何であるかを正確に知らずに、そのアドバイスを 100% 確信することは少し困難です。

最後に、問題を好転させてみませんか? StartStringMiddleStringおよびの3 つの文字列を格納しEndStringます。次に、 と呼ばれるゲッターとセッターに裏打ちされたプロパティを用意しEntireStringます。読むときEntireStringは3つのパーツをバラバラにして、書き込むときはパーツを引き出します。その方が簡単だと思います。

于 2011-03-31T11:33:31.997 に答える
3

ShortString暗黙の長さがあるため、最初の例では、メイン文字列の上にサブ文字列の長さ部分をマップします。

2 番目のサンプルは、これらのメモを使用して開始する方法です。

  • レコードのプロパティが可能
  • 各部分文字列の長さを考慮する必要があります (または、常に 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.

このクラスは多かれ少なかれあなたが望むことをします:

  • データ構造が重複しています
  • 配列を割り当てるとき:問題ありません
  • 文字列を割り当てるとき:文字列が短くなったときに注意してください
于 2011-03-31T11:34:10.077 に答える
1

StringStart/StringMiddle/StringEnd他の人が述べたように、バリアントサイズのレコードはタイプの途中で長さを追加するため、機能しませんEntireString

*charCの型とパスカル型を混同してい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 文字の長さの文字列全体を入力する必要があります。

短い文字列を使用できますが、正確な長さのカスタム型を使用して、隠しコピーを避けたい場合(型を使用して n<255で作業string[255]する場合に発生します):shortstringstring[n]

type 
  String20 = string[20];
  String4 = string[4];
  String8 = string[8];

procedure StringSplit(const EntireString: String20; out StringStart: String8;
            out StringMiddle: String4; out StringEnd: String8);
begin
  if length(EntireString)<>20 then
    exit;
  StringStart := copy(EntireString,1,8);
  StringMiddle := copy(EntireString,9,4);
  StringEnd := copy(EntireString,13,8);
end;
于 2011-03-31T12:15:47.860 に答える