Delphi Programming / Object Pascal
[掲載 2016年05月17日] [更新 2019年06月09日] Delphi 一般・その他 |
型宣言と型宣言なしの配列 - 整数型配列の場合 |
動作確認等 |
Windows 7 U64(SP1) + Delphi XE(UP1) Pro |
Delphi では,type 節でいろいろな「型」を宣言することができます,変数の定義の際,それらの「型」を指定することができます.
代表的なのは,コンポーネント等のクラスです.クラスにはプロパティ等,いろいろなメンバがあります.それらのメンバもまた type で宣言した「型」であることがあります.type で宣言した「型」のインスタンスはポインタです.
本ページでは,整数型の配列の「型」を例にして,型宣言した整数型の配列を使用する場合と,型宣言の整数型配列を使用しない場合のテストしています.
具体的には,以下の 5 つの型をテストしています. |
var
Array1 : TIntegerDynArray;
Array2 : array of Integer;
Array3 : array [0..9] of Integer;
Array4 : TArray<Integer>;
Array5 : TList<Integer>;
System.Types には TIntegerDynArray 以外の数値型の動的配列の型が定義されています. |
|
図1
型宣言の例
- TForm1 というクラス型の宣言の例
- TForm1 はポインタである
- 継承元の TForm というクラスもまた型宣言されている
|
[備考 1]
整数型の配列ではなく,(文字列ではない) Char 型の文字配列の静的配列,動的配列を扱うサンプルが以下の記事にあります.
|
[備考 2]
一般的に,配列に限らずオブジェクトは型宣言して使用した方が使い勝手はいいと言えます.型宣言の際,そのポインタ型を定義しておくと,更に使い勝手がよくなると言えます. |
[備考 3]
要素数の多い配列を扱う場合,ローカル変数だとメモリの確保に失敗して,実行時にメモリアクセス違反の例外が発生することがあります.その場合は配列変数をグローバル変数にします. |
01_型宣言による動的な整数配列 - インスタンスの代入 |
整数型の配列は,Types.pas で次のように定義されています.この TIntegerDynArray 型を変数として整数配列を使用するテストです.
下のサンプルは,TIntegerDynArray 型の変数が,クラス型変数のように,そのインスタンスの代入が可能かを確認するためのコードです. |
リスト1
整数型の配列 TIntegerDynArray の型宣言文 |
type
TIntegerDynArray = array of Integer;
まず,TIntegerDynArray 型の整数配列を 2 つ用意します.TIntegerDynArray
は型宣言では配列要素の数は設定していません.配列の要素数は実行時に設定します.これを動的配列と言います.
片方の配列要素数,つまりサイズを設定し,そこに数値を代入します.次に, |
という演算を実行します.これでインスタンスの代入となります.これで両方の配列のポインタ (の値) が同じになり,共に同じ要素にアクセスできるようになります.
Array2 は要素数を設定してはいませんが,Array1 と同じになります.確認のために,片方の配列の要素の値を変更してみると,別の配列の要素の値も変わることが確認できます. |
|
図2
配列のインスタンスの代入
- 変数名は違うが,インスタンスが同じなので,一方の配列の要素の値を変更すると,他の配列の要素の値も変わる
|
リスト2
整数型の配列 TIntegerDynArray 型のテスト
Array2 := Array1; はインスタンスの代入であって,配列要素のコピーではない |
//=============================================================================
// 型宣言した配列型(TIntegerDynArray)のインスタンスは代入が可能
//
// TIntegerDynArrayの使用にはusesにTypesが必要
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
Array1 : TIntegerDynArray;
Array2 : TIntegerDynArray;
i : Integer;
begin
Memo1.Lines.Clear;
//Array1のサイズを設定して各要素に値を代入
SetLength(Array1, 10);
for i := Low(Array1) to High(Array1) do begin
Array1[i] := i;
end;
//インスタンスの代入
//両方の配列を指す位置(ポインタ)が同じになる
Array2 := Array1;
Array1[0] := 5;
//各配列の要素の値を表示
DispArray(' Array1: ', Array1);
DispArray(' Array2: ', Array2);
end;
//-----------------------------------------------------------------------------
// 結果を表示するためのメソッド
// Low,Highは配列型のインデックスの下限と上限を返す
//-----------------------------------------------------------------------------
procedure TForm1.DispArray(const AMsg: string; const AArray: TIntegerDynArray);
var
i : Integer;
LText : string;
begin
LText := AMsg + ' ';
for i := Low(AArray) to High(AArray) do begin
LText := LText + IntToStr(AArray[i]) + ' '
end;
Memo1.Lines.Add(LText);
end;
という演算は,クラス型のインスタンスではよく使われています.
次のコードは,TStringGrid のセルの背景色を変更する例です.
このコードでは,LStrGrid という TStringGrid 型の変数を定義して,StringGrid1 のインスタンスを代入しています.この時,LStrGrid のインスタンスは生成していません.前のサンプルで,Array2 の配列要素を設定していないのと同じです.
しかし,このサンプルコードは正常に動作します.その証拠が下の図です.もちろんエラーや例外も発生しません. |
|
図3
TSTringGrid のセルの背景色を変更 |
リスト3
TSTringGrid のセルの背景色を変更 |
//=============================================================================
// StringGrid1のOnDrawCellイベント処理
// Createしていないクラス型(コンポーネントの)変数にもインスタンスは代入できる
//=============================================================================
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
LStrGrid : TStringGrid;
begin
//LStrGridはCreateしていない(インスタンスは生成していない)
//LStrGridにStringGrid1のインスタンスを代入
LStrGrid := StringGrid1;
if (ARow > 0 ) and (ACol = 2) then begin
LStrGrid.Canvas.Brush.Style := bsSolid;
LStrGrid.Canvas.Brush.Color := clGreen;
LStrGrid.Canvas.FillRect(Rect);
end;
end;
もし,インスタンスを代入しなければ,例外が発生します.下のコードでは,LStrGrid が何であるかが全く不明の状態になっています.
クラス型の変数のインスタンスを生成するということは,その変数の型のオブジェクトが使用するメモリを確保することです.また,インスタンスの代入は,メモリ領域を共有します.
これは動的配列でも同じです.要素数を設定しないまま実行すると例外が発生します.要素数を設定するということは,その配列で使用するメモリを確保することです. |
|
図4
インスタンスが無効な場合 |
リスト4
TSTringGrid のセルの背景色を変更
インスタンスが無効なコードの例 |
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
LStrGrid : TStringGrid;
begin
if (ARow > 0 ) and (ACol = 2) then begin
LStrGrid.Canvas.Brush.Style := bsSolid;
LStrGrid.Canvas.Brush.Color := clGreen;
LStrGrid.Canvas.FillRect(Rect);
end;
end;
02_型宣言による動的な整数配列 - 配列要素のコピー |
インスタンスの代入ではなく,配列要素をコピーするには,全ての要素の値を 1 つづつ他方の配列の要素に代入します.また,本サンプルのように Copy 関数も利用できます.
これは,要素の値のコピーですから,インスタンスは同じにはなりません.したがって,Copy 関数で要素の値をコピーした後に,一方の配列の要素の値を変更しても,もう一方の配列の要素の値を変更することはできません. |
|
図5
配列要素の値のコピー
- Copy 関数で配列要素の値をコピー
- Array1 の 0 番の要素の値を変更した結果
- Array2 の要素の値は変わらない
|
リスト5
配列要素のコピー
Copy 関数を使用する例 |
//=============================================================================
// 型宣言した配列型(TIntegerDynArray)のインスタンスは代入が可能
// Copyで配列の要素をコピーするには,転送先の領域も確保しておく
//
// TIntegerDynArrayの使用にはusesにTypesが必要
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
Array1 : TIntegerDynArray;
Array2 : TIntegerDynArray;
i : Integer;
begin
Memo1.Lines.Clear;
//Array1のサイズを設定して各要素に値を代入
SetLength(Array1, 10);
for i := Low(Array1) to High(Array1) do begin
Array1[i] := i;
end;
//Array1の値をArray2にコピー
//インスタンスが同じになるわけではない
Array2 := Copy(Array1);
Array1[0] := 5;
//各配列の要素の値を表示
DispArray(' Array1: ', Array1);
DispArray(' Array2: ', Array2);
end;
//-----------------------------------------------------------------------------
// 結果を表示するためのメソッド
// Low,Highは配列型のインデックスの下限と上限を返す
//-----------------------------------------------------------------------------
procedure TForm1.DispArray(const AMsg: string; const AArray: TIntegerDynArray);
var
i : Integer;
LText : string;
begin
LText := AMsg + ' ';
for i := Low(AArray) to High(AArray) do begin
LText := LText + IntToStr(AArray[i]) + ' '
end;
Memo1.Lines.Add(LText);
end;
配列要素の値はメモリに格納されています.したがって,Move 関数等を使用して,メモリの値をコピーすれば要素の値のコピーとなります.その例です. |
リスト6
配列要素の値のコピー.メモリの値をコピーする例 |
//=============================================================================
// 型宣言した配列型(TIntegerDynArray)のインスタンスは代入が可能
// Moveで配列の要素をコピーするには,転送先の領域も確保しておく
//
// 以下でも要素のコピーができる
// CopyMemory(Array2, Array1, LSize);
// MoveMemory(Array2, Array1, LSize);
//
// TIntegerDynArrayの使用にはusesにTypesが必要
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
Array1 : TIntegerDynArray;
Array2 : TIntegerDynArray;
LSize : Integer;
i : Integer;
begin
Memo1.Lines.Clear;
//Array1,Array2のサイズを設定して各要素に値を代入
SetLength(Array1, 10);
SetLength(Array2, 10);
for i := Low(Array1) to High(Array1) do begin
Array1[i] := i;
end;
//Array1の値をArray2にコピー
//インスタンスが同じになるわけではない
LSize := Length(Array1) * SizeOf(Integer);
Move(Array1[0], Array2[0], LSize);
Array1[0] := 5;
//各配列の要素の値を表示
DispArray(' Array1: ', Array1);
DispArray(' Array2: ', Array2);
end;
//-----------------------------------------------------------------------------
// 結果を表示するためのメソッド
// Low,Highは配列型のインデックスの下限と上限を返す
//-----------------------------------------------------------------------------
procedure TForm1.DispArray(const AMsg: string; const AArray: TIntegerDynArray);
var
i : Integer;
LText : string;
begin
LText := AMsg + ' ';
for i := Low(AArray) to High(AArray) do begin
LText := LText + IntToStr(AArray[i]) + ' '
end;
Memo1.Lines.Add(LText);
end;
03_型宣言でない動的な整数配列 - 配列型の変数を定義 |
前項では,type 節で「宣言した型」を変数の定義に使用しました.以下は,変数の定義の際,整数型の配列の「型を定義」して使用する方法です.配列の要素数は,前項と同じく,実行時に設定します.したがって,これも動的配列ということになります.
この方法で定義した配列は完全に独立しています.インスタンスの代入はできません.実行しようとすると,互換性の無い型のエラーとなります. |
//[DCC エラー] Unit1.pas(73): E2008 互換性の無い型です
Array2 := Array1;
お互いの配列要素を参照するには,要素ごとに代入すればできます.他の配列要素の値をまとめてコピーしたいのであれば,本サンプルのように,CopyMemory 関数等が使用できます.Copy 関数は,互換性のない型の間では使用できません. |
配列 Arrray1, Array2 を以下のように定義すれば,インスタンスの代入が可能です.
この代入により,Array1, Array2 は全く同じものとなります.一方の要素の値を変更すれば,別の配列の該当する要素の値も変わります.また,型宣言した整数型の配列と同じように,Copy 関数も使用できます. |
procedure TForm1.Button4Click(Sender: TObject);
var
Array1, Array2 : array of Integer;
i : Integer;
begin
Memo1.Lines.Clear;
//Array1のサイズを設定して各要素に値を代入
SetLength(Array1, 10);
for i := Low(Array1) to High(Array1) do begin
Array1[i] := i;
end;
//インスタンスの代入
//要素の値のコピー(代入)ではない
Array2 := Array1;
end;
|
図6
動的な整数型配列の値のコピー |
リスト7
型宣言されている整数型配列ではなく,整数型の型を定義して使用する例
動的配列の場合 |
//=============================================================================
// 型宣言でない単なる変数前言の動的配列
// 配列の要素をコピーするには,転送先の領域も確保しておく
//
// 以下でも要素のコピーができる
// CopyMemory(Array2, Array1, LSize);
// MoveMemory(Array2, Array1, LSize);
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
Array1 : array of Integer;
Array2 : array of Integer;
LSize : Integer;
i : Integer;
begin
Memo1.Lines.Clear;
//Array1,Array2のサイズを設定して各要素に値を代入
SetLength(Array1, 10);
SetLength(Array2, 10);
for i := Low(Array1) to High(Array1) do begin
Array1[i] := i;
end;
//Array1の値をArray2にコピー
LSize := Length(Array1) * SizeOf(Integer);
Move(Array1[0], Array2[0], LSize);
Array1[0] := 5;
//各配列の要素の値を表示
DispArray(' Array1: ', Array1);
DispArray(' Array2: ', Array2);
end;
//-----------------------------------------------------------------------------
// 結果を表示するためのメソッド
// Low,Highは配列型のインデックスの下限と上限を返す
//-----------------------------------------------------------------------------
procedure TForm1.DispArray(const AMsg: string; const AArray: array of Integer);
var
i : Integer;
LText : string;
begin
LText := AMsg + ' ';
for i := Low(AArray) to High(AArray) do begin
LText := LText + IntToStr(AArray[i]) + ' '
end;
Memo1.Lines.Add(LText);
end;
04_型宣言でない静的な整数配列 - 要素数指定の配列型の変数 |
前項と同じで,変数の定義の際,整数型の配列の「型を定義」して使用する方法です.ただし,配列の要素数を変数の定義の際に設定します.これを静的配列と言います.
この方法で定義した配列は完全に独立しています.インスタンスの代入はできません.実行しようとすると,互換性の無い型のエラーとなります. |
//[DCC エラー] Unit1.pas(73): E2008 互換性の無い型です
Array2 := Array1;
お互いの配列要素を参照するには,要素ごとに代入すればできます.他の配列要素の値をまとめてコピーしたいのであれば,本サンプルのように,CopyMemory 関数等が使用できます.Copy 関数は,互換性のない型の間では使用できません. |
|
図7
静的な整数型配列の値のコピー |
リスト8
型宣言されている整数型配列ではなく,整数型の型を定義して使用する例
静的配列の場合 |
//=============================================================================
// 静的配列
// 配列要素数を変数宣言で確保する場合
//
// 以下でも要素のコピーができる
// CopyMemory(Array2, Array1, LSize);
// MoveMemory(Array2, Array1, LSize);
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
Array1 : array [0..9] of Integer;
Array2 : array [0..9] of Integer;
LSize : Integer;
i : Integer;
begin
Memo1.Lines.Clear;
//各要素に値を代入
for i := Low(Array1) to High(Array1) do begin
Array1[i] := i;
end;
//Array1の値をArray2にコピー
LSize := Length(Array1) * SizeOf(Integer);
Move(Array1[0], Array2[0], LSize);
Array2[0] := 5;
//各配列の要素の値を表示
DispArray(' Array1: ', Array1);
DispArray(' Array2: ', Array2);
end;
//-----------------------------------------------------------------------------
// 結果を表示するためのメソッド
// Low,Highは配列型のインデックスの下限と上限を返す
//-----------------------------------------------------------------------------
procedure TForm1.DispArray(const AMsg: string; const AArray: array of Integer);
var
i : Integer;
LText : string;
begin
LText := AMsg + ' ';
for i := Low(AArray) to High(AArray) do begin
LText := LText + IntToStr(AArray[i]) + ' '
end;
Memo1.Lines.Add(LText);
end;
05_プロシージャや関数の引数としてのオープン配列パラメータ |
前項のサンプルでは要素数を指定した静的配列を変数として設定しています.しかし,プロシージャの引数には配列要素の指定なしで渡しています.このように,異なるサイズの配列を同じ手続き,あるいは関数に渡せることをオープン配列パラメータと言っています.静的配列,動的配列のどちらでも利用できます.
下に示すサンプルコードでは,2 番目の配列をプロシージャー (手続き) に渡す際,Slice 関数で要素数を 4 に制限しています.その結果,プロシージャ内で配列の要素を取得して表示した結果も 4 番目までとなっています. |
|
図8
実行結果 |
リスト9
オープン配列パラメータと Slice 関数 |
//=============================================================================
// 静的配列の要素のコピー
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
Array1 : array [0..9] of Integer;
Array2 : array [1..10] of Integer;
LSize : Integer;
i : Integer;
begin
Memo1.Lines.Clear;
//各要素に値を代入
for i := Low(Array1) to High(Array1) do begin
Array1[i] := i;
end;
//Array1の値をArray2にコピー
LSize := Length(Array1) * SizeOf(Integer);
Move(Array1[0], Array2[1], LSize);
Array2[1] := 5;
//各配列の要素の値を表示
DispArray(' Array1: ', Array1);
DispArray(' Array2: ', Slice(Array2, 4));
end;
//-----------------------------------------------------------------------------
// 結果を表示するためのメソッド
// Low,Highは配列型のインデックスの下限と上限を返す
//-----------------------------------------------------------------------------
procedure TForm1.DispArray(const AMsg: string; const AArray: array of Integer);
var
i : Integer;
LText : string;
begin
LText := AMsg + ' ';
for i := Low(AArray) to High(AArray) do begin
LText := LText + IntToStr(AArray[i]) + ' '
end;
Memo1.Lines.Add(LText);
end;
Delphi 2009 で実装されたジェネリックスの TArray は汎用的な配列用です.
以下のように型宣言されています.
インスタンスの代入ができ,Copy 関数で要素のコピーもできます.ソート用の Sort メソッド等も実装されています. |
type
TArray<T> = array of T;
|
図9
インスタンスの代入
- 変数名は違うが,インスタンスが同じなので,一方の配列の要素の値を変更すると,他の配列の要素の値も変わる
|
|
図10
配列の要素のコピー
- Array1 と Array2 はインスタンスが異なるので,一方の要素の値を変更しても,他の配列の要素の値は変わらない
|
|
図11
Sort メソッドのテスト |
リスト10
TArray<T> による動的な整数型の配列のテスト
uses に Generics.Collections が必要 |
//=============================================================================
// 動的配列 TArray<T>
// インスタンスの代入
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
Array1 : TArray<Integer>;
Array2 : TArray<Integer>;
i : Integer;
begin
Memo1.Lines.Clear;
SetLength(Array1, 10);
//各要素に値を代入
for i := Low(Array1) to High(Array1) do begin
Array1[i] := i;
end;
//Array1をArray2に代入
//インスタンスの代入となる
Array2 := Array1;
//これでArray2の該当要素の値も変わる
Array1[0] := 5;
//各配列の要素の値を表示
DispArray(' Array1: ', Array1);
DispArray(' Array2: ', Array2);
end;
//=============================================================================
// 動的配列 TArray<T>
// 配列要素のコピー
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
Array1 : TArray<Integer>;
Array2 : TArray<Integer>;
i : Integer;
begin
Memo1.Lines.Clear;
SetLength(Array1, 10);
//各要素に値を代入
for i := Low(Array1) to High(Array1) do begin
Array1[i] := i;
end;
//Array1の要素をArray2にコピー
//インスタンスの代入ではない
Array2 := copy(Array1);
Array1[0] := 5;
//各配列の要素の値を表示
DispArray(' Array1: ', Array1);
DispArray(' Array2: ', Array2);
end;
//=============================================================================
// 動的配列 TArray<T>
// 配列要素のソート(整列)
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
Array1 : TArray<Integer>;
i : Integer;
begin
Memo1.Lines.Clear;
SetLength(Array1, 10);
//各要素に値を代入
for i := Low(Array1) to High(Array1) do begin
Array1[i] := High(Array1) - i;
end;
//各配列の要素の値を表示
DispArray(' Array1: ', Array1);
//ソートして結果を表示
TArray.Sort<Integer>(Array1);
DispArray(' Array1: ', Array1);
end;
//-----------------------------------------------------------------------------
// 結果を表示するためのメソッド
//-----------------------------------------------------------------------------
procedure TForm1.DispArray(const AMsg: string; const AArray: TArray<Integer>);
var
LText : string;
LValue : Integer;
begin
LText := AMsg + ' ';
for LValue in AArray do begin
LText := LText + IntToStr(LValue) + ' '
end;
Memo1.Lines.Add(LText);
end;
TList<T> はリスト操作用のジェネリックスです.リストも配列の一種と考えられますから,動的な配列の操作に使用できます.以下のように型宣言されています.
TList<T> の ToArray メソッドを使用すると,TArray<T> の要素に値をコピーできます. |
type
TList<T> = class(TEnumerable)
|
図12
TList<T> のインスタンスの代入
- 変数名は違うが,インスタンスが同じなので,一方のリストの要素の値を変更すると,他のリストの要素の値も変わる
|
|
図13
TList<T> の ToArray メソッドのテスト |
リスト11
TList<T> による動的な整数型の配列のテスト
uses に Generics.Collections が必要 |
//=============================================================================
// TList<T>によるリスト
// インスタンスの代入
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
List1 : TList<Integer>;
List2 : TList<Integer>;
i : Integer;
begin
Memo1.Lines.Clear;
List1 := TList<Integer>.Create;
try
//要素を追加して値をセット
for i := 0 to 9 do begin
List1.Add(i);
end;
//List1をList2に代入
//インスタンスの代入となる
List2 := List1;
List1[0] := 5;
//各リストの要素の値を表示
DispList(' List1: ', List1);
DispList(' List2: ', List2);
finally
FreeAndNil(List1);
end;
end;
//=============================================================================
// TList<T>によるリスト
// TList<T>の要素をTArray<T>にコピー
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
List1 : TList<Integer>;
Array1 : TArray<Integer>;
i : Integer;
begin
Memo1.Lines.Clear;
List1 := TList<Integer>.Create;
try
//要素を追加して値をセット
for i := 0 to 9 do begin
List1.Add(i);
end;
//リストを配列の要素に変換
Array1 := List1.ToArray;
//各配列の要素の値を表示
DispList (' List1 : ', List1);
DispArray(' Array1: ', Array1);
finally
FreeAndNil(List1);
end;
end;
//-----------------------------------------------------------------------------
// 結果を表示するためのメソッド
// TList<T>用
//-----------------------------------------------------------------------------
procedure TForm1.DispList(const AMsg: string; const AArray: TList<Integer>);
var
LText : string;
LValue : Integer;
begin
LText := AMsg + ' ';
for LValue in AArray do begin
LText := LText + IntToStr(LValue) + ' '
end;
Memo1.Lines.Add(LText);
end;
//-----------------------------------------------------------------------------
// 結果を表示するためのメソッド
// TArray<T>用
//-----------------------------------------------------------------------------
procedure TForm1.DispArray(const AMsg: string; const AArray: TArray<Integer>);
var
LText : string;
LValue : Integer;
begin
LText := AMsg + ' ';
for LValue in AArray do begin
LText := LText + IntToStr(LValue) + ' '
end;
Memo1.Lines.Add(LText);
end;
|