delphi參數傳遞數組
參數傳遞
聲明/實現一個過程使用的參數稱爲形式參數(簡稱形參),調用過程時傳入的參數稱爲實際參數(簡稱實參)。ide
{ Info是形參}函數
procedure ShowInfo(Info: String);
begin
ShowMessage(Info);
end;指針
var
S: String;
begin
S := 'lxpbuaa';
{S是實參}
ShowInfo(S);
end;orm
參數傳遞分兩種:按值(by val)和引用(by ref)。這兩種方式的本質區別是:
按值傳遞時,形參和實參是兩個變量,它們開始時的值是相同的,即實參的數據被拷貝一份傳遞給了形參。因此此時,形參的改變不會影響到實參。
引用傳遞時,形參和實參是同一個變量,能夠將它們之一看作是另外一個的別名。 因此此時,形參改變時,實參跟着改變。
默認狀況下,參數是按值傳遞的,傳遞的是數據拷貝;若是加了var前綴,則成了引用傳遞。對象
咱們看以下例子:
procedure TForm1.ByVal(I: Integer); {按值傳遞I}
begin
ShowMessage(IntToStr(Integer(@I)));
{取得形參所在地址。你會發現它和實參地址是不一樣的,由於此時實參和形參是不一樣的兩個變量}
I := I + 1;
end;內存
procedure TForm1.ByRef(var I: Integer); {引用傳遞I}
begin
ShowMessage(IntToStr(Integer(@I)));
{取得形參所在地址。你會發現它和實參地址是相同的,由於此時實參和形參是同一個變量}
I := I + 1;
end;開發
procedure TForm1.Button1Click(Sender: TObject);
var
I: Integer;
begin
I := 1;
ShowMessage(IntToStr(Integer(@I))); {取得實參所在地址}
ByVal(I); { I =1}
Showmessage(i); {i:=1;實參沒有變}
ByRef(I); { I =2}
showmessage(i); {i:=2,實參改變了}
end;字符串
按值傳遞的參數能夠指定默認值,好比上面的ByVal能夠是這樣:
procedure ByVal(I: Integer = 0);
調用它時能夠省掉有默認值的參數:ByVal。帶默認值的參數必須位於參數列表的最後,如:編譯器
procedure ByVal(I: Integer = 0; B: Boolean);
是不行的,應該改成:
procedure ByVal(B: Boolean; I: Integer = 0);
由於默認值必須是一個常數表達式,因此dynamic-array、procedural、class、class-reference和interface等參數只能指定nil默認值;而record、variant、file和static-array等類型的參數則根本不能指定默認值。
若是按值傳遞一個指針類型的參數,狀況會變得複雜而又頗有意思。此時,實際傳遞的是什麼呢?是實際數據的拷貝嗎?不,是指針的拷貝,也就是說形參和實參是兩個指針,不過這兩個指針指向了相同地址。因此這時候,形參和實參能夠共享它們指向地址中的數據,但若是改變了形參的指針指向,實參的指針指向不能跟着改變。那麼總結一下,就是:按值傳遞指針參數時,實參和形參能夠共享指針指向地址中的數據,可是不能共享指針自己的指向。而引用傳遞時,由於實參和形參是同一個變量,所以實現徹底共享。看下面的例子:
procedure TForm1.ByVal(Obj: TObject);
begin
Obj := Button1;
{改變形參指針指向,實參的指針指向不會跟着改變,由於它們是兩個變量。若是僅僅是改變Obj的屬性而不改變指向,則實參的屬性會跟着改變}
end;
procedure TForm1.ByRef(var Obj: TObject);
begin
Obj := Button1;
{改變形參指針指向,實參的指針指向跟着改變,由於它們是同一個變量}
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Obj: TObject;
begin
Obj := Self;
{Self即Form1,因此此時實參Obj的類名(ClassName)是"TForm1"}
ByVal(Obj); {按值傳遞指針變量Obj}
ShowMessage(Obj.ClassName); {顯示類名"TForm1"}
ByRef(Obj); {引用傳遞指針變量Obj}
ShowMessage(Obj.ClassName); {顯示類名"TButton1"}
end;
上面講了這麼多,最根本的仍是一句話:按值傳遞時,形參和實參是兩個變量;引用傳遞時,形參和實參是同一個變量。抓住這句話,就等於抓住了一切。(ps:關鍵總結):
相信你還看到過以下格式的參數聲明:
function CompareStr(const S1, S2: string): Integer;
function TryStrToInt(const S: string; out Value: Integer): Boolean;
其中使用了const和out關鍵字。若是你沒有看到過這樣的聲明,也沒關係,它們是真實存在的。
const聲明的參數是按值傳遞的,並且形參不能被改變。
out聲明的參數是引用傳遞的,主要用於定義輸出參數,也就是說不須要輸入值(即實參不須要初始化),實參傳遞給形參的值被忽略。
若是用const修飾指針參數,那麼只能經過形參修改指針地址裏的數據而不能修改指針自己的指向。例如對於一個const對象參數,能夠修改其屬性,可是不能將它指向其餘對象。例如:
procedure ShowInfo(const Form: TForm);
begin
{如下一句不能經過,編譯器提示:[Error] Unit1.pas(28): Left side cannot be assigned to}
{Form := Form1;}
{可是經過其屬性或者方法修改隸屬於Form的數據}
Form.Caption := 'lxpbuaa';
ShowMessage(Form.Caption);
end;
在本小節的最後,還不得不說起一種很特殊的參數類型:無類型參數(Untyped parameters)。
聲明時沒有指定數據類型的參數稱爲無類型參數。所以,從語法上講,無類型參數能夠接收任何類型的數據。
無類型參數必須加const、out或var前綴;無類型參數不能指定默認值。
如如下一些Delphi定義的過程都使用了無類型參數:
procedure SetLength(var S; NewLength: Integer); {參數S}
procedure Move(const Source;var Dest;Count:Integer); {參數Source、Dest}
procedure TStream.WriteBuffer(const Buffer; Count: Longint);{參數Buffer}
所謂無類型參數能夠接收任何類型的值,只是從語法角度而言的。或者說,理論上咱們能夠實現一個可使用任何類型變量做爲參數的過程,可是實際上沒有必要,也不可能作到。
打個比方說,咱們想造一輛能夠裝載任何物體的汽車。由於是「任何物體」,因此物體多是任何形狀,因而這輛車必須沒有車篷,除了在幾個車輪上鋪一個足夠大(足夠大就已是個大問題了)的平板外,不能再有任何東西。這時候,這個平板就能夠看作是無類型的,由於它上面能夠坐人、擺一張桌子,也能夠趕一些動物上去站着或者躺着。儘管它能夠承載不少種類的東西,可是也是有限制的,好比不能放一座山、也沒法容納1萬頭豬。因此無類型參數的類型每每是有必定限制的。好比SetLength的參數S只能是字符串、動態數組等。
這種限制通常是在過程的實現中完成的,在運行時檢查參數值的實際類型。對於與開發環境關係緊密的參數,限制也能夠構築在編譯器裏。
使用無類型參數的緣由是沒法在聲明時使用一個統一的類型來描述運行時可能的類型,如SetLength的參數S能夠是字符串和動態數組,而並無一個統一的類型來表明字符串和動態數組類型,因此乾脆聲明爲無類型。而將類型限制放到別的地方實現(如編譯器)。例如SetLength的限制規則是寫在編譯器中的,它只能做用於長字符串或者動態數組。你企圖完成下面的功能時:
var
I: Integer;
begin
SetLength(I, 10);
end;
編譯器編譯時將給出錯誤信息:[Error] Unit1.pas(35): Incompatible types。致使編譯中斷。
小結
本小節的內容比較重要,重點是理解參數按值傳遞和引用傳遞的本質:按值傳遞時,形參和實參是兩個變量;引用傳遞時,形參和實參是同一個變量。
聲明指令
聲明一個過程,可使用register、pascal、cdecl、stdcall和safecall指令來指定參數傳遞順序和參數內存管理方式,從而影響過程的運做。如:
function MyFunction(X, Y: Integer): Integer; cdecl;
這五個指令具備不一樣含義,如表3-1所示。
表3-1 五個指令的不一樣含義
指令 參數存放位置 參數傳遞順序 參數內存管理 適用地點
register CPU寄存器 從左到右 被調用者 默認.published屬性存取 方法 必須使用
pascal 棧 從左到右 被調用者 向後兼容,再也不使用
cdecl 棧 從右到左 調用者 調用C/C++共享庫
stdcall 棧 從右到左 被調用者 API調用,如回調函數
safecall 棧 從右到左 被調用者 API調用,如回調函數。雙
在一些源代碼(包括Delphi自帶的VCL源代碼)中,你還可能看到near、far、export以及inline、assemble等指令,它們是爲了和16位Windows系統或者早期Pascal/Delphi兼容,在目前的Delphi版本中,已經不具備任何意義,因此在新的開發中不要再使用。