Delphi Programming / Object Pascal
[掲載 2012年07月13日] [更新 2019年11月09日] Delphi サンプルプログラム集
080_TList へ数値等を格納
動作確認等
Windows 7 U64(SP1) + Delphi XE5(UP2) Pro VCL-32
080_TListValue.zip [17,712 KB] 2019年11月09日版 (EXE 同梱)
TList は汎用のリスト操作用のクラスです.
Add メソッドを使用して各種のオブジェクトを格納できますが,Add メソッドの引数は Pointer 型です.したがって,数値や文字列を格納するにはポインタ型を使用します.ポインタ型の変数を使用する場合は,その変数用のメモリの確保と解放処理が必要です.
TList の要素にはポインタを代入するので,レコード型のデータを格納する場合はポインタ型の定義が必要です.Windows API で使用するほとんどのレコード型にはポインタ型も定義されています.
Delphi 2009 で実装されたジェネリックス TList<T> を使用すると,ポインタを意識しないてリストの操作が可能です.リストの要素 T にはいろいろな型を指定することができます.
TList<T> には,ソート等のメソッドも実装されています.
[備考 1]
参考リンクの RTTI (実行時型情報) のサンプルは,ジェネリックス Array<T> を使用して,クラスやレコードを配列として扱っています.
[備考 2]
文字列には TStringList
という専用のリスト用のクラスがあります.ファイルへの保存,ファイルからの読み込みのメソッドも実装されています.
[備考 3]
リストは文字通り,データのリストの操作用です.リスト項目の入れ換えや追加,挿入,削除,あるいはソート等,データをリストとして管理する目的で使用されています.
配列は,主に要素間の演算が目的で,数値計算 (数学) 言えばベクトルや行列等を扱う場合に使用されています.基本的に,要素の追加,挿入,削除,入れ換え等の機能はありませんが,TArray<T> にはソートの機能が実装されています.
目的にもよりますが,1 次元の配列をリストとして利用することも考えられます.数値型の 1 次元の動的配列であれば,System.Types には TIntegerDynArray, TDoubleDynArray 等の配列型が宣言されています.
01_ TList の要素の値はメモリ上のデータの先頭アドレス
TList の要素には,データが書き込まれるメモリの先頭アドレスの値がポインタとして格納されます.本サンプルはそのことを確認するためのテストコードです.
TList.Add メソッドの引数は Pointer 型です.これはアドレス情報だけを持つポインタ型です.このサンプルでは,TList の要素に配列のデータを格納していますが,代入する時に,配列要素の値に @ 演算子 (アドレス演算子) を付けています.@ 演算子を付けると,その変数の値が書き込まれているポインタとしてのメモリアドレスとなります.
つまり,TList の要素には,配列要素のデータが書き込まれているメモリアドレスの値を格納していることになります.
このサンプルでは,TList も配列もデータは同じメモリアドレスにあるわけですから,配列要素の値を変更すると,TList から取得する値も変わります.その逆も同じです.このことを確認するためのコードも追加しています.
TList に格納されているのがアドレスですから,TList からデータを取得する時は,データを格納した時の変数型のポインタ型でキャストして逆参照します.逆参照は,メモリ上のデータを参照するポインタ型の機能です.
本サンプルのような使用方法では,データを格納するメモリとして配列要素のメモリを共有します.ポインタ型の変数を用意してメモリを確保する必要はありません.
図1
TList に配列要素の値を代入
リスト1
TList の要素には,データが書き込まれるメモリアドレスの値が格納される
copy code
// =====================================================================================
// TList の要素の値はデータが書き込まれるメモリアドレスの値
// その確認用のテスト
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LArray : array [0 ..4 ] of Double;
LList : TList;
LValue : Double;
LIndex : Integer;
begin
Memo1.Lines.Clear;
// テスト用のデータを配列要素に代入
LArray[0 ] := 100.0 ;
LArray[1 ] := 200.0 ;
LArray[2 ] := 300.0 ;
LArray[3 ] := 400.0 ;
LArray[4 ] := 500.0 ;
LList := TList.Create;
try
// 配列要素のデータのアドレスを TList に格納
for LIndex := 0 to Length(LArray) - 1 do begin
LList.Add(@LArray[LIndex]);
end ;
// 配列要素の値を変更してみる
// 対応する TList の要素の値も変わる
LArray[0 ] := 5000000.5 ;
// TList に格納したデータを表示
// 実際には TList に格納されているのはデータがあるメモリアドレスの値
// しだがって,逆参照で取得することになる
for LIndex := 0 to LList.Count - 1 do begin
LValue := PDouble(LLIst[LIndex])^;
Memo1.Lines.Add(FloatToStr(LValue));
end ;
Memo1.Lines.Add('' );
// TList の要素のデータを変更してみる
// 対応する配列要素の値も変わる
LValue := 3000000.5 ;
PDouble(LList[1 ])^ := LValue;
Memo1.Lines.Add(FloatToStr(LArray[1 ]));
finally
FreeAndNil(LList);
end ;
end ;
02_ TList に Integer 型の数値を格納 - 補足 : IndexOf メソッド
TList に Integer 型の整数値を格納する例です.
Integer 型のポインタ型は PInteger 型です.数値型のポインタ型はプリフィックスとして大文字の P を付けた型名になっています.Delphi ではポインタ型のプリフィックスとして慣習的に大文字の P が使用されています.
図2
TList に Integer 型の数値を格納
リスト2
TList に Integer 型の数値を格納
copy code
procedure TForm1.Button1Click(Sender: TObject);
var
LList : TList;
LValue : Integer;
LpValue : PInteger;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LList := TList.Create;
try
// テスト用のデータを TList に格納
for LIndex := 0 to 4 do begin
// メモリ領域を確保
New(LpValue);
LValue := (LIndex + 1 ) * 100 ;
// ポインタの逆参照で値を代入
// LpValue^ := (LIndex + 1) * 100; でもよい
LpValue^ := LValue;
LList.Add(LpValue);
end ;
// TList に格納した値を表示
for LIndex := 0 to LList.Count - 1 do begin
// ポインタ型の値として取得
LpValue := PInteger(LLIst[LIndex]);
// 逆参照して表示
Memo1.Lines.Add(IntToStr(LpValue^));
end ;
// 確保したメモリを解放
for LIndex := LList.Count - 1 downto 0 do begin
Dispose(PInteger(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
end ;
end ;
[ 補足 : IndexOf メソッド ]
TList には,指定された値を持つ要素のインデックスを取得する IndexOf メソッドがありますが,このメソッドは,引数のポインタでリストを検索します.つまり,変数のアドレスの値を比較して検索します.Delphi のクラスはポインタですから,コンポーネント等を TList に格納した場合は,そのインスタンス等で検索することができます.
任意の数値の場合,その変数の値が格納されているメモリ上のアドレスの情報は TList には格納されていません.したがって検索できません.整数値や実数値,あるいは文字列の場合は,例えば以下のような関数を作成して対応することも考えられます
// -----------------------------------------------------------------------------
// TList.IndexOf はポインタ (変数の値が格納されているアドレス) で検索する
// この関数は引数の TList に格納されている値で検索する
// 引数の AItem には変数のアドレスを渡す (変数に @ 演算子を付ける)
// ただし Integer 型の数値のリスト専用
// -----------------------------------------------------------------------------
function IndexOf_forInteger(AList: TList; AItem: Pointer): Integer;
var
LIndex : Integer;
begin
Result := -1 ;
for LIndex := 0 to AList.Count -1 do begin
if PInteger(AList[LIndex])^ = PInteger(AItem)^ then begin
Result := LIndex;
Break;
end ;
end ;
end ;
リストの要素に数値を代入したり,数値を取得するには,以下のように,要素に格納している数値型のポインタ型でキャストして逆参照の機能を使用します.
上のコードのように,ポインタ型の変数を使用することもできます.その場合,代入先のポインタ型の変数のメモリを確保する必要はありません.
// リストの要素に値を代入する
// LValue は Integer 型の変数
// リストに格納されているのが Integer 型のデータの場合は
// そのポインタ型の PInteger でキャストして逆参照する
LValue := 500000 ;
PInteger(LList[1 ])^ := LValue;
// リストの要素の値を逆参照で取得する
LValue := PInteger(LList[1 ])^;
04_ New 関数によるメモリの確保と AllocMem 関数
New 関数は,ポインタ型の変数を使用してデータを書き込むメモリ領域を確保します.New 関数を実行してもポインタ型の変数自身のアドレスは変わりません.ポインタ型の変数のアドレスに書き込まれる値が変わります.その値がホインタとしてのアドレスの値です.そのアドレスから,データの型に応じたバイト数のメモリが確保されます.
下図はそのことを確認した結果です.左側の値がポインタ型の変数自身の値です.その右側は,New 関数を実行した結果,そのアドレスに書き込まれたメモリの値です.この値が TList に格納された値です.右端の値はその値を,データが書き込まれているアドレスとして取得したデータです.TList に格納した値と一致しています.
ポインタ型の変数が,ポインタとしての役割をしていることが確認できます.
図3
ポインタ型の変数自身のアドレスと,そのアドレスのメモリの値
copy code
procedure TForm1.Button1Click(Sender: TObject);
var
LList : TList;
LpValue : PInteger;
LAddress : NativeInt;
LAdrData : NativeInt;
LDigits : Integer;
LDstValue : Integer;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LDigits := SizeOf(Pointer) * 2 ;
LList := TList.Create;
try
for LIndex := 0 to 4 do begin
New(LpValue);
LpValue ^ := (LIndex + 1 ) * 100 ;
LList.Add(LpValue);
LAddress := NativeInt(@LpValue);
LAdrData := PNativeInt(LAddress)^;
LDstValue := PInteger(LAdrData)^;
Memo1.Lines.Add('$' + IntToHex(LAddress, LDigits)
+ ' $' + IntToHex(LAdrData, LDigits)
+ ' ' + IntToStr(LDstValue));
end ;
for LIndex := LList.Count - 1 downto 0 do begin
Dispose(PInteger(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
end ;
end ;
New 関数は確保したメモリを初期化はしません.AllocMem 関数を使用すると,確保したメモリを初期化できます.AllocMem 関数で確保したメモリは,FreeMem 関数を使用して解放します.
図4
New 関数で確保したメモリの値
図5
AllocMem 関数で確保したメモリの値
copy code
// =====================================================================================
// New 関数によるメモリの確保
// 確保したメモリの初期化は行わない
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LList : TList;
LpValue : PInteger;
LAddress : NativeInt;
LAdrData : NativeInt;
LDigits : Integer;
LDstValue : Integer;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LDigits := SizeOf(Pointer) * 2 ;
LList := TList.Create;
try
for LIndex := 0 to 4 do begin
New(LpValue);
LList.Add(LpValue);
LAddress := NativeInt(@LpValue);
LAdrData := PNativeInt(LAddress)^;
LDstValue := PInteger(LAdrData)^;
Memo1.Lines.Add('$' + IntToHex(LAddress, LDigits)
+ ' $' + IntToHex(LAdrData, LDigits)
+ ' ' + IntToStr(LDstValue));
end ;
for LIndex := LList.Count - 1 downto 0 do begin
Dispose(PInteger(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
end ;
end ;
// =====================================================================================
// AllocMem 関数によるメモリの確保
// 確保したメモリの初期化を行う
// =====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LList : TList;
LpValue : PInteger;
LAddress : NativeInt;
LAdrData : NativeInt;
LDigits : Integer;
LDstValue : Integer;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LDigits := SizeOf(Pointer) * 2 ;
LList := TList.Create;
try
for LIndex := 0 to 4 do begin
LpValue := AllocMem(SizeOf(Integer));
LList.Add(LpValue);
LAddress := NativeInt(@LpValue);
LAdrData := PNativeInt(LAddress)^;
LDstValue := PInteger(LAdrData)^;
Memo1.Lines.Add('$' + IntToHex(LAddress, LDigits)
+ ' $' + IntToHex(LAdrData, LDigits)
+ ' ' + IntToStr(LDstValue));
end ;
for LIndex := LList.Count - 1 downto 0 do begin
FreeMem(PInteger(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
end ;
end ;
05_ TList に Double 型の数値を格納
TList に Double 型の実数値を格納する例です.Double 型のポインタ型は PDouble 型です.
図6
TList に Double 型の数値を格納
リスト3
TList に Double 型の数値を格納
copy code
procedure TForm1.Button1Click(Sender: TObject);
var
LList : TList;
LValue : Double;
LpValue : PDouble;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LList := TList.Create;
try
// テスト用のデータを TList に格納
for LIndex := 0 to 4 do begin
// メモリ領域を確保
New(LpValue);
// ポインタの逆参照で値を代入
// LpValue^ := (LIndex + 1) * 100.0 + 0.5; でもよい
LValue := (LIndex + 1 ) * 100.0 + 0.5 ;
LpValue^ := LValue;
LList.Add(LpValue);
end ;
// TList に格納した値を表示
for LIndex := 0 to LList.Count - 1 do begin
// ポインタ型の値として取得
LpValue := PDouble(LLIst[LIndex]);
// 逆参照して表示
Memo1.Lines.Add(FloatToStr(LpValue^));
end ;
// 確保したメモリを解放
for LIndex := LList.Count - 1 downto 0 do begin
Dispose(PDouble(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
end ;
end ;
06_ TList に String 型の文字列を格納
TList に String 型の文字列を格納する例です.String 型のポインタ型は PString 型です.
String 型には,String 型専用の TStringList と言うクラスがあります.
図7
TList に String 型の文字列を格納
リスト4
TList に String 型の文字列を格納
copy code
procedure TForm1.Button1Click(Sender: TObject);
var
LList : TList;
LValue : string ;
LpValue : PString;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LList := TList.Create;
try
// テスト用のデータを TList に格納
for LIndex := 0 to 4 do begin
// メモリ領域を確保
New(LpValue);
LValue := '文字列 : ' + IntToStr((LIndex + 1 ) * 100 );
// ポインタの逆参照で値を代入
LpValue^ := LValue;
LList.Add(LpValue);
end ;
// TList に格納した値を表示
for LIndex := 0 to LList.Count - 1 do begin
// ポインタ型の値として取得
LpValue := PString(LLIst[LIndex]);
Memo1.Lines.Add(LpValue^);
end ;
// 確保したメモリを解放
for LIndex := LList.Count - 1 downto 0 do begin
Dispose(PString(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
end ;
end ;
TList にレコード型のデータを格納する例です.テスト用に自前のレコード型を定義して,そのポインタ型も定義しています.レコード型のポインタ型は,レコードのデータを取得したり代入する時に必要です.
Windows API で使用するレコード型については,そのポインタ型も定義されています.
図8
TList にレコード型のデータを格納
copy code
implementation
uses
System.StrUtils;
{$R *.dfm}
type
TDataRec = record
Text : string ;
Value : Integer;
end ;
PDataRec = ^TDataRec;
var
FRecordList : TList;
// =====================================================================================
// フォーム生成時の処理
// =====================================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
FRecordList := TList.Create;
end ;
// =====================================================================================
// フォーム破棄時の処理
// =====================================================================================
procedure TForm1.FormDestroy(Sender: TObject);
var
LIndex : Integer;
begin
// 確保したメモリを解放
for LIndex := FRecordList.Count - 1 downto 0 do begin
Dispose(PDataRec(FRecordList[LIndex]));
end ;
FRecordList.Clear;
if Sender = nil then Exit;
FreeAndNil(FRecordList);
end ;
// =====================================================================================
// TList にレコード型のデータを格納
// uses に System.StrUtils が必要
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LpDataRec : PDataRec;
LIndex : Integer;
begin
Memo1.Lines.Clear;
FormDestroy(nil );
// テスト用のデータを TList に格納
for LIndex := 0 to 4 do begin
// メモリ領域を確保
New(LpDataRec);
// レコード型の初期化
// 初期化を忘れると正しいデータとして格納できないことがある
LpDataRec^ := System.Default (TDataRec);
// レコード型のメンバに値をセット
LpDataRec^.Text := DupeString('字' , LIndex + 1 );
LpDataRec^.Value := (LIndex + 1 ) * 100 ;
// ポインタをリストに追加
FRecordList.Add(LpDataRec);
end ;
end ;
// =====================================================================================
// TList に格納しているレコード型のデータを表示
// =====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LpDataRec : PDataRec;
LText : string ;
LIndex : Integer;
begin
Memo1.Lines.Clear;
// TList に格納しているデータを表示
for LIndex := 0 to FRecordList.Count - 1 do begin
// ポインタ型の値として取得
LpDataRec := PDataRec(FRecordList[LIndex]);
LText := IntToStr(LpDataRec^.Value) + ' ' + string (LpDataRec^.Text);
Memo1.Lines.Add(LText);
end ;
end ;
TList のデータをソートする例です.以下のコードでは Integer 型の数値を降順にソートします.
図9
TList に格納した Integer 型の数値を降順ソート
リスト6
TList に格納した Integer 型の数値を降順ソート
copy code
// --------------------------------------------------------------------------------------
// TList のソート関数のコールバック関数
// Integer 型の場合 (降順)
// --------------------------------------------------------------------------------------
function CompareFunc(Item1, Item2: Pointer): Integer;
begin
Result := PInteger(Item2)^ - PInteger(Item1)^;
end ;
// =====================================================================================
// TList に整数値を格納してソート
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LList : TList;
LValue : Integer;
LpValue : PInteger;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LList := TList.Create;
try
// テスト用のデータを TList に格納
for LIndex := 0 to 4 do begin
New(LpValue);
LValue := (LIndex + 1 ) * 100 ;
LpValue^ := LValue;
LList.Add(LpValue);
end ;
// ------------------------
// ソート
// 関数名は任意
// ------------------------
LList.Sort(CompareFunc);
// ソートの結果として TList に格納している値を表示
for LIndex := 0 to LList.Count - 1 do begin
LpValue := PInteger(LLIst[LIndex]);
Memo1.Lines.Add(IntToStr(LpValue^));
end ;
// 確保したメモリを解放
for LIndex := LList.Count - 1 downto 0 do begin
Dispose(PInteger(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
end ;
end ;
TList に Double 型の数値が格納されている場合は,ソート用の関数を,例えば次のようにすると降順ソートできます.
// --------------------------------------------------------------------------------------
// TList のソート関数のコールバック関数
// Double 型の場合 (降順)
// uses に System.Math が必要
// --------------------------------------------------------------------------------------
function CompareFunc(Item1, Item2: Pointer): Integer;
begin
Result := Sign(PDouble(Item2)^ - PDouble(Item1)^);
end ;
Math ユニットに実装されている CompareValue 関数を利用すると,ソート用の関数を以下のように書くことができます.
// --------------------------------------------------------------------------------------
// TList のソート関数のコールバック関数
// Double 型の場合 (降順)
// uses に System.Math が必要
// --------------------------------------------------------------------------------------
function CompareFunc(Item1, Item2: Pointer): Integer;
begin
Result := CompareValue(PDouble(Item2)^, PDouble(Item1)^);
end ;
レコード型のソートは,レコードのメンバの値で処理します.前出のレコード型のサンプルの場合は,例えば次のようになります.
// --------------------------------------------------------------------------------------
// TList のソート関数のコールバック関数
// レコード型の場合 (Value メンバの値で降順)
// --------------------------------------------------------------------------------------
function CompareFunc(Item1, Item2: Pointer): Integer;
begin
Result := PDataRec(Item2)^.Value - PDataRec(Item1)^.Value;
end ;
TList のソート関数には TList.Sort メソッドの他に TList.SortList というメソッドがあります.このメソッドを使用すると,以下のように無名メソッド (無名関数) が使用できます.
TList.SortList は Delphi 2010 で追加されたメソッドです.
LList.SortList(
function (Item1, Item2: Pointer): Integer
begin
Result := PInteger(Item2)^ - PInteger(Item1)^;
end
);
var
LSortFunc : TListSortCompareFunc;
begin
// ;
LSortFunc := function (Item1, Item2: Pointer): Integer
begin
Result := PInteger(Item2)^ - PInteger(Item1)^;
end ;
LList.SortList(LSortFunc);
//
TList には,データをファイルに保存するメソッド類はありません.本サンプルでは TList のデータをメモリストリームに書き込んで,そのメモリストリームをファイルに保存します.
ファイルから読み込む時も,ファイルに保存されているデータをメモリストリームにロードします.そのメモリストリームからデータを読み出して TList の要素に代入します.
ファイルからデータ読み出す時に読み出すデータのサイズが必要です.したがって,ファイルへの保存と読み出しは,TList に格納されているデータが固定長の場合に限ります.String 型等の可変長のデータは以下のコードでは処理できません.
図10
ファイルに保存したデータを読み込んで表示
リスト7
TList のデータのファイルへの保存とファイルからの読み込み
copy code
// =====================================================================================
// TList に実数値を格納してファイルに保存
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LList : TList;
LValue : Double;
LpValue : PDouble;
LStream : TMemoryStream;
LFilePath : string ;
LIndex : Integer;
begin
LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin' ;
Memo1.Lines.Clear;
LList := TList.Create;
try
// テスト用のデータを TList に格納
for LIndex := 0 to 4 do begin
New(LpValue);
LpValue^ := (LIndex + 1 ) * 100.0 + 0.5 ;
LList.Add(LpValue);
end ;
// TList に格納したデータをメモリストリームに書き込む
LStream := TMemoryStream.Create;
try
for LIndex := 0 to LList.Count - 1 do begin
LStream.Write (LLIst[LIndex], SizeOf(Double));
end ;
// メモリストリームをファイルに保存
LStream.SaveToFile(LFilePath);
finally
FreeAndNil(LStream);
end ;
// 確保したメモリを解放
for LIndex := LList.Count - 1 downto 0 do begin
Dispose(PDouble(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
end ;
end ;
// =====================================================================================
// ファイルからデータを読み込んで TList に格納
// =====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LList : TList;
LValue : Double;
LpValue : PDouble;
LStream : TMemoryStream;
LFilePath : string ;
LIndex : Integer;
begin
LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin' ;
Memo1.Lines.Clear;
LList := TList.Create;
LStream := TMemoryStream.Create;
try
// メモリストリームにファイルの内容をロードする
LStream.LoadFromFile(LFilePath);
while True do begin
// メモリストリームからデータを読み込む
LStream.ReadData(LValue);
// 読み込んだデータを TList に追加
New(LpValue);
LpValue^ := LValue;
LList.Add(LpValue);
if LStream.Position >= LStream.Size then Break;
end ;
// TList に格納した値を表示
for LIndex := 0 to LList.Count - 1 do begin
LpValue := PDouble(LLIst[LIndex]);
Memo1.Lines.Add(FloatToStr(LpValue^));
end ;
// 確保したメモリを解放
for LIndex := LList.Count - 1 downto 0 do begin
Dispose(PDouble(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
FreeAndNil(LStream);
end ;
end ;
11_ TList のデータをファイルに保存 - ファイル型を使用
ファイル型を使用して TList のデータをファイルに保存し,読み込む時もファイル型を使用して,ファイルからデータを読み出して TList の要素に代入する例です.
リスト8
TList のデータのファイルへの保存とファイルからの読み込み.ファイル型を使用する例
copy code
// =====================================================================================
// TList に実数値を格納してファイルに保存
// ファイル型を使用する例
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LList : TList;
LValue : Double;
LpValue : PDouble;
LFile : File of Byte;
LFilePath : string ;
LIndex : Integer;
begin
LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin' ;
Memo1.Lines.Clear;
LList := TList.Create;
try
// テスト用のデータを TList に格納
for LIndex := 0 to 4 do begin
New(LpValue);
LpValue^ := (LIndex + 1 ) * 100.0 + 0.5 ;
LList.Add(LpValue);
end ;
// 保存ファイル名をファイル型の変数に割り当てる
AssignFile(LFile, LFilePath);
try
// 新規にファイルを作成して開く(既存ファイルは上書き)
Rewrite(LFile);
// データを順番にファイルに書き込む
for LIndex := 0 to LList.Count - 1 do begin
LValue := PDouble(LList[LIndex])^;
BlockWrite(LFile, LValue, SizeOf(LValue));
end ;
finally
// ファイルを閉じる
CloseFile(LFile);
end ;
// 確保したメモリを解放
for LIndex := LList.Count - 1 downto 0 do begin
Dispose(PDouble(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
end ;
end ;
// =====================================================================================
// ファイルからデータを読み込んで TList に格納
// ファイル型を使用する例
// =====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LList : TList;
LValue : Double;
LpValue : PDouble;
LFile : File of Byte;
LFilePath : string ;
LIndex : Integer;
begin
LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin' ;
if not FileExists(LFilePath) then Exit;
Memo1.Lines.Clear;
LList := TList.Create;
try
// 保存ファイル名をファイル型の変数に割り当てる
AssignFile(LFile, LFilePath);
try
// ファイルを開く
Reset(LFile);
// ファイルからデータを読み込んでリストに追加
while True do begin
if Eof(LFile) then break;
BlockRead(LFile, LValue, SizeOf(LValue));
// メモリを確保してリストに追加
New(LpValue);
LpValue^ := LValue;
LList.Add(LpValue);
end ;
finally
// ファイルを閉じる
CloseFile(LFile);
end ;
// TList に格納した値を表示
for LIndex := 0 to LList.Count - 1 do begin
LpValue := PDouble(LLIst[LIndex]);
Memo1.Lines.Add(FloatToStr(LpValue^));
end ;
// 確保したメモリを解放
for LIndex := LList.Count - 1 downto 0 do begin
Dispose(PDouble(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
end ;
end ;
12_ TList のデータをファイルに保存 - レコード型
TList に格納しているレコード型のデータのファイルへの保存と読み出しのサンプルです.
TList でレコード型を使用する時は,そのポインタ型も定義しておきます.レコード型のポインタ型は,レコードのデータを取得したり代入する時に必要です.Delphi では,Windows API で使用するレコード型については,そのポインタ型も定義されています.
TList に格納されているデータをファイルに保存して読み出す場合,TList に格納されているデータが固定長の場合に限ります.String 型は可変長なので使用できません.このサンプルでは,レコード型の文字列のメンバとして Char 型の静的な文字配列を使用しています.
この Char 型の静的な文字配列は,Windows API の構造体でもよく使用されています.
図11
ファイルに保存したデータを読み込んで表示
リスト9
TList のレコード型データをファイルに保存
copy code
implementation
uses
System.StrUtils;
{$R *.dfm}
type
TDataRec = record
Text : array [0 ..29 ] of Char;
Value : Integer;
end ;
PDataRec = ^TDataRec;
// =====================================================================================
// TList にレコード型のデータを格納してファイルに保存
// uses に System.StrUtils が必要
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LList : TList;
LpDataRec : PDataRec;
LStream : TMemoryStream;
LFilePath : string ;
LIndex : Integer;
begin
LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin' ;
Memo1.Lines.Clear;
LList := TList.Create;
try
// テスト用のデータを TList に格納
for LIndex := 0 to 4 do begin
// メモリを確保
New(LpDataRec);
// レコード型の初期化
// 初期化を忘れると正しいデータとして格納できないことがある
LpDataRec^ := System.Default (TDataRec);
// string 型の文字列を Char 型の文字配列にコピー
StrPCopy(LpDataRec^.Text, DupeString('字' , LIndex + 1 ));
LpDataRec^.Value := (LIndex + 1 ) * 100 ;
// リストに追加
LList.Add(LpDataRec);
end ;
// TList に格納したデータをメモリストリームに書き込む
LStream := TMemoryStream.Create;
try
for LIndex := 0 to LList.Count - 1 do begin
LStream.Write (LLIst[LIndex], SizeOf(LpDataRec^))
end ;
// メモリストリームをファイルに保存
LStream.SaveToFile(LFilePath);
finally
FreeAndNil(LStream);
end ;
// 確保したメモリを解放
for LIndex := LList.Count - 1 downto 0 do begin
Dispose(PDataRec(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
end ;
end ;
// =====================================================================================
// ファイルからデータを読み込んで TList に格納
// =====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LList : TList;
LpDataRec : PDataRec;
LStream : TMemoryStream;
LFilePath : string ;
LText : string ;
LIndex : Integer;
begin
LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin' ;
if not FileExists(LFilePath) then Exit;
Memo1.Lines.Clear;
LList := TList.Create;
try
LStream := TMemoryStream.Create;
try
// メモリストリームにファイルの内容をロードする
LStream.LoadFromFile(LFilePath);
while True do begin
// ポインタのメモリを確保
New(LpDataRec);
// メモリストリームからデータを読み込む
LStream.Read (LpDataRec^, SizeOf(TDataRec));
// 読み込んだデータを TList に格納
LList.Add(LpDataRec);
if LStream.Position >= LStream.Size then Break;
end ;
finally
FreeAndNil(LStream);
end ;
// TList に格納した値を表示
for LIndex := 0 to LList.Count - 1 do begin
// ポインタ型の値として取得
LpDataRec := PDataRec(LLIst[LIndex]);
LText := IntToStr(LpDataRec^.Value) + ' ' + string (LpDataRec^.Text);
Memo1.Lines.Add(LText);
end ;
// 確保したメモリを解放
for LIndex := LList.Count - 1 downto 0 do begin
Dispose(PDataRec(LLIst[LIndex]));
end ;
finally
FreeAndNil(LList);
end ;
end ;
13_ TList<T> に Integer 型の数値を格納
TList<T> に Integer 型の数値を格納する例です.型の T を Integer にします.
図12
TList<T> に Integer 型の数値を格納
リスト10
TList<T> に Integer 型の数値を格納.uses に Generics.Collections が必要
copy code
procedure TForm1.Button1Click(Sender: TObject);
var
LListInt : TList<Integer>;
LValue : Integer;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LListInt := TList<Integer>.Create;
try
// テスト用のデータを格納
for LIndex := 0 to 4 do begin
LValue := (LIndex + 1 ) * 100 ;
LListInt.Add(LValue);
end ;
// 格納したデータを表示
for LIndex := 0 to LListInt.Count - 1 do begin
LValue := LListInt[LIndex];
Memo1.Lines.Add(IntToStr(LValue));
end ;
finally
FreeAndNil(LListInt);
end ;
end ;
14_ TList<T> に Double 型の数値を格納
TList<T> に Double 型の数値を格納する例です.型の T を Double にします.
図13
TList<T> に Double 型の数値を格納
リスト11
TList<T> に Double 型の数値を格納.uses に Generics.Collections が必要
copy code
procedure TForm1.Button1Click(Sender: TObject);
var
LListDouble : TList<Double>;
LValue : Double;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LListDouble := TList<Double>.Create;
try
// テスト用のデータを格納
for LIndex := 0 to 4 do begin
LValue := (LIndex + 1 ) * 100.0 + 0.5 ;
LListDouble.Add(LValue);
end ;
// 格納したデータを表示
for LIndex := 0 to LListDouble.Count - 1 do begin
LValue := LListDouble[LIndex];
Memo1.Lines.Add(FloatToStr(LValue));
end ;
finally
FreeAndNil(LListDouble);
end ;
end ;
15_ TList<T> に String 型の文字列を格納
TList<T> に String 型の文字列を格納する例です.型の T を String にします.
String 型には,String 型専用の TStringList と言うクラスがあります.
図14
TList<T> に String 型の文字列を格納
リスト12
TList<T> に String 型の文字列を格納.uses に Generics.Collections が必要
copy code
procedure TForm1.Button1Click(Sender: TObject);
var
LListStr : TList<string >;
LValue : string ;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LListStr := TList<String >.Create;
try
// テスト用のデータを格納
for LIndex := 0 to 4 do begin
LValue := '文字列 : ' + IntToStr((LIndex + 1 ) * 100 );
LListStr.Add(LValue);
end ;
// 格納したデータを表示
for LIndex := 0 to LListStr.Count - 1 do begin
LValue := LListStr[LIndex];
Memo1.Lines.Add(LValue);
end ;
finally
FreeAndNil(LListStr);
end ;
end ;
16_ TList<T> にレコード型のデータを格納
TList<T> にレコード型のデータを格納する例です.テスト用に自前のレコード型を定義しています.
図15
TList<T> にレコード型のデータを格納
リスト13
TList<T> にレコード型のデータを格納
copy code
implementation
uses
System.StrUtils;
{$R *.dfm}
type
TDataRec = record
Text : string ;
Value : Integer;
end ;
PDataRec = ^TDataRec;
var
FListRec : TList<TDataRec>;
// =====================================================================================
// フォーム生成時の処理
// =====================================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
FListRec := TList<TDataRec>.Create;
end ;
// =====================================================================================
// フォーム破棄時の処理
// =====================================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
FListRec.Clear;
if Sender = nil then Exit;
FreeAndNil(FListRec);
end ;
// =====================================================================================
// ジェネリックスの TList にレコード型のデータを代入
// uses に Generics.Collections, System.StrUtils が必要
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LSrcRec : TDataRec;
LIndex : Integer;
begin
Memo1.Lines.Clear;
FormDestroy(nil );
// テスト用のデータを格納
for LIndex := 0 to 4 do begin
// レコード型の初期化
// 初期化を忘れると正しいデータとして格納できないことがある
LSrcRec := System.Default (TDataRec);
// レコード型のメンバに値をセット
LSrcRec.Text := DupeString('文字列' , LIndex + 1 );
LSrcRec.Value := (LIndex + 1 ) * 100 ;
FListRec.Add(LSrcRec);
end ;
end ;
// =====================================================================================
// ジェネリックスの TList に格納しているレコード型のデータを表示
// =====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LDstRec : TDataRec;
LText : string ;
LIndex : Integer;
begin
Memo1.Lines.Clear;
// 格納しているデータを表示
for LIndex := 0 to FListRec.Count - 1 do begin
LDstRec := FListRec[LIndex];
LText := IntToStr(LDstRec.Value) + ' ' + string (LDstRec.Text);
Memo1.Lines.Add(LText);
end ;
end ;
17_ TList<T> の OnNotify イベントで項目の追加や削除を検出
TList<T> には,項目を追加,削除,あるいは抽出した時に発生する OnNotify イベントが実装されています.そのイベントの使用例です.
図16
TList<T> の項目の追加や削除のイベント
リスト14
TList<T> の OnNotify イベントで項目の追加や削除を検出
copy code
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
Generics.Collections;
type
TForm1 = class (TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private 宣言 }
public
{ Public 宣言 }
// イベントの手続き宣言文
// Item は使用する型に応じて変更する
procedure MyNotify(Sender: TObject; const Item: Integer;
Action: TCollectionNotification);
end ;
var
Form1: TForm1;
implementation
{$R *.dfm}
// =====================================================================================
// ジェネリックスの TList のイベント OnNotify の使用例
// uses に Generics.Collections が必要
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LListInt : TList<Integer>;
LValue : Integer;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LListInt := TList<Integer>.Create;
try
LListInt.OnNotify := MyNotify;
// テスト用のデータを格納
for LIndex := 0 to 4 do begin
LValue := (LIndex + 1 ) * 100 ;
LListInt.Add(LValue);
end ;
finally
FreeAndNil(LListInt);
end ;
end ;
// ------------------------------------------------------------------------------------^^^
// イベントの実現部
// ------------------------------------------------------------------------------------^^^
procedure TForm1.MyNotify(Sender: TObject; const Item: Integer;
Action: TCollectionNotification);
begin
case Action of
cnAdded:
Memo1.Lines.Add('追加: ' + IntToStr(Item));
cnRemoved:
Memo1.Lines.Add('削除: ' + IntToStr(Item));
cnExtracted:
Memo1.Lines.Add('抽出: ' + IntToStr(Item));
end ;
end ;
end .
TList<T> のデータをソートする例です.Integer 型の数値を降順にソートします.
図17
TList<T> に格納した Integer 型の数値を降順ソート
リスト15
TList<T> に格納した Integer 型の数値を降順ソート
copy code
implementation
uses
Generics.Defaults;
{$R *.dfm}
// -----------------------------------------------------------------------------
// ジェネリックス TList のソート関数
// Integer 型用なので引数の型を Integer にしている
// -----------------------------------------------------------------------------
function CompareFunc(const Left, Right: Integer): Integer;
begin
// これは降順
Result := Right - Left;
end ;
// =====================================================================================
// ジェネリックスの TList に整数型の値を代入してソート
// uses に Generics.Collections, Generics.Defaults が必要
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LListInt : TList<Integer>;
LValue : Integer;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LListInt := TList<Integer>.Create;
try
// テスト用のデータを格納
for LIndex := 0 to 4 do begin
LValue := (LIndex + 1 ) * 100 ;
LListInt.Add(LValue);
end ;
// ---------------------------------------------------------
// ソート
// Construct の引数の関数名は任意
// 数値,文字列等の昇順ソートであれば引数なしでよい
// TComparer関係の使用にはusesにGenerics.Defaultsが必要
// ---------------------------------------------------------
LListInt.Sort(TComparer<Integer>.Construct(CompareFunc));
// ソートの結果として格納しているデータを表示
for LIndex := 0 to LListInt.Count - 1 do begin
LValue := LListInt[LIndex];
Memo1.Lines.Add(IntToStr(LValue));
end ;
finally
FreeAndNil(LListInt);
end ;
end ;
Double 型の場合は,例えば以下のように処理できます.
リスト16
TList<T> に格納した Double 型の数値を降順ソート
copy code
implementation
uses
Generics.Defaults,
System.Math;
{$R *.dfm}
// -----------------------------------------------------------------------------
// ジェネリックス TList のソート関数
// Double 型用なので引数の型を Double にしている
// uses に System.Math が必要
// -----------------------------------------------------------------------------
function CompareFunc(const Left, Right: Double): Integer;
begin
// これは降順の場合
// 昇順の場合は引数を逆にする
Result := Sign(Right - Left);
end ;
// =====================================================================================
// ジェネリックスの TList に実数型の値を代入してソート
// uses に Generics.Collections, Generics.Defaults が必要
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LListDouble : TList<Double>;
LValue : Double;
LIndex : Integer;
begin
Memo1.Lines.Clear;
LListDouble := TList<Double>.Create;
try
// テスト用のデータを格納
for LIndex := 0 to 4 do begin
LValue := (LIndex + 1 ) * 100.0 + 0.5 ;
LListDouble.Add(LValue);
end ;
// ---------------------------------------------------------
// ソート
// Construct の引数の関数名は任意
// 数値,文字列等の昇順ソートであれば引数なしでよい
// TComparer関係の使用にはusesにGenerics.Defaultsが必要
// ---------------------------------------------------------
LListDouble.Sort(TComparer<Double>.Construct(CompareFunc));
// ソートの結果として格納しているデータを表示
for LIndex := 0 to LListDouble.Count - 1 do begin
LValue := LListDouble[LIndex];
Memo1.Lines.Add(FloatToStr(LValue));
end ;
finally
FreeAndNil(LListDouble);
end ;
end ;
Math ユニットに実装されている CompareValue 関数を利用すると,ソート用の関数を以下のように書くことができます.
// -----------------------------------------------------------------------------
// ジェネリックス TList のソート関数
// Double 型用なので引数の型を Double にしている
// uses に System.Math が必要
// -----------------------------------------------------------------------------
function CompareFunc(const Left, Right: Double): Integer;
begin
// これは降順の場合
Result := CompareValue(Right, Left);
end ;
レコード型のソートは,レコードのメンバの値で処理します.前出のレコード型のサンプルの場合は,例えば次のようになります.
// -----------------------------------------------------------------------------
// ジェネリックス TList のソート関数
// レコード型で,レコード型の型名が TDataRec の場合
// レコード型のメンバの値で処理する
// -----------------------------------------------------------------------------
function CompareFunc(const Left, Right: TDataRec): Integer;
begin
// これは降順
Result := Right.Value - Left.Value;
end ;
// ソートの実行
LListRec.Sort(TComparer<TDataRec>.Construct(CompareFunc));
//
19_ TList<T> のソート - 無名メソッド
TList<T>.Sort メソッドでは無名メソッドが使用できます.以下のコードは Integer 型の数値を降順にソートする例です.
// ---------------------------------------------------------
// ソート
// 数値,文字列等の昇順ソートであれば引数なしでよい
// TComparer関係の使用にはusesにGenerics.Defaultsが必要
// ---------------------------------------------------------
LListInt.Sort(TComparer<Integer>.Construct(
function (const Left, Right: Integer): Integer
begin
// これは降順の判定
Result := Right - Left;
end )
);
var
LSortFunc : TComparison<Integer>;
begin
// ---------------------------------------------------------
// ソート
// 数値,文字列等の昇順ソートであれば引数なしでよい
// TComparer関係の使用にはusesにGenerics.Defaultsが必要
// ---------------------------------------------------------
LSortFunc := function (const Left, Right: Integer): Integer
begin
// これは降順の判定
Result := Right - Left;
end ;
LListInt.Sort(TComparer<Integer>.Construct(LSortFunc));
//
[備考]
以下の記事にレコード型のソートの例があります.
TList<T> には,データをファイルに保存するメソッド類はありません.本サンプルでは TList<T> のデータをメモリストリームに書き込んで,そのメモリストリームをファイルに保存します.
ファイルから読み込む時も,ファイルに保存されているデータをメモリストリームにロードします.そのメモリストリームからデータを読み出して TList<T> の要素に代入します.
ファイルからデータ読み出す時に読み出すデータのサイズが必要です.したがって,ファイルへの保存と読み出しは,TList<T> に格納されているデータが固定長の場合に限ります.String 型等の可変長のデータは以下のコードでは処理できません.
図18
ファイルに保存したデータを読み込んで表示
リスト17
TList<T> のデータのファイルへの保存とファイルからの読み込み
copy code
// =====================================================================================
// ジェネリックスの TList に実数値を格納してファイルに保存
// uses に Generics.Collections が必要
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LListDouble : TList<Double>;
LValue : Double;
LStream : TMemoryStream;
LFilePath : string ;
LIndex : Integer;
begin
LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin' ;
Memo1.Lines.Clear;
LListDouble := TList<Double>.Create;
try
// テスト用のデータを格納
for LIndex := 0 to 4 do begin
LValue := (LIndex + 1 ) * 100.0 + 0.5 ;
LListDouble.Add(LValue);
end ;
LStream := TMemoryStream.Create;
try
for LIndex := 0 to LListDouble.Count - 1 do begin
// TList に格納したデータをメモリストリームに書き込む
LValue := LListDouble[LIndex];
LStream.WriteData(LValue);
// または
// LStream.Write(@LValue, SizeOf(LValue));
end ;
// メモリストリームをファイルに保存
LStream.SaveToFile(LFilePath);
finally
FreeAndNil(LStream);
end ;
finally
FreeAndNil(LListDouble);
end ;
end ;
// =====================================================================================
// ファイルからデータを読み込んでジェネリックスの TList に格納
// uses に Generics.Collections が必要
// =====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LListDouble : TList<Double>;
LValue : Double;
LStream : TMemoryStream;
LFilePath : string ;
LIndex : Integer;
begin
LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin' ;
if not FileExists(LFilePath) then Exit;
Memo1.Lines.Clear;
LListDouble := TList<Double>.Create;
try
LStream := TMemoryStream.Create;
try
// メモリストリームにファイルの内容をロードする
LStream.LoadFromFile(LFilePath);
while True do begin
// メモリストリームからデータを読み込む
LStream.ReadData(LValue);
// 読み込んだデータを TList に格納
LListDouble.Add(LValue);
if LStream.Position >= LStream.Size then Break;
end ;
finally
FreeAndNil(LStream);
end ;
// 格納したデータを表示
for LIndex := 0 to LListDouble.Count - 1 do begin
LValue := LListDouble[LIndex];
Memo1.Lines.Add(FloatToStr(LValue));
end ;
finally
FreeAndNil(LListDouble);
end ;
end ;
21_ TList<T> のデータをファイルに保存 - ファイル型を使用
ファイル型を使用して TList<T> のデータをファイルに保存し,読み込む時もファイル型を使用して,ファイルからデータを読み出して TList<T> の要素に代入する例です.
ファイル型を Byte 型にしているので,前項のサンプルで保存したデータを読み込むことができます.また,本サンプルで保存したデータを前項のサンプルで読み出すこともてぎす.
リスト18
TList<T> のデータのファイルへの保存とファイルからの読み込み.ファイル型を使用する例
copy code
// =====================================================================================
// ジェネリックスの TList に実数値を格納してファイルに保存
// ファイル型を使用する例
// uses に Generics.Collections が必要
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LFilePath : string ;
LListDouble : TList<Double>;
LValue : Double;
LFile : File of Byte;
LIndex : Integer;
begin
LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin' ;
Memo1.Lines.Clear;
LListDouble := TList<Double>.Create;
try
// テスト用のデータを格納
for LIndex := 0 to 4 do begin
LValue := (LIndex + 1 ) * 100.0 + 0.5 ;
LListDouble.Add(LValue);
end ;
// 保存ファイル名をファイル型の変数に割り当てる
AssignFile(LFile, LFilePath);
try
// 新規にファイルを作成して開く(既存ファイルは上書き)
Rewrite(LFile);
// データを順番にファイルに書き込む
for LIndex := 0 to LListDouble.Count - 1 do begin
LValue := LListDouble[LIndex];
BlockWrite(LFile, LValue, SizeOf(LValue));
end ;
finally
// ファイルを閉じる
CloseFile(LFile);
end ;
finally
FreeAndNil(LListDouble);
end ;
end ;
// =====================================================================================
// ファイルからデータを読み込んでジェネリックスの TList に格納
// ファイル型を使用する例
// uses に Generics.Collections が必要
// =====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LFilePath : string ;
LListDouble : TList<Double>;
LValue : Double;
LFile : File of Byte;
LIndex : Integer;
begin
LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin' ;
if not FileExists(LFilePath) then Exit;
Memo1.Lines.Clear;
LListDouble := TList<Double>.Create;
try
// 保存ファイル名をファイル型の変数に割り当てる
AssignFile(LFile, LFilePath);
try
// ファイルを開く
Reset(LFile);
// ファイルからデータを読み込んでリストに追加
while True do begin
if Eof(LFile) then break;
BlockRead(LFile, LValue, SizeOf(LValue));
LListDouble.Add(LValue);
end ;
finally
// ファイルを閉じる
CloseFile(LFile);
end ;
// 格納したデータを表示
for LIndex := 0 to LListDouble.Count - 1 do begin
LValue := LListDouble[LIndex];
Memo1.Lines.Add(FloatToStr(LValue));
end ;
finally
FreeAndNil(LListDouble);
end ;
end ;
22_ TList<T> のデータをファイルに保存 - レコード型
TList<T> に格納しているレコード型のデータのファイルへの保存と読み出しのサンプルです.
TList<T> に格納されているデータをファイルに保存して読み出す場合,TList<T> に格納されているデータが固定長の場合に限ります.String 型は可変長なので使用できません.このサンプルでは,レコード型の文字列のメンバとして Char 型の静的な文字配列を使用しています.
この Char 型の静的な文字配列は,Windows API の構造体でもよく使用されています.
図19
ァイルに保存したデータを読み込んで表示
リスト19
TList<T> のレコード型データをファイルに保存
copy code
implementation
uses
System.StrUtils;
{$R *.dfm}
type
TDataRec = record
Text : array [0 ..29 ] of Char;
Value : Integer;
end ;
PDataRec = ^TDataRec;
// =====================================================================================
// ジェネリックスの TList にレコード型のデータを格納してファイルに保存
// uses に Generics.Collections, System.StrUtils が必要
// =====================================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
LListRec : TList<TDataRec>;
LDataRec : TDataRec;
LStream : TMemoryStream;
LFilePath : string ;
LIndex : Integer;
begin
LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin' ;
Memo1.Lines.Clear;
LListRec := TList<TDataRec>.Create;
try
// テスト用のデータを格納
for LIndex := 0 to 4 do begin
// レコード型の初期化
// 初期化を忘れると正しいデータとして格納できないことがある
LDataRec := System.Default (TDataRec);
// string 型の文字列を Char 型の文字配列にコピー
StrPCopy(LDataRec.Text, DupeString('文字列' , LIndex + 1 ));
LDataRec.Value := (LIndex + 1 ) * 100 ;
LListRec.Add(LDataRec);
end ;
LStream := TMemoryStream.Create;
try
for LIndex := 0 to LListRec.Count - 1 do begin
// TList に格納したデータをメモリストリームに書き込む
LDataRec := LListRec[LIndex];
LStream.Write (@LDataRec, SizeOf(LDataRec));
end ;
// メモリストリームをファイルに保存
LStream.SaveToFile(LFilePath);
finally
FreeAndNil(LStream);
end ;
finally
FreeAndNil(LListRec);
end ;
end ;
// =====================================================================================
// ファイルからデータを読み込んでジェネリックスの TList に格納
// uses に Generics.Collections が必要
// =====================================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
LListRec : TList<TDataRec>;
LDataRec : TDataRec;
LStream : TMemoryStream;
LFilePath : string ;
LText : string ;
LIndex : Integer;
begin
LFilePath := ExtractFilePath(Application.ExeName) + 'DATA.bin' ;
if not FileExists(LFilePath) then Exit;
Memo1.Lines.Clear;
LListRec := TList<TDataRec>.Create;
try
LStream := TMemoryStream.Create;
try
// メモリストリームにファイルの内容をロードする
LStream.LoadFromFile(LFilePath);
while True do begin
// メモリストリームからデータを読み込む
LStream.Read (LDataRec, SizeOf(LDataRec));
// 読み込んだデータを TList に格納
LListRec.Add(LDataRec);
if LStream.Position >= LStream.Size then Break;
end ;
finally
FreeAndNil(LStream);
end ;
// 格納したデータを表示
for LIndex := 0 to LListRec.Count - 1 do begin
LDataRec := LListRec[LIndex];
LText := IntToStr(LDataRec.Value) + ' ' + string (LDataRec.Text);
Memo1.Lines.Add(LText);
end ;
finally
FreeAndNil(LListRec);
end ;
end ;
23_ レコード型の保存と String 型や動的配列の変数
レコード型のメンバに String 型の変数があった場合,レコードごとにそれらの文字列の長さが違っていても,SizeOf 関数で取得するレコード型の変数のサイズは同じです.
String 型の変数はポインタです.レコード型のメンバとしての String 型の変数のサイズは,このポインタのサイズとなります.ポインタのサイズは 32 ビットの EXE では 4 バイトです.ストリームに書き込むと,String 型の変数の位置にはポインタとしてのメモリアドレスの値だけが書き込まれます.レコード型の String 型の文字列はストリームには書き込まれません.したがって,保存して読みも出しても文字列としてのデータは取得できません.通常は例外が発生します.
PChar 型,動的配列,クラス型等の変数も同じです.数値型の静的配列であれば,ストリームに全ての要素のデータを書き込むことができます.静的配列はポインタではありません.
このことから,レコード型の文字列のメンバとして,一般的に,要素数が固定の Char 型の静的な文字配列が使用されています.
String 型や動的配列等のメンバも含めて保存しいた場合は,TComponent の派生クラスを生成して,それをリストに格納する方法があります.