比如定义一个新的PChar类型字符串的时候
还有
当有
s:String 和 p:PChar这样两个变量时,如果
s := p
这样做的话是把p所指的字符串内容拷贝到s中去嘛?还是直接把p指针指向的字符串地址赋给s?

解决方案 »

  1.   

    string为长字符串, 其实内部的存在形式是指针。
     pchar 是声明以空字符(Null)结尾的字符串的指针。
     s := p 
     是将长字符串指针指向p指针指向的字符串。
      

  2.   

    不是, 尽管string本质上是个指针, 但它和dynamic array一样都由编译器进行过特殊处理的, 和普通指针不同的是普通指针都是指向一块内存的起始地址, 但是string和dynamic array都是指向一块内存起始偏移8字节处, 前面8字节依次为引用计数和长度计数两个整数, 尤其是引用计数, 由编译器生成的代码会在操作一个字符串前把引用计数+1, 操作结束后引用计数-1, 如果引用计数=0时就自动释放整块内存. 由于它的特殊性, 所以s := p其实并不是把s指向p所指的内存而是将p所指的内存处复制一个以0为结尾的字符串(包含0)出来到新分配的内存中, 并将新字符串的引用计数+1, 在过程退出时会把引用计数-1, 如果是s是局部变量的话就会在过程/函数结束时自动释放掉这块内存.
    而如果是p := s的话那到是直接把s指向的地址赋值给p.
      

  3.   

    谢谢gogo__(天行贱) 
    那么p := s这样的操作会增加引用计数吗?
    还有,动态分配PChar用什么函数?释放呢?
      

  4.   

    如果是 p := pchar(s)这样的语句的话会增加s的引用计数, 但如果是p := pointer(s)的话则不会.
      

  5.   

    动态分配可以用任意一个分配内存的函数, 其实系统最终调用的都是GetMem, 其它的New, AllocMem, SetLength等等只不过除了调用GetMem外还做了一些初始化处理比如把内存清零. 释放可以用Dispose或者FreeMem, 系统最终都是调用FreeMem的, Dispose相当于 Finalize(p); FreeMem(p);
    Finalize的作用简单说就是自动释放结构或者数组中的string和动态数组, FreeMem则是直接释放指针所指向的内存, 例如:
    type
      TMyRec = record
        Name: string;
        X, Y: Integer;
      end;
      PMyRec = ^TMyRec;var
      MyRec : PMyRec;begin
      New(MyRec);  // 编译器会根据MyRec的大小自动计算需要分配的内存数量然后生成代码调用GetMem并将其中的Name字段清零
      MyRec.Name := str1 + str2;
      Dispose(MyRec);  // 除了调用FreeMem释放MyRec这个结构的内存外还会自动清除其中的Name所用到的内存(如果Name指向的string引用计数=1时);
    // FreeMem(MyRec); <-- 如果直接调用FreeMem释放MyRec, 则会造成内存泄露, 因为MyRec.Name指向的字符串没有释放(引用计数-1)
    end;
      

  6.   

    由于delphi关于string的内存管理的特殊性, 可以有很多技巧充分利用其优点生成非常高效的代码, 比如要用TList来保存string(不是TStringList), 一般的做法是TList.Items[i]中保存一个PString指针, 这样就需要重新分配一块内存并复制原串, 大数据量的情况下效率很低, 但是如果充分利用string的引用计数和强制类型转换技巧, 可以直接将string作为指针保存在TList.Items[i]中: 比如:var
      List: TList;
      GlobalString1, GlobalString2: string; ...
    procedure Test;
    var
      tmp: string;
    begin
      tmp := GlobalString1+GlobalString2;
      List.Add(Pointer(tmp));  // 将tmp作为指针保存进List  Integer(tmp) := 0;       // 由于Test过程结束时会自动释放掉tmp, 如果直接退出的话List中就保存了一个无效的指针了, 所以这里要欺骗编译器, 让它认为tmp已经被释放掉了, 等于在不改动tmp引用计数(当前是1)的情况下执行相当于tmp := ''的语句, 由于直接tmp := ''会修改引用计数并可能释放掉内存, 所以用强制类型转换将tmp转成一个Integer并将这个Integer设置成0(也就是nil), 此语句完全等价于pointer(tmp) := nil; 只是个人喜好我喜欢用Integer(tmp) := 0而已.
    end;
      

  7.   

    引用:
    1. string是 Delphi 编译器内在支持的(predefined or built-in),是Delphi 的一个基本数据类型,而 PChar 
    只是一个指向零终止字符串的指针;
    2. String 所存字符串是在堆分配内存的,String 变量实际上是指向零终止字符串的指针,与此同时它还具有引用计数(reference 
    count)功能,并且自身保存字符串长度,当引用计数为零时,自动释放所占用的空间。
    3.将 string 赋值给另一个string,只是一个简单的指针赋值,不产生 copy 动作,只是增加string的引用计数;
    4. 将一个 PChar 变量类型赋值给一个 string 变量类型会产生真正的 Copy 动作,即将 PChar 
    所指向的字符串整个copy到为string分配的内存中;
    5. 将 string 赋值给一个 PChar 变量类型,只是简单地将string的指针值赋值给PChar 变量类型,而string 
    的引用计数并不因此操作而发生变化,因为这种情况 PChar 会对 string 产生依赖,当string 
    的引用计数为零自动释放内存空间后,PChar很可能指向一个无效的内存地址,在你的程序你必须小心对付这种情况。
    6. 对 PChar 的操作速度要远远高于对 string 操作的速度,但 PChar 是一种落后的管理字符串的方式,而 string 
    则以高效的管理而胜出,PChar 它的存在只是为了兼容早期的类型和操作系统(调用 Windows API时会经常用到),建议平常使用 string。
      

  8.   

    -->6. 对 PChar 的操作速度要远远高于对 string 操作的速度,
    这个好象不对哦. 大部分PChar字符串操作的函数都比相应的string字符串函数速度慢, 看原代码就可以看到PChar字符串操作的函数凡是要计算字符串长度的, 都是用一个循环判断#0来实现的. 哪有string直接取-4偏移处的值来得快. 当然把PChar作为PByte类似使用的不算.另外string->PChar转换我说错了, xixuemao是对的. 不会增加string的引用计数. 我只看到它生成的最终代码要调用LStrToPChar这个函数, 以为那里面会增加引用计数. 最后跟踪进去看了一下, 它里面只是判断了一下是不是nil而没有进行任何进一步操作(不管是不是nil都返回了)而已, 不过p := PChar(s)生成的代码要白白调用一个函数而p := pointer(s)却只是简单赋值, 搞不懂borland为什么要这样设计.
      

  9.   

    找到篇文章:http://www.ruanxun.com/HArticle3178.aspx,是讲字符串的,我看了下,感觉不错,学了不少东西。
      

  10.   

    Delphi 中字符串的操作很简单,但幕后情况却相当复杂。Pascal 传统的字符串操作方法与Windows 不同,Windows吸取了C语言的字符串操作方法。32位Delphi中增加了长字符串类型,该类型功能强大,是Delphi 确省的字符串类型。字符串类型 
    在Borland公司的Turbo Pascal和16位Delphi中,传统的字符串类型是一个字符序列,序列的头部是一个长度字节,指示当前字符串的长度。由于只用一个字节来表示字符串的长度,所以字符串不能超过255个字符。这一长度限制为字符串操作带来不便,因为每个字符串必须定长(确省最大值为255),当然你也可以声明更短的字符串以节约存储空间。 字符串类型与数组类型相似。实际上一个字符串差不多就是一个字符类型的数组,因为用[]符号,你就能访问字符串中的字符,这一事实充分说明了上述观点。为克服传统Pascal 字符串的局限性,32位Delphi增加了对长字符串的支持。这样共有三种字符串类型:ShortString 短字符串类型也就是前面所述的传统 Pascal 字符串类型。这类字符串最多只能有255个字符,与16位Delphi中的字符串相同。短字符串中的每个字符都属于ANSIChar 类型(标准字符类型)。 
    ANSIString长字符串类型就是新增的可变长字符串类型。这类字符串的内存动态分配,引用计数,并使用了更新前拷贝(copy--on-write)技术。这类字符串长度没有限制(可以存储多达20亿个字符!),其字符类型也是ANSIChar 类型。 
    WideString 长字符串类型与ANSIString 类型相似,只是它基于WideChar 字符类型,WideChar 字符为双字节Unicode 字符。 
    使用长字符串 
    如果只简单地用String定义字符串,那么该字符串可能是短字符串也可能是ANSI长字符串,这取决于$H 编译指令的值,$H+(确省)代表长字符串(ANSIString 类型)。长字符串是Delphi 库中控件使用的字符串。Delphi 长字符串基于引用计数机制,通过引用计数追踪内存中引用同一字符串的字符串变量,当字符串不再使用时,也就是说引用计数为零时,释放内存。如果你要增加字符串的长度,而该字符串邻近又没有空闲的内存,即在同一存储单元字符串已没有扩展的余地,这时字符串必须被完整地拷贝到另一个存储单元。当这种情况发生时,Delphi运行时间支持程序会以完全透明的方式为字符串重新分配内存。为了有效地分配所需的存储空间,你可以用SetLength 过程设定字符串的最大长度值:SetLength (String1, 200);
    SetLength 过程只是完成一个内存请求,并没有实际分配内存。它只是把将来所需的内存预留出来,实际上并没有使用这段内存。这一技术源于Windows 操作系统,现被Delphi用来动态分配内存。例如,当你请求一个很大的数组时,系统会将数组内存预留出来,但并没有把内存分配给数组。一般不需要设置字符串的长度,不过当需要把长字符串作为参数传递给API 函数时(经过类型转换后),你必须用SetLength 为该字符串预留内存空间,这一点我会在后面进行说明。看一看内存中的字符串 
    为了帮你更好地理解字符串的内存管理细节,我写了一个简例StrRef 。在程序中我声明了两个全程字符串:Str1 和 Str2,当按下第一个按钮时,程序把一个字符串常量赋给第一个变量,然后把第一个变量赋给第二个: Str1 := 'Hello';
    Str2 := Str1;
    除了字符串操作外,程序还用下面的StringStatus 函数在一个列表框中显示字符串的内部状态:function StringStatus (const Str: string): string;
    begin
    Result := 'Address: ' + IntToStr (Integer (Str)) +
    ', Length: ' + IntToStr (Length (Str)) + 
    ', References: ' + IntToStr (PInteger (Integer (Str) - 8)^) +
    ', Value: ' + Str;
    end;
    在StringStatus 函数中,用常量参数传递字符串至关重要。用拷贝方式(值参)传递会引起副作用,因为函数执行过程中会产生一个对字符串的额外引用;与此相反,通过引用(var)或常量(const)参数传递不会产生这种情况。由于本例不希望字符串被修改,因此选用常量参数。为获取字符串内存地址(有利于识别串的实际内容也有助于观察两个不同的串变量是否引用了同一内存区),我通过类型映射把字符串类型强行转换为整型。字符串实际上是引用,也就是指针:字符串变量保存的是字符串的实际内存地址。为了提取引用计数信息,我利用了一个鲜为人知的事实:即字符串长度和引用计数信息实际上保存在字符串中, 位于实际内容和字符串变量所指的内存位置之前,其负偏移量对字符串长度来说是-4(用Length 函数很容易得到这个值),对引用记数来说是-8。不过必须记住,以上关于偏移量的内部信息在未来的Delphi版本中可能会变,没有写入正式Delphi文档的特性很难保证将来不变。通过运行这个例子,你会看到两个串内容相同、内存位置相同、引用记数为2,如图7.1中列表框上部所示。现在,如果你改变其中一个字符串的值,那么更新后字符串的内存地址将会改变。这是copy-on-write技术的结果。图 7.1: 例StrRef显示两个串的内部状态,包括当前引用计数 
    第二个按钮(Change)的OnClick 事件代码如下,结果如图7.1列表框第二部分所示:procedure TFormStrRef.BtnChangeClick(Sender: TObject);
    begin
    Str1 [2] := 'a';
    ListBox1.Items.Add ('Str1 [2] := ''a''');
    ListBox1.Items.Add ('Str1 - ' + StringStatus (Str1));
    ListBox1.Items.Add ('Str2 - ' + StringStatus (Str2));
    end;
    注意,BtnChangeClick 只能在执行完BtnAssignClick 后才能执行。为此,程序启动后第二个按钮不能用(按钮的Enabled 属性设成False);第一个方法结束后激活第二个按钮。你可以自由地扩展这个例子,用StringStatus 函数探究其它情况下长字符串的特性。
      

  11.   

    Delphi 字符串与 Windows PChar字符串 
    长字符串为零终止串,这意味着长字符串完全与Windows使用的C语言零终止串兼容,这给长字符串使用带来了便利。一个零终止串是一个字符序列,该序列以一个零字节(或null)结尾。零终止串在Delphi中可用下标从零开始的字符数组表示,C语言就是用这种数组类型定义字符串,因此零终止字符数组在Windows API 函数(基于C语言)中很常见。由于Pascal长字符串与C语言的零终止字符串完全兼容,因此当需要把字符串传递给Windows API 函数时,你可以直接把长字符串映射为PChar 类型。下例把一个窗体的标题拷贝给PChar 字符串(用API 函数GetWindowText),然后再把它拷贝给按钮的Caption 属性,代码如下:procedure TForm1.Button1Click (Sender: TObject);
    var
    S1: String;
    begin
    SetLength (S1, 100);
    GetWindowText (Handle, PChar (S1), Length (S1));
    Button1.Caption := S1;
    end;
    你可以在例LongStr 中找到这段代码。注意:代码中用SetLength函数为字符串分配内存,假如内存分配失败,那么程序就会崩溃;如果你直接用PChar 类型传递值(而不是象以以上代码那样接受一个值),那么代码会很简单,因为不需要定义临时字符串,也不需要初始化串。下面代码把一个Label(标签)控件的Caption 属性作为参数传递给了API函数,只需要简单地把属性值映射为PChar类型:SetWindowText (Handle, PChar (Label1.Caption));
    当需要把WideString 映射为Windows兼容类型时,你必须用PWideChar 代替PChar进行转换,WideString常用于OLE和 COM 程序。刚才展现了长字符串的优点,现在谈谈它的弊端。当你把长字符串转换为PChar 类型时可能会引发一些问题,问题根本在于:转换以后字符串及其内容将由你来负责,Delphi 不再管了。现在把上面Button1Click代码稍作修改:procedure TForm1.Button2Click(Sender: TObject);
    var
    S1: String;
    begin
    SetLength (S1, 100);
    GetWindowText (Handle, PChar (S1), Length (S1));
    S1 := S1 + ' is the title'; // this won't work
    Button1.Caption := S1;
    end;
    程序编译通过,但执行结果会令你惊讶,因为按钮的标题并没变,所加的常量字符串没有添加到按钮标题中。问题原因是Windows写字符串时(在GetWindowText API调用中),Windows 没有正确设置Pascal 长字符串的长度。Delphi 仍可以输出该字符串,并能通过零终止符判断字符串何时结束,但是如果你在零终止符后添加更多的字符,那么这些字符将被忽略。怎么解决这个问题呢?解决方法是告诉系统把GetWindowText API函数返回的字符串再转换成Pascal字符串。然而,如果你用以下代码:S1 := String (S1);
    Delphi 系统将不予理睬,因为把一种类型转换为它自己的类型是无用的操作。为获得正确的Pascal 长字符串,需要你把字符串重新映射为一个PChar 字符串,然后让Delphi 再把它转回到字符串:S1 := String (PChar (S1));
    实际上,你可以跳过字符串转换(S1 := PChar (S1));, 因为在Delphi中Pchar转换到string是自动执行的,最终代码如下:procedure TForm1.Button3Click(Sender: TObject);
    var
    S1: String;
    begin
    SetLength (S1, 100);
    GetWindowText (Handle, PChar (S1), Length (S1));
    S1 := String (PChar (S1));
    S1 := S1 + ' is the title';
    Button3.Caption := S1;
    end;
    另一个办法是用PChar 字符串的长度重新设定Delphi 字符串长度,可以这样写:SetLength (S1, StrLen (PChar (S1)));
    在例LongStr中你可以看到三种方法的结果,分别由三个按钮执行。如果只想访问窗体标题,仅需要用到窗体对象本身的Caption 属性,没有必要写这段迷糊人的代码,这段代码只是用来说明字符串转换问题。当调用Windows API 函数时会遇到这种实际问题,那时你就不得不考虑这一复杂情况了。格式化字符串 
    使用加号(+)操作符和转换函数(如IntToStr),你确实能把已有值组合成字符串,不过另有一种方法能格式化数字、货币值和其他字符串,这就是功能强大的Format 函数及其一族。Format 函数参数包括:一个基本文本字符串、一些占位符(通常由%符号标出)和一个数值数组,数组中每个值对应一个占位符。例如,把两个数字格式化为字符串的代码如下:Format ('First %d, Second %d', [n1, n2]);
    其中n1和n2是两个整数值,第一个占位符由第一个值替代,第二个占位符由第二个值替代,以此类推。如果占位符输出类型(由%符号后面的字母表示)与对应的参数类型不匹配,将产生一个运行时间错误,因此设置编译时间类型检查会有利于Format 函数的使用。除了%d外,Format 函数还定义了许多占位符,见表7.1。这些占位符定义了相应数据类型的默认输出,你可以用更深一层的格式化约束改变默认输出,例如一个宽度约束决定了输出中的字符个数,而精度约束决定了小数点的位数。例如Format ('%8d', [n1]);
    该句把数字n1转换成有8个字符的字符串,并通过填充空白使文本右对齐,左对齐用减号(-) 。表 7.1: Format函数的占位符 占位符 说明 
    d (decimal) 将整型值转换为十进制数字字符串 
    x (hexadecimal) 将整型值转换为十六进制数字字符串 
    p (pointer) 将指针值转换为十六进制数字字符串 
    s (string) 拷贝字符串、字符、或字符指针值到一个输出字符串 
    e (exponential) 将浮点值转换为指数表示的字符串 
    f (floating point) 将浮点值转换为浮点表示的字符串 
    g (general) 使用浮点或指数将浮点值转换为最短的十进制字符串 
    n (number) 将浮点值转换为带千位分隔符的浮点值 
    m (money) 将浮点值转换为现金数量表示的字符串,转换结果取决于地域设置,详见Delphi帮助文件的Currency and date/time formatting variables主题 
    领会以上内容最好的办法是你亲自进行字符串格式化试验。为了简便起见,我写了FmtTest 程序,它能将整数和浮点数转换为格式化字符串。从图7.2可见,程序窗体分为左右两部分,左边对应整型数字转换,右边对应浮点数转换。各部分的第一个编辑框显示需要格式化为字符串的数值。第一个编辑框下方有一个按钮,用来执行格式化操作并在消息框中显示结果;紧接着第二个编辑框用于输入格式化类型串。你也可以单击ListBox 控件中的任一行,选择预定义的格式化类型串,也可以自行输入,每输入一个新的格式化类型串,该类型串就会被添加到列表框中(注意,关闭程序就失去了添加的类型)。 图 7.2: 程序 FmtTest 的浮点值输出 
    本例只简单使用了不同的控制文本来产生输出,下面列出了其中一个Show 按钮事件代码:procedure TFormFmtTest.BtnIntClick(Sender: TObject);
    begin
    ShowMessage (Format (EditFmtInt.Text,
    [StrToInt (EditInt.Text)]));
    // if the item is not there, add it
    if ListBoxInt.Items.IndexOf (EditFmtInt.Text) < 0 then
    ListBoxInt.Items.Add (EditFmtInt.Text);
    end;
    这段代码主要用EditFmtInt 编辑框的文本和EditInt 控件的值进行了格式化操作。如果格式化类型串没有在列表框中列出,那么输入的串会被添加到列表框中;如果用户在列表框中进行点击,代码会把点击的串移到编辑框中:procedure TFormFmtTest.ListBoxIntClick(Sender: TObject);
    begin
    EditFmtInt.Text := ListBoxInt.Items [
    ListBoxInt.ItemIndex];
    end
      

  12.   

    我看到了不是LStrToPChar过程
    我看到的怎么是LStrClr???
      

  13.   

    LStrClr是有局部变量是string或者参数中有string的过程/方法/函数退出时由编译器自动加进去的调用,作用就是相关string的引用计数-1, 必要时会调用freemem释放内存.
      

  14.   

    chenxi_aji (阿吉) ,怎么不给分啊