下面的函数接受XML输入,解析它,并返回一个简单显示在调用函数中的普通字符串。因此,上下文中的对象是下面函数的内部。
但是这个函数有一个奇怪的问题,它会记住输入,这意味着对象没有正确释放。即使检查输入不同,输出字符串也有前一次调用产生的部分。
在为每个XMLDoc,IXMLNodeList分配nil之前,我还尝试在循环中释放每个IXMLNode,它们直接从Item数组引用,通过为它分配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
答案 0 :(得分:2)
但是这个函数有一个奇怪的问题,它会记住输入,这意味着对象没有正确释放。即使检查输入不同,输出字符串也有前一次调用产生的部分。
这是因为String
(以及其他ARC托管类型,以及function
,record
和方法指针)的object
返回值使用隐藏的var
参数在调用者和被调用者之间传递,该参数在输入函数时自动清除 NOT ,正如您所期望的那样。
此代码:
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;
答案 1 :(得分:1)
它与接口无关。 它只在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)有一个旧的德尔福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;
注意并阅读编辑警告。你确实修复了它编译的原始代码,那里没有编译器警告,不是吗?
现在编译我的第二个kōan。阅读警告。整数函数确实生成“非初始化变量”警告。字符串函数没有。是这样吗?
为何与众不同?因为字符串是ARC类型。这意味着字符串函数实际上是一个过程,而不是一个函数。这意味着调用者在类似外观的函数之外初始化Result变量。您可能还会观察到chant
变量在调用后确实发生了变化,因为整数函数是一个实函数,并且调用后的赋值是真实函数。相反,一个字符串函数是一个虚幻的函数,因此是一个虚假的分配后调用。它看起来像是被分配了,但事实并非如此。
5)最后一件事。为什么我在你的代码中加上那个标记。
/// REMEMBER this line, it is important, I would explain later.
Result := TrimRight(Result);
正是因为上面的kōan。你一定是在这里读垃圾了。您必须使用之前未初始化的“结果”变量。您必须期望您的编译器在此行向您发出警告。就像它在上面的ImpossibleI
实函数中一样。但事实并非如此。就像它不具有ImpossibleS
幻觉功能一样。缺乏这种警告是德尔福在这里创造的错觉。你会注意到它应该是一个'非初始化的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种。它应该是OUT参数,而不是VAR(IN + OUT)参数。但是Delphi只是在你的功能上失败了。 Delphi不能做字符串型的OUT参数。比较一下,FreePascal(Lazarus,CodeTyphon)知道差异,并会显示StringOut
程序的合法“非初始化变量”警告。