它与接口无关。
它只有在 Delphi 中的字符串实现。
您必须清除 Result 变量作为函数的第一行。
下面是你的固定功能:
function tform1.nodeToSentence(nodeXml: string): string ;
var
tempXmlDoc : IXMLDocument;
resultWordPuncNodes : IXMLNodeList;
i: Integer;
begin
// Change #1 - added the line
Result := '';
// Variable Result is shared here before by both the function and the caller.
// You DO HAVE to clear the shared variable to make the function FORGET the previous result.
// You may do it by the 'function' or by the calling site, but it should have be done.
// Usually it is better to do it inside the function.
tempXmlDoc := CreateXMLDoc;
tempXmlDoc.LoadXML(nodeXml);
resultWordPuncNodes := XPathSelect(tempXmlDoc.DocumentElement,'//*');
for i:= 0 to resultWordPuncNodes.Length-1 do
begin
if resultWordPuncNodes.Item[i].NodeName = 'name' then
begin
if resultWordPuncNodes.Item[i].Attributes['attrib'] = 'v_attrib' then
begin
/// REMEMBER this line, it is important, I would explain later.
Result := TrimRight(Result);
Result := Result + resultWordPuncNodes.Item[i].Text + ' ';
end
else Result := Result + resultWordPuncNodes.Item[i].Text + ' ';
end;
end;
resultWordPuncNodes:=nil;
// Change #2 - removed the line - it is redundant
(* tempXmlDoc := nil; //interface based objects are freed this way *)
// Yes, they are. But Delphi automatically clears local ARC-variables
// when destroying them where the function exits.
// Variable should both be local and should be one of ARC types foe that.
end;
1) 关于自动清除本地和 ARC 类变量见http://docwiki.embarcadero.com/Libraries/XE8/en/System.Finalize
2) 你真正的函数根本不是函数。
// function tform1.nodeToSentence(nodeXml: string): string ; // only an illusion
procedure tform1.nodeToSentence(nodeXml: string; var Result: string); // real thing
您可能会说您正在编写一个函数,而不是过程。
然而,这只是一个语法糖。
就像TDateTime 和double 是不同的术语,但这些术语是相同实现的同义词;
Delphi 中所有返回 String/AnsiString/UnicodeString 变量的函数都是过程。它们只是伪装成功能,伪装很薄。
3) 关于它有一个古老的 Delphi kōan。如果没有 OmniXML 和所有只会模糊事实的复杂东西。运行这段代码,看看它的行为。
Function Impossible: String;
begin
ShowMessage( 'Empty string is equal to: ' + Result );
end;
Procedure ShowMe; Var spell: string;
begin
spell := Impossible();
spell := 'ABCDE';
spell := Impossible();
spell := '12345';
spell := Impossible();
end;
4) 现在,你知道吗?是的,你可以,它只需要一点点注意力。让我们做一点改变。
Function ImpossibleS: String;
begin
ShowMessage( 'Unknown string today is equal to: ' + Result );
end;
Function ImpossibleI: Integer;
begin
ShowMessage( 'Unknown integer today is equal to: ' + IntToStr(Result) );
end;
Procedure ShowMe;
Var spell: string; chant: integer;
begin
spell := ImpossibleS();
spell := 'ABCDE';
spell := ImpossibleS();
spell := '12345';
spell := ImpossibleS();
chant := ImpossibleI();
chant := 100;
chant := ImpossibleI();
chant := 54321;
chant := ImpossibleI();
end;
注意并阅读编译警告。您确实修复了它编译时没有编译器警告的原始代码,不是吗?
现在编译我的第二个公案。阅读警告。整数函数确实会生成“非初始化变量”警告。字符串函数没有。是这样吗?
为什么不一样?因为字符串是 ARC 类型的。这意味着字符串函数实际上是一个过程,而不是一个函数。这意味着 Result 变量由调用者在外观函数之外初始化。您可能还观察到chant 变量在调用后确实发生了变化,因为整数函数是一个真正的函数,而调用后的赋值是一个真正的函数。相反,字符串函数是一种虚幻的函数,调用后的虚幻赋值也是如此。它看起来像已分配,但实际上并非如此。
5) 最后一件事。为什么我在你的代码中加上那个标记。
/// REMEMBER this line, it is important, I would explain later.
Result := TrimRight(Result);
正是因为上面那个kōan。你一定是在看这里的垃圾。您一定一直在使用您之前未在任何地方初始化的“结果”变量。您一定已经预料到您的编译器会在这一行向您发出警告。就像上面的ImpossibleI 真实函数一样。但事实并非如此。就像它没有 ImpossibleS 幻觉功能一样。这种缺乏警告是德尔福在这里创造的错觉的一个致命的赠品。您是否注意到应该有一个“未初始化的变量”警告但它丢失了,您会问自己“如果它不是我的函数,谁初始化了变量”,您就会明白自己发生了什么。 ;-)
6) 好的,最后一件事。
procedure StringVar( const S1: string; var S2: string );
begin
ShowMessage ( S1 + S2 );
end;
procedure StringOut( const S1: string; out S2: string );
begin
ShowMessage ( S1 + S2 );
end;
这两个过程看起来很相似。不同的是S2种。它应该是 OUT 参数,而不是 VAR (IN+OUT) 参数。但是德尔福在这里失败了,只是因为你的功能。 Delphi 不能做字符串类型的 OUT 参数。作为比较,FreePascal(Lazarus,CodeTyphon) 知道其中的区别,并会为 StringOut 过程显示合法的“未初始化变量”警告。