Процедура строкового формата похожа на writeln

С writeln я могу форматировать числа в строку текста.

var 
  file: text;
  mystring: string;

begin
  writeln(file,'Result is: ', var1:8:2,' | ', var2:8:2,' |');
end;

Есть ли аналогичная простая в использовании процедура в Delphi, которая будет обрабатывать аналогичный результат

  _format_string(mystring, 'Result is: ', var1:8:2,' | ', var2:8:2,' |');

Спасибо.

Ответ 1

Вы используете функцию Format:

mystring := Format('The image dimensions are %d × %d.', [640, 480]);

Ответ 2

Технически ответ "да". Но это не рекомендуется.

Вы можете написать собственный драйвер текстового файла на основе System.TTextRec.

Я делал это в прошлом (особенно в эпоху Turbo Pascal) для целей отладки. Это большая работа, и вам нужно написать специальную процедуру MyAssign и обязательно закрыть файл Text, используя TTextRec в блоке try..finally. Громоздкий, но выполнимый.

Более простая альтернатива - с использованием функции Format, описанной Андреасом Рейбрендом.

Прохладная вещь об использовании Format заключается в том, что вы используете Format Strings не только для параметризации таких вещей, как width и precision внутри Format String, но также как такие параметры, как вы, как правило, будут предоставлять значения.

Вы можете приблизиться к использованию width из 8 и a precision из 2, как пример в вашем вопросе.

Например, чтобы процитировать документацию:

Format ('%*.*f', [8, 2, 123.456]);

эквивалентно:

Format ('%8.2f', [123.456]);

Это очень упущенная особенность Format и Format Strings.

Ответ 3

Хотя Jeroen не рекомендует его, я сделал что-то подобное примерно год назад - просто чтобы узнать, как это сделать. Это код:

type
  TTextFile = class
  private type
    TTextRecHelper = record helper for TTextRec
    public
      function GetTextFile: TTextFile;
      procedure SetTextFile(const Value: TTextFile);
      property TextFile: TTextFile read GetTextFile write SetTextFile;
    end;
  private var
    FBuilder: TStringBuilder;
    class function TextClose(var F: TTextRec): Integer; static;
    class function TextIgnore(var F: TTextRec): Integer; static;
    class function TextInput(var F: TTextRec): Integer; static;
    class function TextOpen(var F: TTextRec): Integer; static;
    class function TextOutput(var F: TTextRec): Integer; static;
    procedure AppendString(const Value: string);
    procedure AssignFile(var F: Text);
  public
    var F: Text;
    constructor Create;
    destructor Destroy; override;
    function ToString: string; override;
  end;

constructor TTextFile.Create;
begin
  inherited Create;
  FBuilder := TStringBuilder.Create();
  AssignFile(F);
  Rewrite(F);
end;

destructor TTextFile.Destroy;
begin
  Close(F);
  FBuilder.Free;
  inherited Destroy;
end;

procedure TTextFile.AppendString(const Value: string);
begin
  FBuilder.Append(Value);
end;

procedure TTextFile.AssignFile(var F: Text);
begin
  FillChar(F, SizeOf(F), 0);
  with TTextRec(F)do
  begin
    Mode := fmClosed;
    BufSize := SizeOf(Buffer);
    BufPtr := @Buffer;
    OpenFunc := @TextOpen;
    TextFile := Self;
  end;
end;

class function TTextFile.TextClose(var F: TTextRec): Integer;
begin
  Result := 0;
end;

class function TTextFile.TextIgnore(var F: TTextRec): Integer;
begin
  Result := 0;
end;

class function TTextFile.TextInput(var F: TTextRec): Integer;
begin
  F.BufPos := 0;
  F.BufEnd := 0;
  Result := 0;
end;

class function TTextFile.TextOpen(var F: TTextRec): Integer;
begin
  if F.Mode = fmInput then
  begin
    F.InOutFunc := @TextInput;
    F.FlushFunc := @TextIgnore;
    F.CloseFunc := @TextIgnore;
  end else
  begin
    F.Mode := fmOutput;
    F.InOutFunc := @TextOutput;
    F.FlushFunc := @TextOutput;
    F.CloseFunc := @TextClose;
  end;
  Result := 0;
end;

class function TTextFile.TextOutput(var F: TTextRec): Integer;
var
  AStr: AnsiString;
begin
  SetLength(AStr, F.BufPos);
  Move(F.BufPtr^, AStr[1], F.BufPos);
  F.TextFile.AppendString(string(AStr));
  F.BufPos := 0;
  Result := 0;
end;

function TTextFile.ToString: string;
begin
  Close(F);
  result := FBuilder.ToString;
  Rewrite(F);
end;

function TTextFile.TTextRecHelper.GetTextFile: TTextFile;
begin
  Move(UserData[1], Result, Sizeof(Result));
end;

procedure TTextFile.TTextRecHelper.SetTextFile(const Value: TTextFile);
begin
  Move(Value, UserData[1], Sizeof(Value));
end;

Пример того, как использовать его в соответствии с вашим вопросом:

  tf := TTextFile.Create;
  try
    Writeln(tf.F, 'Result is: ', var1:8:2,' | ', var2:8:2,' |');
    Caption := tf.ToString;
  finally
    tf.Free;
  end;

Ответ 4

Примечание о Writeln

Writeln(file, 'Result is: ', var1:8:2,' | ', var2:8:2,' |');

Вывод:

Result is:     4.50 |     0.67 |

Кажется, что Delphi делает старое форматирование Pascal без соблюдения DecimalSeparator. Вот почему вывод Writeln использует ., а мои другие подходы ниже используют , (у меня есть испанский вариант Windows).

TStringBuilder

В современных версиях Delphi TStringBuilder обеспечивает элегантный способ конкатенации строк с поддержкой Свободные интерфейсы. Он имеет ограниченные возможности форматирования, но включает в себя аромат Format (который, как обычная функция Format, очень полезен, но не имеет проверки типа):

sb := TStringBuilder.Create;
try       
  sb.Append('Result is: ').Append(var1).Append(' | ').Append(var2).Append(' |');
  Memo.Lines.Add(sb.ToString);
  sb.Clear;
  sb.AppendFormat('Result is: %8.2f | %8.2f |', [var1, var2]);
  Memo.Lines.Add(sb.ToString);
finally                     
  sb.Free;
end;

Вывод:

Result is: 4,5 | 0,666666666666667 |
Result is:     4,50 |     0,67 |

Оператор вставки

Используя некоторые трюки, такие как перегрузка оператора и закрывает, можно имитировать С++ Оператор вставки vstream:


Memo.Lines.Add(stringout < 'My ' < 5 < ' cents' < soEndl < '2/3: ' < soPrec(4) < 2/3);

Вывод:

My 5 cents  
2/3: 0,6667

Ваш пример:

Memo.Lines.Add(
  stringout
    < 'Result is: ' < soWidth(8) < soPrec(2) < var1 < ' | '
    < soWidth(8) < soPrec(2) < var2 < ' |'
);

Вывод:

Result is:     4,50 |     0,67 |

Когда Delphi поддерживает перегрузку операторов в классах, реализация будет намного более чистой. Между тем, использование записей для перегрузки оператора и интерфейсов для автоматического управления памятью делает трюк:

type
  PStringOut = ^TStringOut;

  TStringOutManipulatorRef = reference to procedure(pso: PStringOut);

  PStringOutInternalStorage = ^TStringOutInternalStorage;

  TStringOutInternalStorage = record
    Data: TStringBuilder;
    Width, Precision: integer;
    procedure ClearFormat; inline;
    function GetFormatString(formatType: char): string;
  end;

  IStringOutInternal = interface
    function TheStorage: PStringOutInternalStorage;
  end;

  TStringOutInternal = class(TInterfacedObject, IStringOutInternal)
  strict private
    Storage: TStringOutInternalStorage;
  private
    constructor Create;
    function TheStorage: PStringOutInternalStorage;
  public
    destructor Destroy; override;
  end;

  TStringOut = record
  private
    Buffer: IStringOutInternal;
  public
    // insertion operator
    class operator LessThan(const this: TStringOut; add: string): TStringOut;
    class operator LessThan(const this: TStringOut; add: char): TStringOut;
    class operator LessThan(const this: TStringOut; add: integer): TStringOut;
    class operator LessThan(const this: TStringOut; add: double): TStringOut;
    class operator LessThan(const this: TStringOut; manipulator: TStringOutManipulatorRef): TStringOut; inline;

    // implicit conversion to string ("extraction" operator)
    class operator Implicit(const this: TStringOut): string; inline;
  end;

{ TStringOutInternalStorage }

procedure TStringOutInternalStorage.ClearFormat;
begin
  Width := 0;
  Precision := 0;
end;

function TStringOutInternalStorage.GetFormatString(formatType: char): string;
begin
  Result := '%';
  if Width > 0 then
    Result := Result + IntToStr(Width);
  if Precision > 0 then
    Result := Result + '.' + IntToStr(Precision);
  Result := Result + formatType;
end;

{ TStringOutInternal }

constructor TStringOutInternal.Create;
begin
  inherited;
  Storage.Data := TStringBuilder.Create;
end;

destructor TStringOutInternal.Destroy;
begin
  Storage.Data.Free;
  inherited;
end;

function TStringOutInternal.TheStorage: PStringOutInternalStorage;
begin
  Result := @Storage;
end;

{ TStringOut }

class operator TStringOut.Implicit(const this: TStringOut): string;
begin
  Result := this.Buffer.TheStorage.Data.ToString;
end;

class operator TStringOut.LessThan(const this: TStringOut; add: string): TStringOut;
begin
  this.Buffer.TheStorage.Data.AppendFormat(this.Buffer.TheStorage.GetFormatString('s'), [add]);
  this.Buffer.TheStorage.ClearFormat;
  Result.Buffer := this.Buffer;
end;

class operator TStringOut.LessThan(const this: TStringOut; add: char): TStringOut;
begin
  this.Buffer.TheStorage.Data.Append(add);
  this.Buffer.TheStorage.ClearFormat;
  Result.Buffer := this.Buffer;
end;

class operator TStringOut.LessThan(const this: TStringOut; add: integer): TStringOut;
begin
  this.Buffer.TheStorage.Data.AppendFormat(this.Buffer.TheStorage.GetFormatString('d'), [add]);
  this.Buffer.TheStorage.ClearFormat;
  Result.Buffer := this.Buffer;
end;

class operator TStringOut.LessThan(const this: TStringOut; add: double): TStringOut;
var
  s: PStringOutInternalStorage;
begin
  s := this.Buffer.TheStorage;

  if s.Precision <> 0
  then s.Data.AppendFormat(s.GetFormatString('f'), [add])
  else s.Data.AppendFormat(s.GetFormatString('g'), [add]);

  s.ClearFormat;
  Result.Buffer := this.Buffer;
end;

class operator TStringOut.LessThan(const this: TStringOut; manipulator: TStringOutManipulatorRef): TStringOut;
begin
  Result := this;
  manipulator(@Result);
end;

{ Manipulators }

function soEndl: TStringOutManipulatorRef;
begin
  Result :=
    procedure(pso: PStringOut)
    begin
      pso.Buffer.TheStorage.Data.AppendLine;
      pso.Buffer.TheStorage.ClearFormat;
    end;
end;

function soWidth(value: integer): TStringOutManipulatorRef;
begin
  Result :=
    procedure(pso: PStringOut)
    begin
      pso.Buffer.TheStorage.Width := value;
    end;
end;

function soPrec(value: integer): TStringOutManipulatorRef;
begin
  Result :=
    procedure(pso: PStringOut)
    begin
      pso.Buffer.TheStorage.Precision := value;
    end;
end;

{ The stringout "constructor" }

function stringout: TStringOut; inline;
begin
  Result.Buffer := TStringOutInternal.Create;
end;