【问题标题】:String format procedure similar to writeln类似于 writeln 的字符串格式化过程
【发布时间】:2013-08-02 08:54:00
【问题描述】:

使用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,' |');

谢谢。

【问题讨论】:

  • 确切的答案是,它是语言本身的一个特性(即神圣标准帕斯卡)。没有语法允许我们(程序员)编写这样的子程序。
  • 您必须考虑的一件事是,从 Delphi 2009 开始,WriteLnWriteLn 使用单字节 AnsiStringFormat 等是 UnicodeString

标签: string file delphi format


【解决方案1】:

关于 Writeln 的注意事项

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

输出:

Result is:     4.50 |     0.67 |

似乎 Delphi 执行旧的 Pascal 格式而不尊重 DecimalSeparator。这就是为什么Writeln 输出使用. 而我下面的其他方法使用, 的原因(我有一个Spanish 版本的Windows)。

TStringBuilder

在现代 Delphi 版本中,TStringBuilder 提供了一种优雅的字符串连接方式,支持fluent interfaces。它的格式化功能有限,但包含 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 |

插入操作符

使用运算符重载和closures等一些技巧,可以模仿C++ostream insertion operator


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;

【讨论】:

  • +1 表示DecimalSeparator 观察,以及 C++ ostream 模拟。
【解决方案2】:

虽然 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;

【讨论】:

  • +1;做得好。让我想起了我用 Turbo Pascal 编写的代码。尽管没有try...finally,但使用object 样式类(:
【解决方案3】:

从技术上讲,答案是“是”。但不推荐。

您可以根据System.TTextRec 类型编写自己的text file device driver

我在过去(尤其是在 Turbo Pascal 时代)出于调试目的这样做过。这是很多工作,并且需要您编写一个特殊的MyAssign 过程,并确保在try..finally 块中使用TTextRec 关闭Text 文件。麻烦,但可行。

一个更简单的选择是using the Format function as described by Andreas Rejbrand

使用Format 很酷的一点是,您使用Format Strings 不仅可以参数化widthprecisionFormat String 内部的内容,而且还可以像您通常提供的参数一样提供值。

您可以非常接近使用 8 的 width 和 2 的 precision,就像您问题中的示例一样。

例如,引用文档:

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

相当于:

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

这是FormatFormat Strings 的一个很容易被忽视的特性。

【讨论】:

  • 甜——从来不知道。
  • 不错。与您可以在 Python 中执行的某些格式(IMV)相比仍然相形见绌。
  • @David:你可以在 Python 中做哪些很棒的格式化?
【解决方案4】:

你使用Format函数:

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

【讨论】:

    猜你喜欢
    • 2021-08-21
    • 1970-01-01
    • 2014-08-24
    • 2018-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-10
    • 2010-11-18
    相关资源
    最近更新 更多