zoukankan      html  css  js  c++  java
  • Delphi

    技术交流,DH解说.

    以前写过一次,现在全部重写吧.比较基础了,高手莫笑.

    记得有次在盒子上面看到有个人出的面试题,第一题就是:
    AnsiString 和 WideString的区别.
    好这里先留给大家想想,我讲完了,大家就应该知道了.嘿嘿.

    首先分类:
    1 ShortString,可以容纳255个字符,主要为了老版本兼容
    2 AnsiString,可以容纳2的31次方个字符,D2009前默认的String类型
    3 UnicodeString,可以容纳2的30次方个字符,D2009及以后的默认String类型
    4 WideString,可以容纳2的30次方个字符,主要在COM中用的比较多.

    好一个一个来讲:
    ShortString
    我们看到上面说的它可以容纳255个字符,但是它所占的空间是字符长度加1,为什么?
    我们来看个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    Procedure TForm3.Btn1Click(Sender: TObject);
    Var
      S: String[15];
    Begin
      S:= 'HuangJackyAAAAA';
      ShowMessageFmt('%d', [Length(S)]);//15个字符
      ShowMessageFmt('%d', [SizeOf(S)]);//空间大小是16
    End;

    从上面的代码我们可以看出来,ShortString变量不是用的指针,而是直接就是内存块,不然SizeOf应该是4的.既然我们发现这个问题,我们肯定要去看下它的内存情况吧.
     image
    我们可以发现它第一个字节用来存放的字符串的长度,所以它只能容纳255个字符,$FF,是吧.
    那么把上面的代码改一下:

    1
    2
    3
    4
    5
    6
    7
    8
    Procedure TForm3.Btn1Click(Sender: TObject);
    Var
      S: String[15];
    Begin
      S:= 'HuangJackyAAAAA';
      ShowMessageFmt('%d', [ord(S[0])]);//同样是15,哈哈.
      ShowMessageFmt('%d', [SizeOf(S)]);
    End;

    好的,这样我们已经掌握了第一种字符串了.

    AnsiString
    用同样的代码测试:

    1
    2
    3
    4
    5
    6
    7
    8
    Procedure TForm3.Btn1Click(Sender: TObject);
    Var
      S: AnsiString;
    Begin
      S:= 'HuangJackyAAAAA';
      ShowMessageFmt('%d', [Length(S)]);//15
      ShowMessageFmt('%d', [SizeOf(S)]);//4
    End;

    说明变量S只是一个指针.
    image
    好去看看这个地址:
     image
    是吧,那么存放实际字符串这块内存是怎么样组织的呢?

    偏移 -12 -10 -8 -4 0-长度-1 最后一位
    内容 字符页码 每个字符大小 引用次数 字符串长度 实际内容 0

    名词解释下:页码是什么?编码,UTF-8或者GBK这些.
    我们知道字符串其实是一个对象,但是它的释放却不需要我们操心,那就是因为当引用次数为0的时候,编译器会自动释放.
    我们上面说了AnsiString能容纳2的31次方个字符,是的,因为它的长度占了4个字节.
    好我们写代码来测试一下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Procedure TForm3.Btn1Click(Sender: TObject);
    Var
      S: AnsiString;
      I:Integer;
    Begin
      S:= 'HuangJackyAAAAA';
      I:=Integer(S);
      ShowMessage(IntToHex(PWord(I-12)^,4));//$03A8
      ShowMessage(IntToHex(PWord(I-10)^,4));//$0001,AnsiString中一个元素就一个字节
      ShowMessage(IntToHex(PCardinal(I-8)^,8));//$FFFFFFFF
      ShowMessage(IntToHex(PCardinal(I-4)^,8));//$0000000F
      ShowMessageFmt('%d', [Length(S)]);//15
      ShowMessageFmt('%d', [SizeOf(S)]);//4
    End;

    来贴图一张:
    image
    和我们上面说的一样吧.
    我们再去看下页码对应的是什么编码.$03A8就是936,查MSDN  936 - gb2312.哈哈.
    UnicodeString的内存分布也是一样的.所以我感觉-12和-10加入主要为了UnicodeString服务的.因为Unicode里面的编码就很多了,每个元素的大小也根据编码不同有所不同的.
    进入WideString之前,我们看看Delphi里面Length函数是怎么实现的.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    Unit3.pas.40: I:=Length(S);
    004B33CD 8B45FC           mov eax,[ebp-$04]
    004B33D0 85C0             test eax,eax
    004B33D2 7418             jz $004b33ec
    004B33D4 8BD0             mov edx,eax
    004B33D6 83EA0A           sub edx,$0a
    004B33D9 66833A01         cmp word ptr [edx],$01
    004B33DD 740D             jz $004b33ec
    004B33DF 8D45FC           lea eax,[ebp-$04]
    004B33E2 33C9             xor ecx,ecx
    004B33E4 8B55FC           mov edx,[ebp-$04]
    004B33E7 E8782EF5FF       call @InternalLStrFromUStr//因为我们不是用的Unicode所以这里会被跳过
    004B33EC 85C0             test eax,eax
    004B33EE 7405             jz $004b33f5
    004B33F0 83E804           sub eax,$04 //对就是这样,偏移-4
    004B33F3 8B00             mov eax,[eax] //取得它的值.是吧
    004B33F5 8BD8             mov ebx,eax

    反编译代码比Delphi7多太多了...
    刚才在翻VCL代码的时候发现这样一个结构体:就是我们刚才说的

    1
    2
    3
    4
    5
    6
    StrRec = packed record
      codePage: Word;
      elemSize: Word;
      refCnt: Longint;
      length: Longint;
    end;
    1
      

    WideString
    从名字看,我们就知道了它一个字符肯定占2个字节了.哈哈,看例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Procedure TForm3.Btn1Click(Sender: TObject);
    Var
      S: WideString;
    Begin
      S:= 'HuangJackyAAAAA';
      ShowMessageFmt('%d', [Length(S)]);//15
      ShowMessageFmt('%d', [SizeOf(S[1])]);//2
      ShowMessageFmt('%d', [SizeOf(S)]);//4
    End;

    是吧,每个字符是2个字节,变量还是存放的指针.像AnsiString那样看看它实际数据在内存中的组织:

    偏移 -4 0~长度-1 最后一个
    内容 字符串长度 实际字符串内容 $00 $00

    它同样是用4个字节来存储长度,但是它每个元素的大小是2个字节,所以它最多只能存储2的30次方个字符.

    是不是想跑去看它的内存呢?
    image
    长度是$1E??哈哈,长度要除2塞,因为这个长度是这个内存块的长度.

    最后再多说一个:
    PAnsiChar,PWideChar
    这个就是C++里面的Char*,也就是末尾是0的那种字符,这个就比较单纯了,没有什么用专门的字节来存储长度,存储引用次数.所以很多地方我们能用PChar就用PChar,因为String类型的确很耗资源.
    C++的人笑了,不要笑,CString一样有这个问题.
    看个例子:

    1
    2
    3
    4
    5
    6
    7
    8
    Procedure TForm3.Btn1Click(Sender: TObject);
    Var
      S: PAnsiChar;
    Begin
      S:= 'HuangJackyAAAAA';
      ShowMessageFmt('%d', [StrLen(S)]);//15
      ShowMessageFmt('%d', [SizeOf(S^)]);1
    End;

    看下内存,证明我没有忽悠人.
    image
    前面和它不沾边了吧,尾巴是0结尾了吧.

    开篇时候提的问题,大家也知道区别了吧.平时不要看字符串简单,其实我们了解得还不够.

    从上面看出来,字符串使用的使用第一个元素下标是从1开始的不是0.其他使用的注意事项打算写在下一篇文章里面.

    我是DH,貌似该吃午饭了.

  • 相关阅读:
    Smart Client Architecture and Design Guide
    Duwamish密码分析篇, Part 3
    庆贺发文100篇
    .Net Distributed Application Design Guide
    New Introduction to ASP.NET 2.0 Web Parts Framework
    SPS toplevel Site Collection Administrators and Owners
    来自Ingo Rammer先生的Email关于《Advanced .Net Remoting》
    The newsletter published by Ingo Rammer
    深度探索.Net Remoting基础架构
    信道、接收器、接收链和信道接受提供程序
  • 原文地址:https://www.cnblogs.com/chenmfly/p/4858921.html
Copyright © 2011-2022 走看看