[ クラス | フォーム | 配列、動的配列 | 文字列 | Delphi の格言 | IDE の小技 | エッセンス ]

up

配列、動的配列

配列とは何か

array[0..255] of Char;

の意味がいまいちわかりません。静的配列、と言うらしいのですが、どのような構造になっているのでしょうか。

単純に Char 型がメモリ上に 256個隙間なくならんでいるだけの構造です。Char は1バイトですから、array[0..255] of Char 型は 256バイトのメモリ領域を占めます。静的配列の 「静的」の意味は要素(この場合 Char 型) の個数が決まっていて変更できないという意味です。[delphi-ml:32706]

top

string の配列を戻り値にするには

string の配列を返すように変更したいと思っています。その場合の関数の宣言の仕方なのですが、戻り値を配列にするにはどのように宣言すればよいのでしょうか?

function Test: array of string 

では駄目でした。この場合は下のように予め string の配列型を宣言しておくしかないのでしょうか?

type
  TStringArray: array of string;
 
function Test: TStringArray;

そうです。Pascal は型に厳しい言語なので、まったく同じ内容であっても、違う場所で宣言された型は互換性がなくなります。

ObjectPascal 言語ガイドの「データ型, 変数, 定数」のあたりのトピックスを一通り眺めると、だいたいわかるかと思います。(特に 代入互換性、型の互換性、など)

Delphi 6 なら、定義内容は同じですが Types unit に TStringDynArray があります。こちらを使う方が「型が違う」とか避けやすいでしょう。[delphi-ml:63827]

top

手続き、または関数へ配列を渡すには

例えば、配列B(静的・動的に限らず)を手続きに渡すとき、

procedure F( var A : array of Byte ) ;

のような手続きに対して、

 F( B[0] ) ;  //  F( B ) でも同じ
 F( B[1] ) ;
 F( B[2] ) ;

等のように、配列の途中から渡せる。つまり、配列を参照渡しするときは、配列の途中から渡せる。

procedure F( A : array of Byte ) ; 

もしくは、

procedure F( const A : array of Byte ) ;

のような手続きに対して、

 F( B ) ;

のみで、配列の先頭からしか渡せない。つまり、配列を値渡しするときは、配列の先頭からしか渡せない。

これで、あってるでしょうか?また、配列を値渡しするときは、手続きFの宣言を変更せず、配列の途中から渡す方法はあるのでしょうか?

えーと、できてしまうみたいですが(^^; 文法上は配列を途中から渡すことはできません。オープン配列パラメータに要素型の値や変数(B[1} 等)を渡すということは、要素数が1の配列を渡すことになるとヘルプに書いてあります。

ですから、現在は途中から渡すように書くことができますが、文法の範囲外のやりかたなので、将来の保証はないと思います。

また、渡された配列の添え字の範囲は Low, High 関数で確認できます。{$RANGECHECKS ON} でコンパイルすれば添え字がどの範囲で正当なのかチェックできます。B[1] を渡すと、A の添え字の範囲は 0..0 で A[1} は実行時に範囲チェックエラーになります。

関数などへの var や const での渡し方については、以下のようになってるみたいです。

基本的に、var は、関数・手続きに渡されたデータの変更結果が呼出元へも反映されます。(最終的にはポインタ渡ししてるのと同じなのかな?)const を付けると、渡されたデータは、関数・手続き内部で変更することができません。(もちろん例外はありますが ^^;)何も付けない場合、その中間で、関数・手続き内部で変更は可能ですが、呼出元へ、その結果が反映されることはありません。

ということで、const などを付けても基本的には渡せるデータが限定されるということはないみたいですね。var の場合は結果が返るため、定数は渡せないみたいです。[delphi-ml:51825]

top

配列に初期値を持たすには

ローカル配列に、宣言時、または宣言後にまとめて値を代入するにはうしたら良いんでしょうか?

C言語だったら

float locate[3] = { 10.0, 50.0, 20.0 };

VBだったら

Dim intArray() as integer
intArray = Array(1,2,5)

ヘルプで配列型定数を引きましょう(^^

尚、ローカル配列と配列型定数の型が「同じ」でないと代入互換性はありません。別々に宣言された配列型は宣言が全く同じでも Pascal では別の型として扱われることに注意してください。type でまず配列型の型名を宣言し、その型名を使って、配列型定数とローカル配列を宣言する必要があります。[delphi-ml:50643]

Delphiでは、こんな感じです。

array[1..3] of Double = (10.0, 50.0, 20.0);

type
  TStrArray = array[0..2] of string;

procedure TForm1.Button1Click(Sender: TObject);
var
  str1: array[0..2] of string;
  str2: array[0..2] of string;
begin
  str1 := str2; // エラー!互換性のない型です。
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  str1, str2: array[0..2] of string;
begin
  str1 := str2; // OK
end;

procedure TForm1.Button3Click(Sender: TObject);
var
  str1: TStrArray;
  str2: TStrArray;
begin
  str1 := str2; // OK
end;

top

大きさが未定な配列型の宣言は出来るか

Delphi では、要素数が未定のまま、配列型を宣言する事はできないのでしょうか。 Cではできたような気がするのですが…

仮パラメータとしてならば「オープン配列パラメータ」が使えます。[delphi-ml:13476]

procedure MyProc(arr: array of string); // オープン配列パラメータ
var
  i: Integer;
begin
  for i := Low(arr) to High(arr) do
    Form1.ListBox1.Items.Add(arr[i]);
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  StrArr1: array of string;
  StrArr2: array[0..1] of string;
  i: Integer;
begin
  SetLength(StrArr1, 3);

  for i := Low(StrArr1) to High(StrArr1) do
    StrArr1[i] := 'Hello'+IntToStr(i);

  StrArr2[0] := 'Hello';
  StrArr2[1] := 'World';

  MyProc(StrArr1); // 要素数 3 の動的配列を渡します。
  MyProc(StrArr2); // 要素数 2 の配列を渡します。
end;

top

動的配列とは何か

 +-------------------+
 |動的配列(ポインタ) | <-- SizeOf はこれの大きさを返す
 +-------------------+
         |
         +------------------+(このポインタは最初の要素を指す)
                            |
                            V 動的配列が動的に確保したメモリ
  +-------------+----------+-----+-----+-        +--------+
  |参照カウント |要素数(n) |要素0|要素1| ・・・・| 要素n-1|
  +-------------+----------+-----+-----+-        +--------+

という構造になってて、Length が返すのは上図の 要素数(n) です。

SizeOf は型のコンパイル時の静的な大きさを求める擬似関数なので、動的配列の動的な大きさを求めるような用途には使えません。SizeOf(動的配列名)はポインタの大きさ4バイトを返すだけです。[delphi-ml:60567]

top

動的配列の切捨てに SetLength は使用可能か

ヘルプによれば、「動的配列を切り捨てるには,Copy 関数に配列変数を渡し,結果を配列変数に代入します。たとえば,A が動的配列である場合,A := Copy(A, 0, 20) は A の 21 番目以降の要素を切り捨てます。」とのことですが、これを

SetLength(A, 20);

と書いてはいけないのでしょうか。

SetLength で大丈夫です。SetLength はメモリを再確保します。また切り捨てられた要素の Finalize や増えた要素の Initialize もきちんとやってくれます。ですから配列要素に文字列やインターフェースを使っても大丈夫です(^^ [delphi-ml:57158]

procedure TForm1.Button1Click(Sender: TObject);
var
  DynamicArr: array of string;
  i: Integer;
begin
  SetLength(DynamicArr, 20);

  for i := 0 to High(DynamicArr) do
    DynamicArr[i] := IntToStr(i);

  DynamicArr := Copy(DynamicArr, 0, 10); // 11 番目以降を切り詰めます

  for i := 0 to High(DynamicArr) do
    ListBox1.Items.Add(DynamicArr[i]);
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  DynamicArr: array of string;
  i: Integer;
begin
  SetLength(DynamicArr, 20);

  for i := 0 to High(DynamicArr) do
    DynamicArr[i] := IntToStr(i);

  SetLength(DynamicArr, 10);

  for i := 0 to High(DynamicArr) do
    ListBox1.Items.Add(DynamicArr[i]);
end;

top

動的配列は手動で開放する必要があるか

procedure a
var
  buf: array of byte;
begin
end;

で記述されるような動的配列は使い終わったら Dispose 等で開放しなくて

はならないのでしょうか。それとも必要がなくなれば自動的に開放されるのでしょうか。

動的配列の明示的な解放は、基本的には不要です。

デバッガのCPUウィンドウで見ると分かりますが、スコープから出たときにcall @DynArrayClearが自動的に実行され、参照カウントの処理とメモリの解放が行われます。

もちろんコンパイラ任せにせず明示的に解放するのも良い習慣だと思います。またスコープが広い動的配列では、明示的に解放することで不要なメモリを速やかに解放できるという利点もあります。

いろいろ試してみた結果、AnsiString とよく似ているようです。

  1. 自動変数の場合、自動的に 長さ 0 で初期化され、スコープ外に出ると 自動的に開放される。
  2. レコードのメンバとして動的配列が有り、New でレコードを動的に作る場合、自動的に 長さ 0 に初期化される。レコードが Dispose されると 自動的に開放される。 オブジェクトでも New を コンストラクタ、Disapose をデストラクタに置き換えれば同様。
  3. レコードのメンバとして動的配列が有り、GetMem でレコードを動的に作る場合、動的配列は自動的に初期化されず Initialize 手続きでレコードを初期化要。レコードを Finalize すると 動的配列も開放される。
  4. 動的配列同士を代入すると、代入先の動的配列は開放され、代入もとの動的配列の参照カウントが増え、2つの変数は同じ動的配列を指すようになる。
  5. 4. の状態で 配列要素を書き換えても 配列のコピーは起きないつまり AnsiString のような Copy on Write は採用されていない。

というわけで GetMem でレコードを取得するとき以外は 従来の固定長配列とほぼ同じ取り扱いで よいようです。[delphi-ml:35172]

top

[ クラス | フォーム | 配列、動的配列 | 文字列 | Delphi の格言 | IDE の小技 | エッセンス ]


up

更新日:2005-02-12