DELPHI中記錄的存儲方式
在DELPHI中,我們用record關鍵字來表明一個記錄,有時候,我們還會看到用packed record來聲明的記錄,這二者的區別就在於存儲方式的不同;在windows中,內存的分配一次是4個字節的,而Packed按字節進行內存的申請和分配,這樣速度要慢一些,因爲需要額外的時間來進行指針的定位。因此如果不用Packed的話,Delphi將按一次4個字節的方式申請內存,因此如果一個變量沒有4個字節寬的話也要佔4個字節!這樣浪費了一些空間,但提高了效率。
例如一個記錄,以,sizeof(okwary)應該得到8。而如果使用packed關鍵字,那麼sizeof(okwary)則得到5。
type okwary= record
age : integer;
sex : shortint;
end;
其中age是integer類型,正好4個字節,而sex是showint類型,佔用一個字節,但基於4字節得內存分配方式,這裏它也將佔用4個字節。
DELPHI中的變體記錄
在DELPHI中,觀察Tmessage和TTypeData的定義,從關鍵字record,你一眼就可以看出,它是一個記錄類型,但仔細觀察,你又會發現在它的定義中出現了case關鍵字。它代表什麼呢?
它代表此記錄是變體記錄。讓我們先去了解一下變體記錄。
一個典型的變體記錄定義如下:
type recordTypeName = record
fieldList1: type1;
...
fieldListn: typen;
case tag: ordinalType of
constantList1: (variant1);
...
constantListn: (variantn);
end;
其中case到結尾部分定義了多個變體字段。所有變體字段共享一段內存大小又最大變體字段決定。
使用變體記錄時要注意:
(1)Long String、WideString、Dynamic Array、Interface的大小都是指針大小, OleVariant其實就是COM SDK中的VARIANT結構,大小是16字節。
但在Object Pascal中它們都需要自動終結化,如果它們出現在variant part中,編譯器就無法知道它們是否應該進行終結化――因爲不知道當前存儲的是哪種類型。
(2)所有變體字段共享一段內存。而共享內存的大小則由最大變體字段決定。
(3)當tag存在時,它也是記錄的一個字段。也可以沒有tag。
(4)記錄的變體部分的條件域必須是有序類型
(5)記錄類型中可以含有變體部分,有點象case語句,但沒有最後的end,變體部分必需在記錄中其他字段的聲明之後
事實上Delphi中內存的幾乎所有的變體記錄都有一個特點(儘管這不是要求的),就是所有變體部份長度部和都是一樣的,比如:
TMessage = packed record
Msg: Cardinal;
case Integer of
0: (
WParam: Longint;
LParam: Longint;
Result: Longint);
1: (
WParamLo: Word;
WParamHi: Word;
LParamLo: Word;
LParamHi: Word;
ResultLo: Word;
ResultHi: Word);
end;
WParam,LParam,Result三個字段的長度和是12個字節,而WParamLo,WParamHi,LParamLo,LParamHi,ResultLo,ResultHi六個字段之和也是12個字符,同時仔細觀察,會發現後面六個字段中的每兩個字段與前面三個字段中的每一個字段都是對應的.
再看看
TRect = packed record
case Integer of
0: (Left, Top, Right, Bottom: Longint);
1: (TopLeft, BottomRight: TPoint);
end;
是不是也是一樣的呢?
變體記錄得作用
(1)節約空間。對於那些要根據條件而決定是否存儲得類型,完全可以利用變體記錄來達到節約空間得效果。例如,一個公司的員工薪水可以是月薪、年薪等方式,那麼並沒有必要在記錄中都分配空間而又用不到。
(2)類型的轉換。例如,如果有一個64位的整數類型作爲變體的第一個字段,一個32位的整數Integer類型作爲另一個變體的第一個字段,那麼可以向64字段賦值然後以整數Integer字段讀出其前32位
//假如有這樣一個員工登記表
type TpersonRec = record
ID: Integer; {員工編號}
case Boolean of {根據分類}
True: (A: Cardinal); {如果是股東, 登記年薪}
False: (B: Word); {如果不是, 登記日薪}
end;
var
personRec: TpersonRec;
begin
{先算一算這個結構的大小:
ID 是 Integer 類型, 應該是 4 字節大小;
A 是 Cardinal 類型, 也應該是 4 字節大小;
B 是 Word 類型, 應該是 2 字節大小;
合計爲 10 個字節.
}
{可事實, TpersonRec 只有 8 個字節}
ShowMessage(IntToStr(SizeOf(TpersonRec))); {8}
{
原因是: 字段 A 和 字段 B 公用了一個儲存空間;
當然這個儲存空間得依着大的, 是 Cardinal 的尺寸 4 個字節.
}
//賦值測試:
personRec.ID := 110;
personRec.A := 100000; {一看就知道是個股東}
//取值:
ShowMessage(IntToStr(personRec.A)); {100000; 這不可能有錯, 十萬大洋}
//但是:
ShowMessage(IntToStr(personRec.B)); {34464 ?! 難道這是工人的日薪嗎?}
{
首先, A 和 B 兩個字段佔用同一個空間, 給其中一個賦值, 另一個當然也就有值了;
但因爲數據類型的容量不同, 它們的值有可能是不一樣的.
在很多情況下, 我們可能根本不去理會另一個值, 但如果的確需要呢?
看下一個例子:
}
end;
type
TpersonRec = record
ID: Integer;
case tag: Boolean of {在這裏加了一個 tag 變量}
True: (A: Cardinal);
False: (B: Word);
end;
var
personRec: TpersonRec;
begin
{我們可以用 tag 變量來區分, 記錄中變體部分的值到底是誰的, 譬如:}
personRec.ID := 110;
personRec.tag := True;
personRec.A := 100000; {股東的的年薪}
personRec.ID := 111;
personRec.tag := False;
personRec.B := 100; {工人的日薪}
end;
//最經典的變體結構莫過於 Delphi 定義的 TMessage 結構了, 兩組數據分分合合都是一體, 多麼巧妙啊!
TMessage = packed record
Msg: Cardinal;
case Integer of
0: (
WParam: Longint;
LParam: Longint;
Result: Longint);
1: (
WParamLo: Word;
WParamHi: Word;
LParamLo: Word;
LParamHi: Word;
ResultLo: Word;
ResultHi: Word);
end;