下面的功能输入XML输入,解析并返回一个普通字符串,该字符串简单地显示在呼叫者函数中。因此上下文中的对象是下面函数的内部。
但是此功能有一个奇怪的问题,它记得输入,这意味着对象未正确释放。即使选中输入不同,输出字符串也具有上一个呼叫的零件。
在分配xmldoc,ixmlnodelist的每个nil之前,我还试图在循环中发布,每个IXMLNODE直接通过将nil分配给它直接从项目阵列中引用。
function tform1.nodeToSentence(nodeXml: string): string ;
var
tempXmlDoc : IXMLDocument;
resultWordPuncNodes : IXMLNodeList;
i: Integer;
begin
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
Result := TrimRight(Result);
Result := Result + resultWordPuncNodes.Item[i].Text + ' ';
end
else Result := Result + resultWordPuncNodes.Item[i].Text + ' ';
end;
end;
resultWordPuncNodes:=nil;
tempXmlDoc := nil; //interface based objects are freed this way
end;
调用代码
//iterating over i
nodeXML := mugList.Strings[i];
readableSentence := nodeToSentence(mugList.Strings[i]);
//examplesList.Append(readableSentence);
//for debugging
showMessage(readableSentence);
//iteration ends
但是此功能有一个奇怪的问题,它记得输入,这意味着对象未正确释放。即使选中输入不同,输出字符串也具有上一个呼叫的零件。
这是因为String
的CC_1返回值(以及其他ARC管理的类型以及record
,object
和方法指针)在Caller和Callee之间使用隐藏的var
参数在呼叫者和Callee之间传递在输入功能时自动清除。
此代码:
function tform1.nodeToSentence(nodeXml: string): string ;
...
readableSentence := nodeToSentence(mugList.Strings[i]);
实际上与此代码相同:
procedure tform1.nodeToSentence(nodeXml: string; var Result: string);
...
nodeToSentence(mugList.Strings[i], readableSentence);
多次调用 nodeToSentence()
,您可能会越来越多地添加越来越多的文本到同一String
变量。
在函数内部,您需要在开始将新值串联到其上之前手动重置Result
值:
function tform1.nodeToSentence(nodeXml: string): string ;
var
...
begin
Result := ''; // <-- add this!
...
end;
它与接口无关。它仅在Delphi中具有字符串实现。
您必须将结果变量清除为功能的第一行。
以下是您的函数修复:
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-kind变量,请参见http://docwiki.embarcadero.com/libraries/xe8/xe8/en/system.finalize.finalize
2)您的实际功能根本不是功能。
// function tform1.nodeToSentence(nodeXml: string): string ; // only an illusion
procedure tform1.nodeToSentence(nodeXml: string; var Result: string); // real thing
您可能会说您正在编写功能,而不是过程。但是,那只是句法糖。就像 TDateTime
和 double
是不同的术语,但是这些术语是同一实现的同义词;
delphi中的所有功能返回字符串/ansistring/unicodestring变量是过程。它们只是伪装成功能,伪装很薄。
3)关于它有一个古老的DelphiKō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;
注意并阅读汇编警告。您确实修复了没有编译器警告的原始代码,不是吗?
现在编译我的第二个kōan。阅读警告。整数功能确实会产生"非启示变量"警告。字符串 - 功能没有。是吗?
为什么有区别?因为字符串是弧形。这意味着字符串功能确实是一个过程,而不是函数。这意味着结果变量是由呼叫者在外观样功能之外初始化的。您还可以观察到chant
变量在调用后确实会发生变化,因为整数函数是一个真实的函数,而分配后呼叫是真实的函数。相反,弦乐功能是一种幻觉,幻觉分配后也是如此。看起来是分配的,但不是。
5)最后一件事。我为什么将该标记放在您的代码中。
/// REMEMBER this line, it is important, I would explain later.
Result := TrimRight(Result);
正是由于上面的kōan。您一定在这里阅读垃圾。您必须使用以前没有初始化的"结果"变量。您必须期望您的编译器在此行向您发出警告。就像它在上面的ImpossibleI
实际功能中一样。但这不是。就像ImpossibleS
幻觉功能一样。缺乏警告是Delphi在这里创造的幻觉的死亡赠品。您是否会注意到应该有"非原始化的VAR"警告,但它丢失了您会问自己"谁不是我的功能,谁初始化了变量",您会明白自己发生了什么。; - )
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种。它应该是外部参数,而不是var(in out)参数。但是Delphi在您的功能上仅在这里失败。Delphi不能做字符串型外部参数。要比较,Freepascal(Lazarus,CodeTyphon)知道差异,并将显示StringOut
程序的合法"非原始变量"警告。