標籤:空間 另一個 為什麼 pch 學習 exit http 構造 資料存放區
我們能看到以下代碼
var pSource,pDest:PChar;
len: integer;
.......................//一些代碼
Move(pSource,pDest,len); //錯誤
Move(pSource^,pDest^,len); //正確
看起來確實好像是傳值,而不是傳地址,但是各位別忘了,這不是C,C++,而是Delphi
Object Pascal,所以,絕不能從函數調用的方法判斷是傳值還是串地址!!必須看函數的
定義,只有定義才能說明是傳值還是傳地址,再說一遍,這不是C,C++!!
我們看到的函數定義是這樣的
procedure Move(const Source; var Dest; Count: Integer);
從定義上看,很清楚,Dest是傳地址,而不是傳值,那麼Source呢,其實大家不太清楚
這裡的Const修飾符有兩個含義,第一個大家都知道就是Source一常量方式在函數體內,
不可以改變它的值,第二個可能知道的人不多,那就是Source的傳遞方式和Dest一樣,
是傳地址!也就是說const和var一樣,都是傳地址,只不過一個在函數內不允許修改,
另一個是修改後影響調用的變數值
所以Move是傳地址,而恰恰不是傳值!
通過一段時間的學習,上面的理解是不全對的。 其實Const 關鍵詞修飾的 Source是傳值還是傳地址,要看Const修飾的參數類型。如果參數是簡單類型,那還是傳值,如果修飾的參數不是簡單類型,而是字串(不包括短字串),傳的是地址,是實在參數的記憶體位址,即使沒有任何關鍵字修飾,如 Procedure ProcA(s:string),也是傳的是實參的地址,通過引用計數實現,只要在改寫S的值,才寫複製。 Var關鍵詞修飾的變數肯定是傳地址。對於Delphi中資料類型,我們知道聲明一個變數分為兩部分:”變數自身“和”變數的記憶體佔用“。對於簡單類型的變數,變數本身儲存的就是變數值;而對於複雜類型(構造類型或字串類型),則”變數自身“僅僅存放”變數的記憶體佔用“的指標,而”變數的記憶體佔用“有自己的儲存空間,有地址值。就是說,複雜類型的”變數自身“存放的是實際資料所在地址的指標,一個4位元組大小的整數值。
Var
I:integer;
s:string;
begin
I:=1;// 在記憶體中I的地址裡面的值就是 1,即變數本身儲存的就是變數值;
s:=‘HELLO,WORLD!‘; // 變數本身 存放的是 ‘HELLO,WORLD!’在記憶體中存放的地址值,其實是其首地址。
end;
我們再來看 Move 函數的聲明:
procedure Move( const Source; var Dest; count : Integer );
其內部實現代碼:
procedure Move( const Source; var Dest; count : Integer );
var
S, D: PChar;
I: Integer;
begin
S := PChar(@Source);//取的是 Source變數的地址
D := PChar(@Dest); //取的是 Dest變數的地址
if S = D then Exit;
if Cardinal(D) > Cardinal(S) then
for I := count-1 downto 0 do
D[I] := S[I]
else
for I := 0 to count-1 do
D[I] := S[I];
end;
現在明白為什麼 Move(pSource,pDest,len); 是錯誤的 ,而 Move(pSource^,pDest^,len); 是正確的?儘管 pSource,pDest 聲明都是PChar類型,是一個指向字串的以NULL為字串的指標類型的指標變數。如果Move(pSource,pDest,len)這樣寫,在Move的實現代碼中(@pSource)取的是指標本身的地址,而不是指標指向資料的地址。注意是指標本身地址而不是資料存放區的地址 ,如果這樣,顯然是不能實現資料移動的,拿一個指標的地址,是取不到值的;而Move(pSource^,pDest^,len); 正確,是傳給函數 PChar變數的指向的地址值。pSource^,指示的是指向的實際的資料的地址。(@取出變數地址運算子);pDest^,指向目的記憶體位址,Move 函數,就知道要從哪個地址開始,移動多少位元組的資料到目的地址空間了,不看Move 函數的內部實現,還真不好理解其實現過程。
通過上面的分析,我們現在回過頭來,再看看Move過程的聲明,procedure Move( const Source; var Dest; count : Integer ); const 修飾的變數,傳的實參的地址值,之所以使用Const 修飾,是不允許函數內部,修改源記憶體位址,即不能將Source指向別的地址,Source的值是指向pSource^,是pSource^值的拷貝。
Souce是一個記憶體首地址,這個記憶體首地址,當然是一個值,而不是實參數的地址。它傳的還是值,這個值是變數指向的實際資料所在記憶體位址。Const 和 Var 根本就不是一樣的。對於Const 修飾指標時,理解要特別注意。可以參考 http://rainux.org/delphi 和 http://www.cnblogs.com/sonicit/archive/2008/03/23/1118524.html ,裡面講的很詳細。
總結:按值傳遞一個指標類型的參數,並且用Const 修飾。情況會變得複雜而又很有意思,實際傳遞的是指標的拷貝,形參和實參是兩個指標。不過這兩個指標指向了相同的地址。它們可以共用指標向地址中的資料,但是不能共用指標本身的指向。而引用傳遞的,形參和實參是同一個變數。因而完全共用。 Source and Dest都是指向一個首地址,但是不能說 Const 修飾的參數是按地址傳遞,這是概念的錯誤。
看下面的例子:
注意區別:
procedure TForm1.ByConstVal(const obj:TEdit)
begin
obj.Text:=‘Hello World!‘;// 正確 修改 Obj的屬性 ;
obj:=Form1;// 錯誤,不能將它指向其它的對象;不能修改指標本身的指向,可以修改指向對象的資料,包括屬性等;
end;
procedure TForm1.ByVal(obj:TEdit)
begin
obj.Text:=‘Hello World!‘;// 正確 修改 Obj的屬性 ;
obj:=Form1;// 正確,這個時候Obj指向了其它對象,而不是傳入的對象,它的指向發送了變化,不影響外面實參的指向;
end;
procedure TForm1.ByRef(obj:TEdit)
begin
obj.Text:=‘Hello World!‘;// 正確 修改 Obj的屬性 ;
obj:=Form1;// 正確,這個時候Obj指向了其它對象,而不是傳入的對象,它的指向發送了變化,影響了外面實參的指向,外面實參也指向了Form1對象;
end;
其它一些參考例子:
Move(const Source; var Dest; Count: Integer);
Source and Dest都是指向一個首地址,
如
var 首地址
P: Pointer; P^
P: PChar P^
//只要是指標類型就是 P^
S: string S[1];
Arr: array [0..10] of Char; Arr[0] or Arr
Arr: array [0..10] of TDataRecord Arr[0] or Arr
//只要是array 類型就是 Arr[0] or Arr
明白首地址後,就很容易明白C的memcpy和Delphi的Move ,或API的MoveMemory/CopyMemory,就是記憶體的相互Copy,只要給出首地址就行了。
當然在數組中首地址也可以不是0開頭,有時我們對一個record or array進行賦值時,是一個個域進行賦值,如下:
type
TArrChar: array [0..3] of Char;
procedure SetValue(Arr: TArrChar);
var
Chars: array of Char;
begin
SetLength(Chars, 4);
Chars[0] := Arr[0];
...
end;
我們可以如此:
procedure SetValue(Arr: TArrChar);
var
Chars: array of Char;
begin
SetLength(Chars, 4);
Move(Arr, Chars, SizeOf(Char) * 4);
end;
record類型也可以這樣,不過要注意對string/array類型進行靜態分配空間; 如
S: string[255];
Arr: array [0..len] of TMyType
感悟:學習Delphi入門很容易,但是深入其中,需要一個過程。不過在學習的過程中注意這些細節,徹底搞清楚,在實際開發中,就事半功倍。
http://blog.csdn.net/tjb_1216/article/details/4627346
delphi中move函數的正確理解(const和var一樣,都是傳地址,所以Move是傳地址,而恰恰不是傳值)太精彩了 good