Короткие строки в записи варианта? - PullRequest
2 голосов
/ 31 марта 2011

Я хотел бы иметь возможность получить доступ к разделам короткой строки как части записи

Что-то вроде

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 голосов
/ 31 марта 2011

Ваш первый образец не учитывает длину байта. Расположение памяти выглядит так:

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

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

(L = длина байта).

В зависимости от ваших требований (например: являются ли частичные строки всегда 8, 4 и 8 символов?) Я бы попытался сохранить частичные строки и сделать EntireString свойством, используя System.Copy, StrUtils.LeftStr и т. Д.

4 голосов
/ 31 марта 2011

Короткая строка Delphi содержит больше, чем просто содержимое строки. Начальный байт в структуре данных содержит длину строки. Вот почему короткие строки ограничены 255 символами.

Таким образом, вы не можете использовать короткие строки в вашем массиве вариантов так, как вы предлагаете.

Что вы можете сделать, так это адаптировать свой второй подход, основанный на методах получения и установки, чтобы он был немного более читабельным.

Например:

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

Вы можете рассмотреть возможность использования строки, а не массива char, но немного сложно быть на 100% уверенным в этом совете, не зная точно, в чем заключается ваша основная проблема.

В качестве последней мысли, почему бы не перевернуть проблему? Сохраните 3 строки: StartString, MiddleString и EndString. Затем добавьте свойство с помощью метода get и set с именем EntireString. Когда вы читаете EntireString, он разбивает его на части из 3 отдельных частей, а когда вы пишете в него, он вытягивает отдельные части. Я подозреваю, что так будет проще.

3 голосов
/ 31 марта 2011

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.

Этот класс делает более или менее то, что вы хотите:

  • имеет перекрывающиеся структуры данных
  • при назначении массивов: нет проблем
  • при назначении строк: знать, когда строки становятся короткими
1 голос
/ 31 марта 2011

Как уже говорилось, это не сработает, потому что запись размером с вариант добавляет несколько длин для StringStart/StringMiddle/StringEnd в середине типа EntireString.

Вы путаете *charтип С с типом паскаль 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;

Обратите внимание, что *Тип параметра 1014 * установит все выходные переменные String * в '' перед вызовом функции.

В этой версии ожидается ввод всей строки длиной 20 символов.

Вы можете использовать короткие строки, но спользовательские типы точной длины, если вы хотите избежать скрытых копий от / до string[255] (которые появляются, когда вы используете тип shortstring и работаете с string[n] с n <255): </p>

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;
...