代码之家  ›  专栏  ›  技术社区  ›  Andreas Rejbrand

Delphi Unicode字符串类型直接存储在其地址(或“Unicode短字符串”)。

  •  3
  • Andreas Rejbrand  · 技术社区  · 16 年前

    我想要一个Unicode的字符串类型,它将字符串直接存储在变量的地址处,就像(仅限于ANSI)的短字符串类型一样。

    我是说,如果我声明 S: ShortString S := 'My String' 然后,在 @S ,我将找到字符串的长度(作为一个字节,因此字符串不能包含超过255个字符),后跟ANSI编码的字符串本身。

    我想要的是这个的Unicode变体。也就是说,我想要一个字符串类型,这样,在 @ ,我将找到一个无符号的32位整数(实际上一个字节就足够了),它包含字符串的长度(以字节为单位,或以字符为单位,字节数的一半),后跟字符串的Unicode表示。我试过了 WideString , UnicodeString RawByteString 但它们似乎都只是在 @ 以及其他地方的实际字符串(我想这与引用计数等有关)。 更新: 最重要的原因可能是,如果sizeof(string)是变量,那么问题会非常严重。

    我怀疑没有内置的类型可以使用,我必须想出自己的方式来存储我想要的文本(这实际上很有趣)。我说的对吗?

    更新 除其他外,我需要在打包的记录中使用这些字符串。我还需要手动读/写这些字符串到文件/堆。我可以使用固定大小的字符串,例如<=128个字符,我可以重新设计该问题,以便它可以使用以空结尾的字符串。但是pchar不起作用,因为sizeof(pchar)=1-它只是一个地址。

    我最终决定采用的方法是使用静态字节数组。今天晚些时候,我将把我的实现作为一个解决方案发布。

    5 回复  |  直到 16 年前
        1
  •  4
  •   Rob Kennedy    16 年前

    你说得对。没有确切的模拟 ShortString 包含Unicode字符的。有很多事情很接近,包括 WideString , UnicodeString 和数组 WideChar 但是,如果您不愿意重新审视您打算使用数据类型的方式(在内存和文件中进行逐字节复制,同时在所有上下文中仍可以使用它们,那么可以使用字符串),那么Delphi的任何内置类型都不会适合您。

    宽字符串 失败,因为您坚持字符串的长度必须存在于 地址 字符串变量,但是 宽字符串 是一个引用类型;其地址处的唯一内容是另一个地址。它的长度正好在地址处 持有 变量,减4。不过,这可能会有所改变,因为该类型上的所有操作都应该通过API。

    单列字符串 由于同样的原因失败,也因为它是引用计数类型;对一个的逐字节复制会破坏引用计数,因此会出现内存泄漏、无效的指针操作异常或更细微的堆损坏。

    一个数组 宽字元 可以毫无问题地复制,但它不跟踪有效长度,而且也不经常像字符串一样工作。您可以为它分配字符串文本,并且它将像您调用的那样工作。 StrLCopy ,但不能分配字符串 变量 对它。

    您可以定义一个记录,该记录的长度有一个字段,字符数组有另一个字段。这将解决长度问题,但它仍然具有未修饰数组的所有其他缺点。

    如果我是你,我只需要使用一个内置的字符串类型。然后我编写函数来帮助在文件、内存块和本机变量之间传输它。这并不难,可能比尝试让操作符重载刚好与自定义记录类型一起工作要容易得多。考虑您将写入多少代码来加载和存储您的数据,而您将写入多少代码来像普通字符串一样使用您的数据结构。您将要编写数据持久性代码 一旦 但是在项目的余生中,你将使用这些字符串,你将希望它们看起来和行为就像真正的字符串。所以使用真正的字符串。”遭受“手动生成所需磁盘格式的不便,并获得能够使用所有现有字符串库功能的优势。

        2
  •  1
  •   Chris Thornton    16 年前

    PChar应该这样工作,对吗?阿法克,它是一组字符,存储在你放的地方。以零结尾,不确定如何使用Unicode字符。

        3
  •  1
  •   Francesca    16 年前

    实际上,在新的unicode字符串中也有这样的特性。
    s作为指针指向s[1],左边的4个字节包含长度。
    但为什么不简单地使用长度呢?

    直接读取记忆长度:

    procedure TForm9.Button1Click(Sender: TObject);
    var
      s: string;
    begin
      s := 'hlkk ljhk jhto';
      {$POINTERMATH ON}
      Assert(Length(s) = (PInteger(s)-1)^); 
      //if you don't want POINTERMATH, replace by PInteger(Cardinal(s)-SizeOf(Integer))^
      showmessage(IntToStr(length(s)));
    end;
    
        4
  •  1
  •   Mason Wheeler    16 年前

    没有短字符串的Unicode版本。如果要将Unicode数据以内联方式存储在对象内,而不是作为引用类型,则可以分配一个缓冲区:

    var
      buffer = array[0..255] of WideChar;
    

    这有两个缺点。1,大小是固定的,2,编译器不将其识别为字符串类型。

    这里的主要问题是1:固定尺寸。如果要在较大的对象或记录内声明数组,编译器需要知道数组有多大才能计算对象或记录本身的大小。对于短字符串来说,这不是一个大问题,因为它们最多只能达到256字节(k的1/4),这并不算多。但是,如果您想使用由32位整数寻址的长字符串,则最大大小为4GB。你不能把它放在物体里面!

    这不是引用计数,而是将长字符串实现为引用类型的原因,其内联大小始终是常量sizeof(指针)。然后编译器可以将字符串数据放入动态数组中,并根据当前需要调整其大小。

    为什么需要将类似这样的东西放入压缩数组中?如果我猜的话,我会说这可能与序列化有关。如果是这样,最好使用tstream和普通的unicode字符串,然后向流中写入一个整数(大小),然后再写入字符串的内容。这比试图将所有东西塞进一个压缩的数组要灵活得多。

        5
  •  0
  •   Andreas Rejbrand    16 年前

    我最终确定的解决方案是这样的(真实世界的样本-字符串当然是第三个名为“ident”的成员):

    TASStructMemHeader = packed record
      TotalSize: cardinal;
      MemType: TASStructMemType;
      Ident: packed array[0..63] of WideChar;
      DataSize: cardinal;
      procedure SetIdent(const AIdent: string);
      function ReadIdent: string;
    end;
    

    哪里

    function TASStructMemHeader.ReadIdent: string;
    begin
      result := WideCharLenToString(PWideChar(@(Ident[0])), length(Ident));
    end;
    
    procedure TASStructMemHeader.SetIdent(const AIdent: string);
    var
      i: Integer;
    begin
      if length(AIdent) > 63 then
        raise Exception.Create('Too long structure identifier.');
      FillChar(Ident[0], length(Ident) * sizeof(WideChar), 0);
      Move(AIdent[1], Ident[0], length(AIdent) * sizeof(WideChar));
    end;
    

    但后来我意识到编译器确实可以解释 array[0..63] of WideChar 作为一个字符串,所以我可以简单地写

      var
        MyStr: string;
    
      Ident := 'This is a sample string.';
      MyStr := Ident;
    

    因此,毕竟,上面梅森·惠勒给出的答案实际上是 这个 回答。