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

# 6. 構造化型の概要と配列型
[単純型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b) ([順序型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#21-%E9%A0%86%E5%BA%8F%E5%9E%8B-%E9%A0%86%E5%BA%8F%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B)と[実数型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#25-real-%E5%9E%8B)) は構造を持たない型ですが、Pascal には構造を持つ型として **構造化型 (構造データ型)** と[ポインタ型](https://qiita.com/ht_deko/items/745ad4a792d277d33831)があります。Delphi には他にも文字列型、手続き型、バリアント型、型識別子があります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/bfbfa5d4-a82f-3a9e-75c2-b9f5a1e65a60.png)

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

 - [文字列型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%96%87%E5%AD%97%E5%88%97%E5%9E%8B%EF%BC%88Delphi%EF%BC%89)
 - [手続き型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%89%8B%E7%B6%9A%E3%81%8D%E5%9E%8B)
 - [バリアント型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%90%E3%83%AA%E3%82%A2%E3%83%B3%E3%83%88%E5%9E%8B%EF%BC%88Delphi%EF%BC%89)
 - [型識別子 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E5%9E%8B%E3%81%AE%E4%BA%92%E6%8F%9B%E6%80%A7%E3%81%A8%E5%90%8C%E4%B8%80%E6%80%A7)

## 構造化型 (Structured Types)
**構造化型 (構造データ型)** には、配列型、[レコード型 (レコードデータ型)](https://qiita.com/ht_deko/items/1d2c85768febc95009bf)、[集合型 (集合データ型)](https://qiita.com/ht_deko/items/50bac0de384d66208944)、[ファイル型 (ファイルデータ型)](https://qiita.com/ht_deko/items/986fa01c9e6cfc3fd673) があります。構造化型はすべてプログラマ定義の型 [^1] です。

Delphi には他の構造型として、クラス型、クラス参照型、インターフェイス型があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21785/56799d30-c5d7-8e4b-d626-93b6842a3fb9.png)

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

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

**See also:**

 - [構造化型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89)
 - [クラス型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AF%E3%83%A9%E3%82%B9%E3%81%A8%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%EF%BC%88Delphi%EF%BC%89#.E3.82.AF.E3.83.A9.E3.82.B9.E5.9E.8B)
 - [クラス参照型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AF%E3%83%A9%E3%82%B9%E5%8F%82%E7%85%A7#.E3.82.AF.E3.83.A9.E3.82.B9.E5.8F.82.E7.85.A7.E5.9E.8B)
 - [インターフェイス型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88_%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%BC%E3%83%95%E3%82%A7%E3%82%A4%E3%82%B9#.E3.82.A4.E3.83.B3.E3.82.BF.E3.83.BC.E3.83.95.E3.82.A7.E3.82.A4.E3.82.B9.E5.9E.8B)

## 6.1. 配列型 (Arrays)
**配列型**は、**基底型**と呼ばれる同じ型の要素 (**要素型**) のインデックス付きの集まりを表します。インデックスは **添字** とも呼ばれ、計算が可能です。この型を **添字型 (Index Type)** と呼びます。添字型は任意の[順序型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#21-%E9%A0%86%E5%BA%8F%E5%9E%8B-%E9%A0%86%E5%BA%8F%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B)です。

配列の添字に任意の順序型が使える言語は割と珍しいので、移植性の高い汎用的なコード (アルゴリズムを記述するなど) を書こうとしている場合には注意してください。

```pascal
var
  Buf: array [0..1023] of Char;
```

この配列の添字は配列のインデックスの上限値と下限値を指定しているのではなく、`0..1023 という部分範囲型` を指定しています。つまり次のコードと同等です。

```pascal
type
  TRange = 0..1023;
var
  Buf: array [TRange] of Char;
```

Pascal では添字型に[部分範囲型](https://qiita.com/ht_deko/items/335e59857cc78aea951a#52-部分範囲型-subrange-types)を使う事が多いため、上限値と下限値を指定しているように見えますが、指定しているのは値ではなく実際には**型**なのです。

Delphi の場合も、添字型は任意の[順序型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#21-%E9%A0%86%E5%BA%8F%E5%9E%8B-%E9%A0%86%E5%BA%8F%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B)なのですが、Delphi  には **"型のサイズは 2GB 以内"** という制限があるため、実際には Integer (Int32) や Int64 のような[順序型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#21-%E9%A0%86%E5%BA%8F%E5%9E%8B-%E9%A0%86%E5%BA%8F%E3%83%87%E3%83%BC%E3%82%BF%E5%9E%8B)は添字型として使えません。

例えば Integer 型配列の最大サイズ (2GB) を指定する添字型の定義は次のようになります。

```pascal
type
  TInt32Index = 0..(2147483648 div SizeOf(Integer)) - 2; 
```

添字型に ShortInt を使った場合、配列のインデックスの範囲は -128..127 となります。

```pascal
var
  Buf: array [ShortInt] of String;
```



**See also:**

 - [配列型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E9.85.8D.E5.88.97.E5.9E.8B)

### (6.1.1.) 静的配列 (Static Arrays)
新しい静的な配列型を定義するには次のようにします。

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

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

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

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

```pascal
var
  Sheet: array [1..100] of array [1..100] of Integer;
```

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

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

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

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

```pascal: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()** を使う事ができます。例えば配列を初期化する次のコードは

```pascal: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.
```

このようにも書けます。

```pascal: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:**

 - [静的配列 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E9.9D.99.E7.9A.84.E9.85.8D.E5.88.97)
 - [System.High (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.High)
 - [System.Low (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Low)


### (6.1.2.) 動的配列 (Dynamic Arrays)
標準 Pascal には動的配列はありません。Delphi では次のようにして動的配列を宣言します [^2]。

```pascal
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 の二次元配列として確保しましたが、次のような確保の仕方もあります。

```pascal
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 のヘルプには次のような記述があります。

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

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

**See also:**

 - [動的配列 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A7%8B%E9%80%A0%E5%8C%96%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E5.8B.95.E7.9A.84.E9.85.8D.E5.88.97)

#### (6.1.2.1.) 配列コンストラクタ (Array Constructor)
Delphi 2005 以降だと、動的配列を確保と同時に初期化するために**配列コンストラクタ**が使えます。

```pascal
uses
  ..., Types;

...

var
  IA: TIntegerDynArray;
begin
  IA := TIntegerDynArray.Create(1, 2, 3);
end;
```

配列コンストラクタは作成した動的配列を返すため、

```pascal
uses
  ..., Types;

...

begin
  Writeln(TStringDynArray.Create('Spade', 'Diamond', 'Heart', 'Club')[2]); // Heart
end;
```

その場で定義できる無名の動的配列として使う事もできます。

#### (6.1.2.2.) 配列定数式 (Array Constant Expression)
Delphi XE7 以降だと、動的配列を確保と同時に初期化するために**配列定数式**が使えます。

```pascal
var
  IA: array of Integer;
begin
  IA := [1, 2, 3];
end;
```

配列コンストラクタとは異なり、プログラマ定義の配列型を用意しなくても使う事ができます。

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

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

```pascal: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()** 関数を使います。

```pascal: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.
```

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

```pascal: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() を使ってもダメでしたね。正しくは次のようになります。

```pascal: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:**

 - [System.SetLength (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.SetLength)
 - [ジャグ配列 (Wikipedia)](https://ja.wikipedia.org/wiki/%E9%85%8D%E5%88%97#%E3%82%B8%E3%83%A3%E3%82%B0%E9%85%8D%E5%88%97)
 - [System.Copy (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Copy)

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

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

```pascal: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()** があります。

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

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

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

**See also:**

 - [配列定数 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E5%AE%A3%E8%A8%80%E3%81%95%E3%82%8C%E3%81%9F%E5%AE%9A%E6%95%B0#.E9.85.8D.E5.88.97.E5.AE.9A.E6.95.B0)

### (6.1.5.) 動的配列の割り当て解除
Delphi で動的配列そのものを消去 (未割り当ての状態) するには Finalize() を使います。

```pascal
uses
  System.Types;

var
  Arr: TStringDynArray;
  i: Integer;
begin
  // 動的配列の割り当て
  SetLength(Arr, 3);
  Writeln(Length(Arr)); // 3
  Writeln;

  // 内容のセット
  Arr[0] := 'ABC';
  Arr[1] := 'DEF';
  Arr[2] := 'GHI';
  for i := Low(Arr) to High(Arr) do
    Writeln(i, ': ', Arr[i]);
  Writeln;

  // 内容のクリア
  InitializeArray(Arr, TypeInfo(TStringDynArray), Length(Arr));
  for i := Low(Arr) to High(Arr) do
    Writeln(i, ': ', Arr[i]);
  Writeln;

  // 割り当ての解除
  Finalize(Arr);
  Writeln(Length(Arr)); // 0
end.
```

次の方法でも動的配列の割り当てを解除できます。

```pascal
  Arr := nil;
  SetLength(Arr, 0);
```

**See also:**

 - [System.Finalize (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Finalize)
 - [System.InitializeArray (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.InitializeArray)

## 6.2. 文字配列 (標準 Pascal の文字列型)
標準 Pascal で言う文字列型はちょっと特殊な**パックされた文字配列**です。

```pascal
packed array [1..N] of Char;
```

添字型は 1..N の[部分範囲型](https://qiita.com/ht_deko/items/335e59857cc78aea951a#52-%E9%83%A8%E5%88%86%E7%AF%84%E5%9B%B2%E5%9E%8B)でなくてはいけません (N は 2 以上)。

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

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

```pascal
  Buf2 := Buf1; (* OK *)
  Buf3 := Buf1; (* NG *)
```

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

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

```pascal
  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);
```

このようにするか、

```pascal
  Buf := 'Hello     ';
```

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

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

この文字列型の何が**特殊**なのか？標準 Pascal の文字列型の特殊性を端的に表したコードを次に示します。

```pascal:StringTest.pas
program StringTest(Output);
var
  Buf1: packed array [1..10] of Char;
  Buf2: packed array [1..10] of Char;
begin
  Buf1 := 'Hello,    '; { <-- (1) }
  Buf2 := Buf1;         { <-- (2) }
  Buf2 := 'world.    '; { <-- (1) }

  if Buf1 = Buf2 then   { <-- (3) }
    Writeln(Buf1)       { <-- (4) }
  else  
    Writeln(Buf2);      { <-- (4) }
end.
```
 - **(1)**: 文字列を代入できる (空白などで長さ調整の必要あり)。
 - **(2)**: 異なる宣言でも、長さが合っていれば代入できる。
 - **(3)**: 関係演算子を使える。
 - **(4)**: write() / writeln() で一括出力できる。


### (6.2.2.) Delphi の文字列型 (String Types)
Delphi には文字配列ではない文字列型がありますが構造化型ではありません。

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

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

| 演算子 | 意味 | 説明 |
|:---:|:---:|:---|
| + |連結|2 つの文字列を連結する|

また、次の組み込みルーチンも使えます。

|手続き /<br>関数|説明|
|:---|:---|
|[Concat()](http://docwiki.embarcadero.com/Libraries/ja/System.Concat)|2 つ以上の文字列を連結して 1 つの文字列にします。|
|[Copy()](http://docwiki.embarcadero.com/Libraries/ja/System.Copy)|文字列の部分文字列を返します。|
|[Delete()](http://docwiki.embarcadero.com/Libraries/ja/System.Delete)|文字列から部分文字列を削除します。|
|[Insert()](http://docwiki.embarcadero.com/Libraries/ja/System.Insert)|文字列の指定された位置に部分文字列を挿入します。|
|[Length()](http://docwiki.embarcadero.com/Libraries/ja/System.Length)|文字列内の文字数を返します。|
|[SetLength()](http://docwiki.embarcadero.com/Libraries/ja/System.SetLength)|文字列変数の長さを設定します。|
|[SetString()](http://docwiki.embarcadero.com/Libraries/ja/System.SetString)|指定した文字列の内容と長さを設定します。|
|[Str()](http://docwiki.embarcadero.com/Libraries/ja/System.Str)|文字列を書式設定し変数に格納して返します。|
|[Val()](http://docwiki.embarcadero.com/Libraries/ja/System.Val)|文字列を数値表現に変換します。|

Delphi の **string** 型は文字配列として扱えます。配列のインデックスは 1 から始まります。

```pascal:StringTest3.pas
program StringTest3(output);
{$APPTYPE Console}
var
  s: string;
begin
  s := 'Hello,world.';
  s[12] := '!';
  Writeln(s);
end.
```

**See also:**

 - [文字列型 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%96%87%E5%AD%97%E5%88%97%E5%9E%8B%EF%BC%88Delphi%EF%BC%89)
 - [文字列の宣言と初期化 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%96%87%E5%AD%97%E5%88%97%E3%81%AE%E5%AE%A3%E8%A8%80%E3%81%A8%E5%88%9D%E6%9C%9F%E5%8C%96)
 - [インデックスが 0 から始まる文字列（Delphi）(DocWiki)](https://docwiki.embarcadero.com/RADStudio/ja/%E3%82%A4%E3%83%B3%E3%83%87%E3%83%83%E3%82%AF%E3%82%B9%E3%81%8C_0_%E3%81%8B%E3%82%89%E5%A7%8B%E3%81%BE%E3%82%8B%E6%96%87%E5%AD%97%E5%88%97%EF%BC%88Delphi%EF%BC%89)

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

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/26f34616-29d9-33fe-e6af-f60b3a79a0eb.png)

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

```pascal: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** 扱いとなります。

```pascal: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** であるため意味を成さず、コンパイラに無視されます。

標準 Pascal では "**packed array** [1..n] **of** char" が文字列型なので、これと互換性を持った可変長の文字列型を実装しようとすると次のような制限が出てきます。

 - 配列の拡張で文字列型を定義するしかない
 - インデックスは 1 から始めなくてはならない
 - ヌル文字を許容できなくてはならない
 - ヌル終端でないとすると、文字列の長さをどこかに持たせる必要がある

そうなるとやっぱり Pascal String のような実装になってしまうのだと思われます。

**See also:**

 - [短い文字列 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%96%87%E5%AD%97%E5%88%97%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#.E7.9F.AD.E3.81.84.E6.96.87.E5.AD.97.E5.88.97)
 - [長い文字列（Delphi）(DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E9%95%B7%E3%81%84%E6%96%87%E5%AD%97%E5%88%97%EF%BC%88Delphi%EF%BC%89)
 - [UCSD Pascal (Wikipedia)](https://ja.wikipedia.org/wiki/UCSD_Pascal)
 - [INSIDE MACINTOSH - Macintosh Toolbox Essentials (Apple)](https://developer.apple.com/library/archive/documentation/mac/pdf/MacintoshToolboxEssentials.pdf?fbclid=IwAR2wWH9FOuOH98nvfS7uS1jdtP7d_Y5UbNdPGSFUa8KULKJgUXGtohbka5M)

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

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

何度も何度も **var** ブロックで宣言するのは面倒ですものね。

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

```pascal
type
  TCharArray = array[0..15] of Char;
  TAnsiCharArray = array[0..15] of AnsiChar;
  TWideCharArray = array[0..15] of WideChar;
```
コンパイラ指令 `{$X}` が有効 (`{$X+}`) である場合、NULL 終端文字列に対して文字列定数の代入が可能となります。

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

NULL 終端文字列は[ポインタや動的変数 (10.1.節)](https://qiita.com/ht_deko/items/745ad4a792d277d33831#101-%E3%83%9D%E3%82%A4%E3%83%B3%E3%82%BF%E5%A4%89%E6%95%B0%E3%81%A8%E5%8B%95%E7%9A%84%E5%A4%89%E6%95%B0-%E5%AF%BE%E8%B1%A1%E5%A4%89%E6%95%B0)と一緒に使われる事が多いです。

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

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

|Delphi|PChar|PAnsiChar|PWideChar|
|:---|:---:|:---:|:---:|
|ANSI 版|^AnsiChar<br>(^Char)|^AnsiChar|^WideChar|
|Unicode 版|^WideChar<br>(^Char)|^AnsiChar|^WideChar|

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

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

**See also:**

 - [2.4. Char 型 (Character Types)](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#24-char-%E5%9E%8B-character-types)
 - [NULL 終端文字列の取り扱い (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%96%87%E5%AD%97%E5%88%97%E5%9E%8B%EF%BC%88Delphi%EF%BC%89#NULL_.E7.B5.82.E7.AB.AF.E6.96.87.E5.AD.97.E5.88.97.E3.81.AE.E5.8F.96.E3.82.8A.E6.89.B1.E3.81.84)
 - [PChar (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.PChar)
 - [PAnsiChar (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.PAnsiChar)
 - [PWideChar (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.PWideChar)
 - [一般に使用される NULL 終端文字列処理ルーチン (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E4%B8%80%E8%88%AC%E3%81%AB%E4%BD%BF%E7%94%A8%E3%81%95%E3%82%8C%E3%82%8B_NULL_%E7%B5%82%E7%AB%AF%E6%96%87%E5%AD%97%E5%88%97%E5%87%A6%E7%90%86%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3)


## 6.3. パックとアンパック

[標準手続き](https://qiita.com/ht_deko/items/15b27e00fac9d723d9f0#%E6%A8%99%E6%BA%96%E6%89%8B%E7%B6%9A%E3%81%8D-required-procedures) **Pack()** はパックされていない配列変数 (**array**) の内容をパックされている配列変数 (**packed array**) へコピーするものです。

[標準手続き](https://qiita.com/ht_deko/items/15b27e00fac9d723d9f0#%E6%A8%99%E6%BA%96%E6%89%8B%E7%B6%9A%E3%81%8D-required-procedures) **Unpack()** はパックされている配列変数 (**packed array**) の内容をパックされていない配列変数 (**array**) へコピーするものです。

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

```pascal: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:**

 - [Pack - The GNU Pascal Manual (GNU Pascal)](https://www.gnu-pascal.de/gpc/Pack.html)
 - [Unpack - The GNU Pascal Manual (GNU Pascal)](https://www.gnu-pascal.de/gpc/Unpack.html)

## (6.4) 型の互換性 (Compatible Types) と代入の互換性 (Assignment Compatibility)

### (6.4.1) 型の同一性 (Type Identity)

型識別子が他の型識別子を "そのまま使って" 宣言される場合には、それらは同一の型となります。次の例で、`Integer`，`T1`，`T2` は同じ型です。つまり `T1`，`T2` は `Integer` のエイリアスとなります。

```pascal
type
  T1 = Integer;
  T2 = T1; 
```

他の型識別子を "そのまま使わない" 場合にはプログラマ定義の新しい型 [^1] を作る事になります。次の例で、`Char`，`TS1`，`TS2` は別の型です。`TS2` と `TS3` は同じ型です。

```pascal:
type
  TS1 = set of Char;
  TS2 = set of Char;
  TS3 = TS2;
```

Delphi では他の型識別子を "そのまま使って" 新しい型を作る事ができます。型定義で **type** をもう一度繰り返す事で新しい型を作れます。次の例では `T3` だけが別の型となります。 

```pascal
type
  T1 = Integer;
  T2 = T1; 
  T3 = type Integer; 
```

次の例で、変数 `A1`，`A2` には暗黙的なプログラマ定義の型 [^1] (`array [0..9] of Char`) が割り当てられます。この時に作られる型は `A1` と `A2` で別のものになります。

```pascal
var
  A1: array [0..9] of Char;
  A2: array [0..9] of Char;
```

次の例だと、変数 `A1` と `A2` の型は同一の型となります。

```pascal
var
  A1, A2: array [0..9] of Char;
```

### (6.4.2) 型の互換性 (Compatible Types)

2 つの型があった時、それらが次の条件のうちのいずれかを満たせばそれらには**互換性 (適合性)** があるといいます。

**標準 Pascal の場合:**

 - 両方とも同一の型である。
 - 一方の型が、他方の型の部分範囲であるか、両方の型が、同一の型の部分範囲である。
 - 両方が集合型で、それらの基底型に互換性があり、さらに両者がともにパック状態かアンパック状態である。
 - 両方が文字列型で、要素の個数が同じである。

**Delphi の場合:**

 - 両方とも実数型である。
 - 両方とも整数型である。
 - 一方の型が、他方の型の部分範囲である。
 - 両方の型が、同一の型の部分範囲である。
 - 両方とも、互換性のある基底型を基に設定されている。
 - 両方とも、同一の文字数のパック文字列型である。
 - 一方が文字列型で、他方が文字列型、パック文字列型、または Char 型である。
 - 一方がバリアント型で、他方が整数型、実数型、文字列型、文字型、または論理型である。
 - 両方ともクラス型、クラス参照型、またはインターフェイス型で、一方が他方の型から派生した型である。
 - 一方が PAnsiChar または PWideChar で、他方が PAnsiChar または PWideChar の array[0..n] という形式の インデックスが 0 から始まる文字配列である。
 - 一方が Pointer（型なしポインタ）で、他方が何らかのポインタ型である。
 - 両方とも同一の型への（型付き）ポインタで、{$T+} コンパイラ指令が有効である。
 - 両方とも手続き型で、結果の型が同一で、パラメータの数とパラメータの対応する位置の型が同一である。

**See also:**

 - [型の互換性 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E5%9E%8B%E3%81%AE%E4%BA%92%E6%8F%9B%E6%80%A7%E3%81%A8%E5%90%8C%E4%B8%80%E6%80%A7#.E5.9E.8B.E3.81.AE.E4.BA.92.E6.8F.9B.E6.80.A7)

### (6.4.3) 代入の互換性 (Assignment Compatibility)

型 T1 と T2 について、次の条件のうちのいずれかを満たせば、T2 型の値は T1 型と**代入の互換性 (適合性) がある**といいます。

**標準 Pascal の場合:**

 - T1 と T2 が同一の代入可能な型。
 - T1 が Real で T2 が Integer である。
 - T1 と T2 がともに、互換性のある順序型か集合型で、T2 型の値は、T1 によって定められる値の集合の要素である。
 - T1 と T2 は互換性のある文字列型である。

**Delphi の場合:**

 - T1 と T2 が同一の型で、ファイル型でないか、いずれかのレベルにファイル型を含む構造化型でない。
 - T1 と T2 が、互換性のある順序型である。
 - T1 と T2 が、両方とも実数型である。
 - T1 が実数型で、T2 が整数型である。
 - T1 が PAnsiChar、PWideChar、PChar、または何らかの文字列型で、式が文字列定数である。
 - T1 と T2 が、両方とも文字列型である。
 - T1 が文字列型で、T2 が Char またはパック文字列型である。
 - T1 が長い文字列で、T2 が PAnsiChar、PWideChar、または PChar である。
 - T1 と T2 が、互換性のあるパック文字列型である。
 - T1 と T2 が、互換性のある集合型である。
 - T1 と T2 が、互換性のあるポインタ型である。
 - T1 と T2 が、両方ともクラス型、クラス参照型、またはインターフェイス型で、T2 が T1 から派生している。
 - T1 がインターフェイス型で、T2 が T1 を実装したクラス型である。
 - T1 が PAnsiChar または PWideChar で、T2 が array[0..n] of Char という形式（T1 が PAnsiChar の場合）または WideChar（T1 が PWideChar の場合）のインデックスが 0 から始まる文字配列である。
 - T1 と T2 が、互換性のある手続き型である。（特定の代入文では、関数または手続き識別子は、手続き型の式として扱われます。「手続き型」トピックの「文と式の中の手続き型」節を参照。）
 - T1 がバリアント型で、T2 が、整数型、実数型、文字列型、文字型、Boolean 型、インターフェイス型、または OleVariant 型である。
 - T1 が OleVariant 型で、T2 が、整数型、実数型、文字列型、文字型、Boolean 型、インターフェイス型、またはバリアント型である。
 - T1 が、整数型、実数型、文字列型、文字型、Boolean 型で、T2 が、バリアント型、または OleVariant 型である。
 - T1 が、IUnknown または IDispatch インターフェイス型で、T2 が、バリアント型または OleVariant 型である。（バリアントの型コードは、T1 が IUnknown の場合、varEmpty、varUnknown、または varDispatch、T1 が  - IDispatch の場合は、varEmpty または varDispatch でなければなりません）

**See also:**

 - [代入の互換性 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E5%9E%8B%E3%81%AE%E4%BA%92%E6%8F%9B%E6%80%A7%E3%81%A8%E5%90%8C%E4%B8%80%E6%80%A7#.E4.BB.A3.E5.85.A5.E3.81.AE.E4.BA.92.E6.8F.9B.E6.80.A7)


### (6.4.4) 構造等価 (Structual Equivalence) と 名前等価 (Name Equivalence)
                     
次のような標準 Pascal のコードがあったとします。文字列型 (標準 Pascal) と文字配列を定義しています。

```pascal
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 だとどちらも通りません)。

変数 `Buf1`，`Buf2`，`Buf3`，`Buf4` の型はすべて異なるため、本来はどちらも代入できないのですが、標準 Pascal の文字列型には代入の互換性に特別なルールがあるため、代入が可能となっています。

次のような型の定義があった場合には、

```pascal
type
  Arr = array [0..9] of Integer;
var
  a   : Arr;
  b   : array [0..9] of Integer;
  c, d: array [0..9] of Integer;
```

このように解釈されます。

```pascal
type
  Arr  = array [0..9] of Integer;
  Arr2 = array [0..9] of Integer;
  Arr3 = array [0..9] of Integer;
var
  a: Arr;
  b: Arr2;
  c: Arr3;
  d: Arr3;
```

`新しい型ができると暗黙的な型の名前が振られる` と考えるといいでしょう。Arr, Arr2, Arr3 のいずれも、構造的には `array [0..9] of Integer` の型なのですが、この時、`a := b` が許されるのが**構造等価 (Structual Equivalence)** [^5] で、許されないのが**名前等価 (Name Equivalence)**  [^6]  です。

標準 Pascal や Delphi は名前等価を採用しているので、`a := b` や `b := c` の代入が許されません。

**See also:**

 - [Information & Computing 別巻5 コンパイラ I - 原理・技法・ツール (サイエンス社)](https://www.saiensu.co.jp/search/?isbn=978-4-7819-0585-3&y=1990)
 - [Information & Computing 別巻6 コンパイラ II - 原理・技法・ツール (サイエンス社)](https://www.saiensu.co.jp/search/?isbn=978-4-7819-0586-0&y=1990)


# 索引
[ [← 5. 列挙型と部分範囲型](https://qiita.com/ht_deko/items/335e59857cc78aea951a) ] [ [↑ 目次へ](https://qiita.com/ht_deko/items/e2796788c65c2cda1de5) ] [ [→ 7. レコード型](https://qiita.com/ht_deko/items/1d2c85768febc95009bf) ] [:sushi:](https://qiita.com/ht_deko/items/bfd46dc32bcb413d44e1)

[^1]: `プログラマ定義の型` は ISO/IEC 7185 の構文定義では **new-type**、JIS X 3008 では **書下(くだ)し型** と記述されています。『J&W』の構文定義には記述がありません。
[^2]: Delphi 4 以降。
[^3]: Default() 関数は、正確には **Delphi 2007 for .NET** から存在します。つまりジェネリック型のサポートも **Delphi 2007 for .NET** からです ([参考](http://docs.embarcadero.com/products/rad_studio/radstudio2007/RS2007_helpupdates/HUpdate4/JA/html/devcommon/genericspart_xml.html))。
[^4]: "インデックスが 0 から始まる基底型が Char の配列型" またはそのポインタ型があり、末尾に NULL を書き込む手段さえあれば実現可能です。
[^5]: 構造同値・構造等値・Structural Equivalence とも。
[^6]: 名前同値・名前等値とも。