如何解决将char分配给string时的W1047/W1068警告?

How to solve W1047/W1068 warnings when assigning char to string?

我有一个将数字转换为字符串的简单函数(类似于 Excel 列号 1..n 可以转换为列名 A-ZZZ 的方式)。由于我经常使用它,我已经删除了局部变量作为速度优化并且只直接使用 Result

当我编译时,我收到了这 2 个警告:

W1047 Unsafe code 'String index to var param'

W1068 Modifying strings in place may not be supported in the future

这是缩短的函数:

function Idx2Code_(Idx: integer): string;
begin
  SetLength(Result, 3); // init Result size
  if Idx <= 26 then
  begin
    // single char: A-Z
    Result := Chr(64 + Idx);
    SetLength(Result, 1);
  end
  else if Idx <= 676 then
  begin
    // 2 chars codes: BA-ZZ
    Result[1] := Chr((Idx div 26) + 64 + 1); // <- warnings for this line
    Result[2] := Chr((Idx mod 26) + 64);     // <- warnings for this line
    SetLength(Result, 2);
  end
  else
  begin
    // 3 chars codes: BAA-ZZZ
    Result[1] := Chr((Idx div 676) + 64 + 1);            // <- warnings for this line
    Result[2] := Chr(((Idx mod 676) div 26) + 64 + 26);  // <- warnings for this line
    Result[3] := Chr((Idx mod 26) + 64 + 26);            // <- warnings for this line
  end;
end;

我可以使用局部变量,但速度较慢,这是我不想要的。

我不想使用在未来的 Delphi 版本中可能会变成 "obsolete" 的代码。

如何解决警告,同时尽可能保持简单和快速?

在移动开发中,Delphi的string类型的直接索引默认是0-based(这可以通过{$ZEROBASEDSTRINGS OFF}指令)。

在桌面开发中,直接索引默认情况下仍然是1-based,但可能将来会变成0-based。

由于平台之间的这种差异,string 的直接索引不鼓励向前发展,因此警告消息。为避免这种情况,所有平台都提供基于 0 的 TStringHelper.Chars[] 属性。然而,这个 属性 是 只读的,所以你不能用它来修改 string 的内容(有一次,Embarcadero 正在考虑制作 string不可变的,但后来决定反对,但警告仍然存在)。

在这种情况下,您必须:

  1. 禁用警告,并使用适合平台的索引。您可以使用 Low(string) 来帮助您:

    {$WARN UNSAFE_CODE OFF}
    {$WARN IMMUTABLE_STRINGS OFF}
    function Idx2Code_(Idx: integer): string;
    begin
      if Idx <= 26 then
      begin
        // single char: A-Z
        SetLength(Result, 1);
        Result[Low(string)] := Chr(64 + Idx);
      end
      else if Idx <= 676 then
      begin
        // 2 chars codes: BA-ZZ
        SetLength(Result, 2);
        Result[Low(string)] := Chr((Idx div 26) + 64 + 1);
        Result[Low(string)+1] := Chr((Idx mod 26) + 64);
      end
      else
      begin
        // 3 chars codes: BAA-ZZZ
        SetLength(Result, 3);
        Result[Low(string)] := Chr((Idx div 676) + 64 + 1);
        Result[Low(string)+1] := Chr(((Idx mod 676) div 26) + 64 + 26);
        Result[Low(string)+2] := Chr((Idx mod 26) + 64 + 26);
      end;
    end;
    {$WARN IMMUTABLE_STRINGS DEFAULT}
    {$WARN UNSAFE_CODE DEFAULT}
    
  2. 使用 TStringBuilder:

    uses
      System.SysUtils;
    
    function Idx2Code_(Idx: integer): string;
    var
      SB: TStringBuilder;
    begin
      SB := TStringBuilder.Create(3);
      try
        if Idx <= 26 then
        begin
          // single char: A-Z
          SB.Append(Chr(64 + Idx));
        end
        else if Idx <= 676 then
        begin
          // 2 chars codes: BA-ZZ
          SB.Append(Chr((Idx div 26) + 64 + 1));
          SB.Append(Chr((Idx mod 26) + 64));
        end
        else
        begin
          // 3 chars codes: BAA-ZZZ
          SB.Append(Chr((Idx div 676) + 64 + 1));
          SB.Append(Chr(((Idx mod 676) div 26) + 64 + 26));
          SB.Append(Chr((Idx mod 26) + 64 + 26));
        end;
        Result := SB.ToString;
      finally
        SB.Free;
      end;
    end;
    

    或者,TStringbuilder 确实有自己的 Chars[] 属性 可写:

    uses
      System.SysUtils;
    
    function Idx2Code_(Idx: integer): string;
    var
      SB: TStringBuilder;
    begin
      SB := TStringBuilder.Create(3);
      try
        if Idx <= 26 then
        begin
          // single char: A-Z
          SB.Length := 1;
          SB[0] := Chr(64 + Idx);
        end
        else if Idx <= 676 then
        begin
          // 2 chars codes: BA-ZZ
          SB.Length := 2;
          SB[0] := Chr((Idx div 26) + 64 + 1);
          SB[1] := Chr((Idx mod 26) + 64);
        end
        else
        begin
          // 3 chars codes: BAA-ZZZ
          SB.Length := 3;
          SB[0] := Chr((Idx div 676) + 64 + 1);
          SB[1] := Chr(((Idx mod 676) div 26) + 64 + 26);
          SB[2] := Chr((Idx mod 26) + 64 + 26);
        end;
        Result := SB.ToString;
      finally
        SB.Free;
      end;
    end;
    

有关详细信息,请参阅 Embarcadero 的文档:

Migrating Delphi Code to Mobile from Desktop: Use 0-Based Strings