Edited at

<6> 構造化型の概要と配列型 (標準 Pascal 範囲内での Delphi 入門)


6. 構造化型の概要と配列型

単純型 (順序型実数型) は構造を持たない型ですが、Pascal には構造を持つ型として構造化型 (構造データ型)ポインタ型があります。Delphi には他にも文字列型、手続き型、バリアント型、型識別子があります。



構造化型の特徴はその要素の型と構造化の方法にあります。

See also:


構造化型 (Structured Types)

構造化型 (構造データ型) には、配列型、レコード型 (レコードデータ型)集合型 (集合データ型)ファイル型 (ファイルデータ型) があります。Delphi には他の構造型として、クラス型、クラス参照型、インターフェイス型があります。

構造化型には予約語 packed を付けることで、データ格納領域の圧縮を行えます。

デフォルトでは構造化型の値はパックされておらず、高速にアクセスためにワード境界またはダブルワード境界でアラインメントされます。つまり高速化と引き換えにデータ格納領域を多く消費します。

予約語 packed を付けてパックされた場合には、データ格納領域の消費は最少になりますがアクセス速度が低下します。メモリ内のデータをそのままファイルに書き出すような用途ではパックを使う必要があります。

See also:


6.1. 配列型 (Arrays)

配列型は、基底型と呼ばれる同じ型の要素 (要素型) のインデックス付きの集まりを表します。インデックスは添字とも呼ばれ、計算が可能です。この型を添字型と呼びます。添字型は任意の順序型です。

See also:


(6.1.1.) 静的配列 (Static Arrays)

新しい静的な配列型を定義するには次のようにします。

type

識別子 = array [添字型] of 基底型

もちろん、変数宣言と同時に定義可能です。

type

TDayOfTheWeek = (Mon, Tue, Wed, Thu, Fri, Sat, Sun);
var
Buf: array [0..1023] of Char;
WorkingHours: packed array [TDayOfTheWeek] of Integer;

基底型は任意の型なので、配列型の基底型に配列型を指定する事も出来ます。

var

Sheet: array [1..100] of array [1..100] of Integer;

基底型が配列の時、その配列型は多次元配列となります。上記の例は二次元配列ですが、三次元配列以上も定義できます。

var

Sheet1: array [0..99] of array [0..99] of Integer;
Sheet2: array [0..99, 0..99] of Integer;

添字型をカンマ , で括って多次元配列を作る事も出来ます。上記例の Sheet1 と Sheet2 はどちらも同じ 100x100 のサイズの Integer 型の二次元配列です。

配列の要素 (要素型) に値を代入するには次のようにします。


StaticArrayTest.pas

program StaticArrayTest(output);

var
Buf: packed array [1..255] of Char;
Sheet: array [0..99] of array [0..99] of Integer;
v: Integer;
begin
Buf[1] := 'A';
Sheet[0][0] := 3;
Sheet[0, 0] := 5;
Sheet[0][1] := 7;
v := Sheet[0][0] * Sheet[0][1];
Writeln(v);
end.

要素型 Sheet[0][0] と Sheet[0, 0] は Integer 型で、同じ要素を指しているため、上記コードの実行結果は 35 となります。

Delphi では配列の下限と上限を知るために、宣言済み関数 High()Low() を使う事ができます。例えば配列を初期化する次のコードは


StaticArrayInit1.pas

program StaticArrayInit1(output);

var
Buf: packed array [1..255] of Char;
i: Integer;
begin
for i:=1 to 255 do
Buf[i] := Chr(0);
end.

このようにも書けます。


StaticArrayInit2.pas

program StaticArrayInit2(output);

{$APPTYPE Console}
var
Buf: packed array [1..255] of Char;
i: Integer;
begin
for i:=Low(Buf) to High(Buf) do
Buf[i] := #$00;
end.

See also:


(6.1.2.) 動的配列 (Dynamic Arrays)

標準 Pascal には動的配列はありません。Delphi では次のようにして動的配列を宣言します。

var

Buf: array of Char;
Sheet: array of array of Integer;
begin
SetLength(Buf, 100); // 0..99
SetLength(Sheet, 10, 10); // 0..9, 0..9
end.

配列の要素数を変更するには宣言済み関数 SetLength() を使います。

上記コードでは Sheet を 10x10 の二次元配列として確保しましたが、次のような確保の仕方もあります。

var

Sheet: array of array of Integer;
i: Integer;
begin
// 一次元目の確保
SetLength(Sheet, 10);
// 二次元目の確保
for i:=Low(Sheet) to High(Sheet) do
SetLength(Sheet[i], i + 1);
end;

上記コードは階段状の二次元配列を確保します。この確保の方法について Delphi のヘルプには次のような記述があります。


特定の次元の配列の長さがすべて同じではない多次元動的配列を作成することもできます。


これは碁盤状でない二次元動的配列、いわゆるジャグ配列 (不規則配列) を作るための方法を示唆しています。


(6.1.3.) 静的配列と動的配列の異なる振る舞い

動的配列変数の実態はポインタであるため、静的配列とは異なる振る舞いになる事があります。


ArrayTest.pas

program ArrayTest(output);

{$APPTYPE Console}
var
A1, B1: array [0..9] of Integer;
A2, B2: array of Integer;
begin
A1[0] := 1;
B1 := A1;
B1[0] := 2;
Writeln(A1[0]); // 1
Writeln(B1[0]); // 2

SetLength(A2, 10);
A2[0] := 1;
B2 := A2;
B2[0] := 2;
Writeln(A2[0]); // 2
Writeln(B2[0]); // 2
readln
end.

上記コードでの静的配列と動的配列の振る舞いの違いは B2 := A2; が配列の中身のコピーではなく、ポインタのコピーとなってしまうために起こります。この問題を回避するためには Copy() 関数を使います。


ArrayTest.pas

program ArrayTest(output);

{$APPTYPE Console}
var
A1, B1: array [0..9] of Integer;
A2, B2: array of Integer;
begin
A1[0] := 1;
B1 := A1;
B1[0] := 2;
Writeln(A1[0]); // 1
Writeln(B1[0]); // 2

SetLength(A2, 10);
A2[0] := 1;
B2 := Copy(A2); // 配列の中身のコピー
B2[0] := 2;
Writeln(A2[0]); // 1
Writeln(B2[0]); // 2
readln
end.

動的二次元配列の場合はどうでしょうか?


ArrayTest2.pas

program ArrayTest2(output);

{$APPTYPE Console}
var
A, B: array of array of Integer;
i: Integer;
begin
SetLength(A, 10, 10);
A[9, 9] := 1;
B := Copy(A);
B[9, 9] := 2;

Writeln(A[9, 9]); // 2
Writeln(B[9, 9]); // 2
end.


Copy() を使ってもダメでしたね。正しくは次のようになります。


ArrayTest2.pas

program ArrayTest2(output);

{$APPTYPE Console}
var
A, B: array of array of Integer;
i: Integer;
begin
SetLength(A, 10, 10);
A[9, 9] := 1;

SetLength(B, Length(A));
for i:=Low(A) to High(A) do
B[i] := Copy(A[i]);

B[9, 9] := 2;

Writeln(A[9, 9]); // 1
Writeln(B[9, 9]); // 2
readln
end.


二次元の動的配列変数は二次元配列を指す単一のポインタではないため、上記のような処理を行う必要があります。とりあえず「動的配列は代入してはいけない」と覚えておけばトラブルは少ないと思います。

See also:


(6.1.4.) 配列定数とグローバル配列変数の初期化

Delphi では次のような配列定数が使えます。


ArrayConstantsTest.pas

program ArrayConstantsTest;

{$APPTYPE CONSOLE}

// グローバル配列定数
const
Digits1: array [0..9] of Char =
('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');

// グローバル配列変数と初期化
var
Digits2: array [0..9] of Char =
('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');

procedure Sub;
//var
// ローカル配列変数は初期化できない
// Digits3: array [0..9] of Char =
// ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9');
begin
...
end; { Sub }
begin
...
end.


さらに Delphi 2009 以降には任意の型の「空の値」または「ゼロ値」または「ヌル値」を返すジェネリック型関数 Default() があります 1

function Default(X: 型識別子): <T>

これを使って配列等を初期化する事ができます。

type

TIntArray10 = array [0..9] of Integer;
var
Arr: TIntArray10;
begin
Arr := Default(TIntArray10);
end.

See also:


6.2. 文字配列 (標準 Pascal の文字列型)

標準 Pascal で言う文字列型はちょっと特殊なパックされた文字配列です。

packed array [1..N] of Char;

添字型は 1..N の部分範囲型でなくてはいけません (N は 2 以上)。

var

Buf1: packed array [1..10] of Char;
Buf2: packed array [1..10] of Char;
Buf3: packed array [1..20] of Char;

例えばこのような文字列型があった場合、

  Buf2 := Buf1; (* OK *)

Buf3 := Buf1; (* NG *)

等しいサイズの文字列型への代入は可能ですが、異なるサイズの文字列型へは代入できません。

文字列型への文字列の代入ですが、配列型なので、

  Buf[ 1] := 'H';

Buf[ 2] := 'e';
Buf[ 3] := 'l';
Buf[ 4] := 'l';
Buf[ 5] := 'o';
Buf[ 6] := Chr(0);
Buf[ 7] := Chr(0);
Buf[ 8] := Chr(0);
Buf[ 9] := Chr(0);
Buf[10] := Chr(0);

このようにするか、

  Buf := 'Hello     ';

空白詰めでよければ長さの等しい文字列定数を代入します。等しいサイズの文字列型への代入は可能なのでしたよね?


(6.2.1.) 文字列型の何が特殊なのか

この文字列型の何が特殊なのかを知るには次のコードをコンパイルしてみるのが手っ取り早いでしょう。

var

Buf1: packed array [1..10] of Char;
Buf2: packed array [1..10] of Char;
Buf3: packed array [0..9] of Char;
Buf4: packed array [0..9] of Char;
begin
Buf2 := Buf1; (* OK *)
Buf4 := Buf3; (* NG *)
end.

Buf1 と Buf2 の代入は OK なのに、Buf3 と Buf4 の代入は通りません (Delphi だとどちらも通りません)。これは配列の定義と変数の宣言を同時に行った場合、別々に宣言した変数は異なる型とみなされるために起こります。

var

Buf1, Buf2: packed array [1..10] of Char;
Buf3, Buf4: packed array [0..9] of Char;
begin
Buf2 := Buf1; (* OK *)
Buf4 := Buf3; (* OK *)
end.

上記のように変数をカンマで区切って宣言すれば同じ型だとみなされます。文字列型が特殊な実装になっているのは文字列定数を代入できるようにするためだと思われます。

type

Int10 = array [0..9] of Integer;
var
IntArray1: Int10;
IntArray2: Int10;
begin
IntArray2:= IntArray1; (* OK *)
end.

構造化型は一旦型として定義してから使う癖を付けておくとよいでしょう。


(6.2.2.) Delphi の文字列型 (String Types)

Delphi には文字配列ではない文字列型がありますが構造化型ではありません。


StringTest3.pas

StringTest.pas

program StringTest(output);
{$APPTYPE Console}
var
s: string;
begin
s := 'Hello,' + 'world.';
Writeln(s);
end.

Delphi の文字列型には 関係演算子 =, <>, <, <=, >=, > が使えます。 + 演算子は 2 つの文字列を連結します。

演算子
意味
説明

+
連結
2 つの文字列を連結する

また、次の宣言済み手続き/関数も使えます。

手続き /
関数
説明

Concat()
2 つ以上の文字列を連結して 1 つの文字列にします。

Copy()
文字列の部分文字列を返します。

Delete()
文字列から部分文字列を削除します。

Insert()
文字列の指定された位置に部分文字列を挿入します。

Length()
文字列内の文字数を返します。

SetLength()
文字列変数の長さを設定します。

SetString()
指定した文字列の内容と長さを設定します。

Str()
文字列を書式設定し変数に格納して返します。

Val()
文字列を数値表現に変換します。

See also:


(6.2.3.) Pascal String (パスカル文字列)

一般的に Pascal String (パスカル文字列) と呼ばれる文字列型 2 3 は標準 Pascal には存在しません。

構造的には packed array [0..N] of Char 、N は最大で 255 です。要素 [0] には文字列長が格納されています。文字列は要素 [1] 以降に格納されています。



文法的には現在の Delphi の String 型と同じ使い方ですが、最大でも 255 文字しか扱えません。Turbo Pascal や 初代 Delphi の String 型もこのパスカル文字列でした。 


PascalStringTest.pas

program PascalStringTest(output);

var
s: string;
s2: string[10]; (* 長さ 10 の Pascal String *)
begin
s := 'Hello,' + 'world.';
Writeln(s);
end.

現在の Delphi ではこの Pascal String を ShortString (短い文字列) として扱えます。互換性のため、String に最大長を指定したものも ShortString 扱いとなります。


ShortStringTest.pas

program ShortStringTest(output);

var
s: ShortString;
s2: String[10]; (* 長さ 10 の ShortString *)
begin
s := 'Hello,' + 'world.';
Writeln(s);
Writeln(Ord(s[0])); (* s[0] には文字列長が格納されている *)
end.

下位互換性を保つため、ANSI 版 Delphi では、string を短い文字列とみなす {$H-} コンパイラ指令があります。Unicode 版 Delphi にもこのコンパイラ指令はあるのですが、Unicode 版 Delphi の string は UnicodeString であるため意味を成さず、コンパイラに無視されます。

See also:


(6.2.4.) alfa 型

クラシック Pascal の独自拡張である Pascal 6000-3.4 では、文字列型である alfa 型が次のように定義されていました。

type

alfa = packed array [1..10] of char;


(6.2.5.) NULL 終端文字列

NULL 終端文字列は NUL(#0)で終わる文字配列で、インデックスがゼロから始まります。配列には長さを指示する手段がないため、最初の NUL 文字で文字列の終わりを表します。

type

TCharArray = array[0..15] of Char;
TAnsiCharArray = array[0..15] of AnsiChar;
TWideCharArray = array[0..15] of WideChar;

コンパイラ指令 {$X} が有効 ({$X}) である場合、NULL 終端文字列に対して文字列定数の代入が可能となります。

type

TCharArray = array[0..15] of Char;
var
CA: TCharArray;
begin
CA := 'Hello, World.'; // {$X-} だとエラー
end.

NULL 終端文字列はポインタや動的変数 (10.1.節)と一緒に使われる事が多いです。

var

P: PChar;
begin
P := 'Hello, World.'; // 文字列定数を指すポインタの代入
// P が指す領域に文字列定数を代入しているのではない。
end.

ANSI 版 Delphi (Delphi 2007 以前)Unicode 版 Delphi (Delphi 2009 以降) それぞれで PChar 型の定義が異なるので注意が必要です。

Delphi
PChar
PAnsiChar
PWideChar

ANSI 版
^AnsiChar
(^Char)
^AnsiChar
^WideChar

Unicode 版
^WideChar
(^Char)
^AnsiChar
^WideChar

NULL 終端文字列は + 演算子による文字列連結はできません。基本的に NULL 終端文字列はライブラリ (System.SysUtils) にあるルーチンで操作します。

もちろん 標準 Pascal でも NULL 終端文字列は扱えるのですが 4、それをサポートするためのルーチンが一切用意されていないため実際に利用するのは難しいでしょう

See also:


6.3. パックとアンパック

Pack() 手続きはパックされていない配列変数 (array) の内容をパックされている配列変数 (packed array) へコピーするものです。

Unpack() 手続きはパックされている配列変数 (packed array) の内容をパックされていない配列変数 (array) へコピーするものです。

基本的にコピー元とコピー先の要素数は同じである必要があります。


PackedTest.pas

program PackedTest(output);

var
s1: packed array [1..12] of Char;
s2: array [1..12] of Char;
begin
s1 := 'Hello,world.';
Unpack(s1, s2, 1); (* s1 の内容を s2 へ *)
s1 := ' ';
Pack(s2, 1, s1); (* s2 の内容を s1 へ *)
Writeln(s1);
end.

いずれも Delphi の宣言済み関数には用意されていません。

See also:


索引

[ ← 5. 列挙型と部分範囲型 ] [ ↑ 目次へ ] [ → 7. レコード型 ]





  1. Default() 関数は、正確には Delphi 2007 for .NET から存在します。つまりジェネリック型のサポートも Delphi 2007 for .NET からです (参考)。 



  2. この拡張は UCSD Pascal 由来のようです。デフォルトで 80 文字 (String)、最大長が 255 文字 (String[255]) でした。 



  3. クラシックな MacOS は Pascal / Object Pascal で書かれており、ここでも Str255 型という Pascal String が使われており、クラシック MacOS が C / C++ でリライト (?) されても 255 文字制限がずーっと残っていたとか。 



  4. "インデックスが 0 から始まる基底型が Char の配列型" またはそのポインタ型があり、末尾に NULL を書き込む手段さえあれば実現可能です。