Assigned 的迷思
作者:高張遠矚(HiLoveS)
博客:http://www.cnblogs.com/hiloves/
轉載請保留該信息
在初學Delphi時,有一個疑問:如何判斷對象已創建。在論壇上問了不少人,回答有“用Assigned”和“沒法判斷”兩種。我開始在代碼中用Assigned,卻發現這個函數好像不起作用,于是偏向于另一個答案,認為“沒法判斷”對象是否創建了。通過一段時間的深入學習與思考,感悟到原來兩種答案都對。如果你也有此類疑問就隨我看下去。
疑問1:是否可以判斷對象已創建?
Delphi中的對象是堆分配,分配方式類似鏈表,系統會分配空閑的內存給對象,因為這一機制,對象的內存地址是不固定的,并可能是不連續的內存塊,并且一旦該對象被銷毀,系統會收回它所占用的內存塊,并標記為空閑內存,如果有需要,系統會將該片內存分配給別的對象使用。如果僅憑對象標識符指向的那片內存中是否有數據來判斷該對象是否已創建,顯然是不可行的。因為指向的內存區的數據可能是該對象的(對象創建未銷毀),也可能是別的對象的(該片內存被系統重新分配給了別的對象)。
因此,從這點上講你是沒辦法判斷對象是否已創建。但為什么還是有人認為并給出了另一個答案呢?
疑問2:為什么有人說Assigned可以用來判斷對象是否已創建?
沒法從內存數據上判斷,但還有曲線救國的辦法。就是判斷對象標識符是否指向某個內存地址。test: TObject,test不是對象的本體,它只是對象標識符,其本質是個指針。這個方法就是,如果這個指針指向一個內存地址,則這個對象就創建了。而且事實確實如此,test := TObject.Create,這一句確實賦給了test正確的對象內存地址。因此,如果Assigned(test)返回了True,則認為test對象已創建了。
我欣喜若狂開始在代碼中用上Assigned后,卻被澆了一盆冷水,Assigned不管用。這是怎么回事?
疑問3:為什么Assigned不起作用?
先來看一段代碼。
Code:
interface
uses
  Dialogs;
type
  TTest = class(TObject)
  public
    procedure Show;
    constructor Create;
    destructor Destroy; override;
  end;
implementation
procedure TTest.Show;
begin
  ShowMessage('Object Show');
end;
constructor TTest.Create;
begin
  ShowMessage('Object Create');
end;
destructor TTest.Destroy;
begin
  ShowMessage('Object Destroy');
end;
procedure Test1;
var
  TestObj1, TestDestroy: TTest;
begin
  if Assigned(TestObj1) then
  begin
    ShowMessage('Assigned True');  //執行了
  end;
  TestDestroy := TTest.Create;
  TestDestroy.Destroy;
  if Assigned(TestDestroy) then
  begin
    ShowMessage('Assigned True');  //執行了
  end;
end;
按照我們的期待,Test1中的兩個ShowMessage是不應該執行的。原因就是TestObj1還沒被創建呢,Assigned(TestObj1)應該返回False才對,TestDestroy被銷毀了,Assigned(TestDestroy)應該返回False才對。但不幸的是,兩個ShowMessage都運行了。Assigned徹底不起作用。
這是怎么回事?原因很簡單,我們一廂情愿的認為:當聲明一個對象時其指針是指向nil的,當銷毀一個對象后其指針自動變為nil。呵呵,Delphi可從來沒說過它會這么干。
事實是這樣的,聲明一個全局對象時Delphi會自動將其指向nil,聲明一個局部對象時Delphi會將其指向一個隨機的地址而不是nil,當銷毀一個對象時Delphi不會自動對對象標識符做什么,原來指哪里銷毀后還是指哪里。
看看全局對象會怎么樣。
Code:
var
  TestObj2: TTest;  //全局對象
implementation
procedure Test2;
begin
  if Assigned(TestObj2) then
  begin
    ShowMessage('Assigned True');  //沒有執行
  end;
end;
TestObj2是全局對象,Test2中的ShowMessage沒有執行。
如何才能讓Assigned恢復本來的雄風呢?
疑問4:Assigned不起作用,怎么辦?
原因找到就很好解決。
Code:
procedure Test4;
var
  TestObj4: TTest;
begin
  TestObj4 := nil;  //聲明之后,創建之前馬上設為nil
  if Assigned(TestObj4) then
  begin
    ShowMessage('Assigned True');  //不會執行
  end;
  TestObj4 := TTest.Create;
  if Assigned(TestObj4) then
  begin
    ShowMessage('Assigned True');  //執行
    TestObj4.Destroy;
    TestObj4 := nil;  //銷毀后馬上設為nil
  end;
  if Assigned(TestObj4) then
  begin
    ShowMessage('Assigned True');  //不會執行
  end;
end;
針對對象的不同生命期,在聲明之后、創建之前和銷毀后馬上設為nil,讓Assigned恢復雄風。
還有人說可以用“= nil”來判斷,用哪個好呢?
疑問5:“Assigned” PK “= nil”,哪個好?
說來只是個效率問題,你在System.pas中找不到有關Assigned的代碼,它是Delphi Intrinsic Routines,Delphi內部函數,是由編譯器實現的,因此效率比用“= nil”高。
另:有關Delphi Intrinsic Routines的詳情請看,這些函數效率較高
http://docwiki.embarcadero.com/RADStudio/en/Delphi_Intrinsic_Routines
疑問6:穿越了的對象參數在另一個地方被銷毀會發生什么情況?
有時候,我們會將一個對象當做參數傳遞到另一個對象中或函數中,供它們調用。如果,在創建該對象的函數中把它銷毀掉,那么穿越到別的對象中的這個對象參數會怎么樣?
Code:
type
  TTest2 = class(TObject)
  public
    fObj: TTest;
    procedure SetObj(fObj: TTest);
    procedure Show;
  end;
implementation
procedure TTest2.SetObj(fObj: TTest);
begin
  Self.fObj := fObj;
end;
procedure TTest2.Show;
begin
  if Assigned(fObj) then
  begin
    ShowMessage('fObj True');  //執行了
    fObj.Destroy;  //重復銷毀,出錯
  end;
end;
procedure Test9;
var
  TestObj9: TTest;
  Test2Obj: TTest2;
begin
  TestObj9 := TTest.Create;
  Test2Obj := TTest2.Create;
  Test2Obj.SetObj(TestObj9);
  TestObj9.Destroy;
  TestObj9 := nil;
  Test2Obj.Show;  //出錯
  Test2Obj.Destroy;
end;
在這種“傳引用”的方式中,銷毀TestObj9后,Test2Obj中的fObj還是指向銷毀前的內存地址,所以Show的代碼出錯了。如果使用Assigned,確實對這種情況無能為力,畢竟它的依據是對象標識符是否指向某個內存地址,如果TestObj9在外部被銷毀,它就會判斷失誤。除非使用傳值或復制對象的方式,我還沒想到有沒有方法解決這一問題。
上述測試代碼下載地址:https://files.cnblogs.com/hiloves/AssignedFreeCode.rar
總結
以上是生活随笔為你收集整理的Assigned 的迷思的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: django配置templates、st
- 下一篇: NO.8:自学python之路-----
