<9> ファイル型 (標準 Pascal 範囲内での Delphi 入門)

# 9. ファイル型 (File Types)

標準 Pascal で言う**ファイル**とは同じ型の要素の列からなる**固定長ファイル**を指します。これとは別の特殊な可変長ファイルは**テキストファイル**として区別されています。

**”ファイル”**と聞くとストレージに保存される**外部ファイル**を想像すると思いますが、とりあえずその固定観念を捨てましょう。

## 9.1. ファイル構造 (File Structure)

**ファイル型**は型の値をまとめた構造です。

```
ファイル型 =
  [packed] file of 基底型.
```

基底型は任意の型ですが、**固定サイズの型に限定**されます。他のファイル型や[ポインタ型](https://qiita.com/ht_deko/items/745ad4a792d277d33831)等も使えません。

ファイル変数 F を宣言すると、その要素型を持つ**バッファ変数 (Buffer Variable)** が自動的に定義されます。バッファ変数は `F^` で表されます。バッファ変数はいわゆる**ファイルポインタ**です。

|手続き|説明|
|:---|:---|
|Get()|ファイルを次の要素まで進め、<br>その値をバッファ変数 F^ に格納する。<br>要素が存在しなければ [Eof()](http://docwiki.embarcadero.com/Libraries/ja/System.Eof) は **True** になり<br>F^ は不定となる。|
|Put()|ファイル F にバッファ変数 F^ の値を追加する。<br>実行後は [Eof()](http://docwiki.embarcadero.com/Libraries/ja/System.Eof) は **True** になり F^ は不定となる。|
|[Read()](http://docwiki.embarcadero.com/Libraries/ja/System.Read)|ファイルからデータを読み込む|
|[Reset()](http://docwiki.embarcadero.com/Libraries/ja/System.Reset)|ファイル F を先頭に位置付け、<br>F が空でなければ F の最初の要素が<br>F^ に代入され、[Eof()](http://docwiki.embarcadero.com/Libraries/ja/System.Eof) は **False** を返す。|
|[Rewrite()](http://docwiki.embarcadero.com/Libraries/ja/System.Rewrite)|ファイル F を生成する。<br>F は空になり、[Eof()](http://docwiki.embarcadero.com/Libraries/ja/System.Eof) は **True** を返す。|
|[Write()](http://docwiki.embarcadero.com/Libraries/ja/System.Write)|ファイルにデータを書き込む|

|関数|説明|
|:---|:---|
|[Eof()](http://docwiki.embarcadero.com/Libraries/ja/System.Eof)|ファイルの終わりの場合に **True** を返す。|

 - `Reset()` はファイルを**検査モード (Inspection Mode)** で開きます。いわゆる読み取りです。
 - `Rewrite()` はファイルを**生成モード (Generation Mode)** で開きます。いわゆる書き込みです。


次のコードは Str12 型 (長さ 12 の文字列型) のファイル型 F を使って 2 要素を書き込み後、読み出して画面に表示しています。**外部ファイル**はどこにも存在していません。

```pascal:FileTest1.pas
program FileTest1(Output);
type
  Str12 = packed array [1..12] of Char;
var
  F: File of Str12;
  X: Str12; 
begin
  Rewrite(F);
  X := 'Hello,world.';
  F^ := X; Put(F);
  X := 'BlaisePascal';
  F^ := X; Put(F);

  Reset(F);
  X := F^; Get(F);
  Writeln(X);
  X := F^; Get(F);
  Writeln(X);
end.
```

なお、Delphi では `Put()`, `Get()` 手続き及び、バッファ変数が使えません。このため、バッファ変数を利用して先読みを行っているプログラムの移植は難しい事があります。

`F^ := X; Put(F);` は **Write(F, X);**、`X := F^; Get(F);` は **Read(F, X);** に置き換えることができるので、先程のコードは次のように書く事もできます。

```pascal:FileTest2.pas
program FileTest2(Output);
type
  Str12 = packed array [1..12] of Char;
var
  F: File of Str12;
  X: Str12; 
begin
  Rewrite(F);
  X := 'Hello,world.';
  Write(F, X);
  X := 'BlaisePascal';
  Write(F, X);

  Reset(F);
  Read(F, X);
  Writeln(X);
  Read(F, X);
  Writeln(X);
end.
```

**Read()** と **Write()** は任意の個数の実パラメータ (可変個パラメータ) が渡せる特殊な手続きです。例えば `Write(F, X1, X2, X3)` とすれば、ファイルに要素を 3 回追記できます。

さて、このファイル型ですが、**外部ファイル**と関連付けて使うにはどうすればいいのでしょうか？

標準 Pascal では次のようにして外部ファイルを扱う事ができます。

```pascal:FileTest3.pas
program FileTest3(Output, DATAFILE);
type
  Str12 = packed array [1..12] of Char;
var
  DATAFILE: File of Str12;
  X: Str12; 
begin
  Rewrite(DATAFILE);
  X := 'Hello,world.';
  Write(DATAFILE, X);
  X := 'BlaisePascal';
  Write(DATAFILE, X);

  Reset(DATAFILE);
  Read(DATAFILE, X);
  Writeln(X);
  Read(DATAFILE, X);
  Writeln(X);
end.
```

変数定義部にある DATAFILE 変数が **program** ヘッダのパラメータにも書かれていますよね？こうする事によって、標準 Pascal は外部ファイルにアクセスします。**"変数名 = Program ヘッダのパラメータ名 = 外部ファイル名"** です。上記例だと **DATAFILE** という名前のファイルができる事になります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/8c334472-e206-fd6b-81c3-4a7c37bf0ca8.png)

ここまで標準 Pascal の検証には **PASCAL-P5** を使ってきたのですが、残念ながら **"任意の"** 外部ファイルのアクセスには対応していません。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/21785/7d1e6c34-c71c-b242-af39-9438b944e61f.png)

次のようにプログラムヘッダの `DATAFILE` をコメントアウトすると **PASCAL-P5** でも実行できますが、DATAFILE が外部ファイルとみなされないため、DATAFILE というファイルは当然生成されません。

```pascal:FileTest3.pas
program FileTest3(Output{, DATAFILE});
...
```

何故外部ファイルがこんな扱いになっているのかと言うと、**クラシック Pascal** は外部ファイルの扱い方も環境依存と考えていたからです (外部ファイルが磁気テープのようにファイル名の使えないデバイスでもいい)。標準 Pascal においても、プログラム引数 (ファイル型) の外部実体への結び付け方は**処理系定義**となっています。

この仕様だとファイルシステムのある OS の場合にはパラメータ名や変数名にドット `.` が使えないため、拡張子のあるファイル名は使えず、別のディレクトリにあるファイルも扱うことができない事になってしまいます。この問題を解決するには何らかのバインド機構が必要となります。いずれにせよ、完全なファイル名やファイル位置をプログラムから指定する事は不可能です。

Delphi だと**必ずファイル変数と外部ファイルを関連付けて扱う**必要があり、そのための **AssignFile()** という手続きが用意されています。

|手続き|説明|
|:---|:---|
|[AssignFile()](http://docwiki.embarcadero.com/Libraries/Rio/ja/System.AssignFile)|外部ファイルの名前をファイル変数と関連付ける。<br>**Turbo Pascal** では Assign()。|
|[CloseFile()](http://docwiki.embarcadero.com/Libraries/Rio/ja/System.CloseFile)|ファイル変数と外部ファイル名の関連付けを終了する。<br>**Turbo Pascal** では Close()。|

**ANSI 版 Delphi** だと標準 Pascal との違いは AssignFile() の記述だけです。

```pascal:FileTest4.pas
program FileTest4(output);
{$APPTYPE CONSOLE}
type
  Str12 = packed array [1..12] of Char;
var
  F: File of Str12;
  X: Str12; 
begin
  AssignFile(F, 'DATA.FILE');

  Rewrite(F);
  X := 'Hello,world.';
  Write(F, X);
  X := 'BlaisePascal';
  Write(F, X);

  Reset(F);
  Read(F, X);
  Writeln(X);
  Read(F, X);
  Writeln(X);
end.
```

**Unicode 版 Delphi** だとちょっと変更が必要です。

```pascal:FileTest4.pas
program FileTest4(output);
{$APPTYPE CONSOLE}
type
  Str12 = packed array [1..12] of AnsiChar;
var
  F: File of Str12;
  X: Str12; 
begin
  AssignFile(F, 'DATA.FILE');

  Rewrite(F);
  X := 'Hello,world.';
  Write(F, X);
  X := 'BlaisePascal';
  Write(F, X);

  Reset(F);
  Read(F, X);
  Writeln(AnsiString(X));
  Read(F, X);
  Writeln(AnsiString(X));
end.
```

**See also:**

 - [ファイル型（Win32） (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#.E3.83.95.E3.82.A1.E3.82.A4.E3.83.AB.E5.9E.8B.EF.BC.88Win32.EF.BC.89)
 - [標準ルーチンと入出力 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A8%99%E6%BA%96%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3%E3%81%A8%E5%85%A5%E5%87%BA%E5%8A%9B#.E3.83.95.E3.82.A1.E3.82.A4.E3.83.AB.E5.85.A5.E5.87.BA.E5.8A.9B)
 - [ファイル入出力のサポート (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%85%A5%E5%87%BA%E5%8A%9B%E3%81%AE%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88)
 - [ファイル記述子 (Wikipedia)](https://ja.wikipedia.org/wiki/ファイル記述子)
 - [12. プログラム引数 (ファイル型) の外部実体への結び付け方 - Delphi における Pascal の処理系定義 (Qiita)](https://qiita.com/ht_deko/items/2b7cab95ffc60de10c25#12-%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%E5%BC%95%E6%95%B0-%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%9E%8B-%E3%81%AE%E5%A4%96%E9%83%A8%E5%AE%9F%E4%BD%93%E3%81%B8%E3%81%AE%E7%B5%90%E3%81%B3%E4%BB%98%E3%81%91%E6%96%B9)

## 9.2. テキストファイル (Textfiles)
テキストファイルは文字の列からなる可変長の行のファイルです。**テキストファイル型**を宣言するには定義済みの型 **Text** を使います。

```
テキストファイル型 =
  [packed] file of Text.
```

テキストファイル型は基底型 **Char** のファイル型に (仮想の) 改行を加えて拡張したものによって定義されます。テキストファイル型は "**(packed) file of Char**" と同義ではありません。改行は次のテキストファイル型用手続き / 関数を使って生成したり検出したりできます。

|手続き|説明|
|:---|:---|
|[Readln(F)](http://docwiki.embarcadero.com/Libraries/ja/System.Readln)|テキストファイル F を次の行の先頭まで読み飛ばす。<br>F^ には次の行の最初の文字が入る。|
|[Writeln(F)](http://docwiki.embarcadero.com/Libraries/ja/System.Writeln)|テキストファイル F に改行を追加する。|

|関数|説明|
|:---|:---|
|[Eoln(F)](http://docwiki.embarcadero.com/Libraries/ja/System.Eoln)|テキストファイル F が改行位置の時に **True** を返す。|

テキストファイル型は基底型 **Char** のファイル型を拡張したものですから、文字の読み書きには `Read(F, Ch)` と `Write(F, Ch)` を使います。Ch は **Char** 型の変数です。

プログラムヘッダに記述することのできる **Input** と **Output** は標準入力と標準出力に割り当てられた宣言済みのテキストファイル型変数です。Delphi には標準エラー出力に割り当てられた **ErrOutput** もあります。

**See also:**

 - [テキストファイル (DocWiki)](http://docwiki.embarcadero.com/RADStudio/Rio/ja/%E6%A8%99%E6%BA%96%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3%E3%81%A8%E5%85%A5%E5%87%BA%E5%8A%9B#.E3.83.86.E3.82.AD.E3.82.B9.E3.83.88_.E3.83.95.E3.82.A1.E3.82.A4.E3.83.AB)
 - [シーケンシャルアクセス (Wikipedia)](https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%BC%E3%82%B1%E3%83%B3%E3%82%B7%E3%83%A3%E3%83%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9)
 - [ランダムアクセス (Wikipedia)](https://ja.wikipedia.org/wiki/%E3%83%A9%E3%83%B3%E3%83%80%E3%83%A0%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9)
 - [System.Input (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Input)
 - [System.Output (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.Output)
 - [System.ErrOutput (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.ErrOutput)


### (9.2.1.) バッファ変数の代替

Delphi 6 以降だと、`TTextRec` を使って、バッファ変数の代替を実装できます。次の関数はファイルポインタを進めずに 1 文字読み込みます。

```pascal
  function CurrentChar(var F: Text): AnsiChar;
  begin
    Eoln(F);
    Result := (TTextRec(F).BufPtr + TTextRec(F).BufPos)^;
  end;
``` 

次のコードはファイルポインタが指す文字を読みますが、ファイルポインタは移動しないため、無限ループとなります。

```pascal
var
  F: Text;
  C: AnsiChar;
begin
  AssignFile(F, 'noname.txt');
  Reset(F);
  while not EOF(F) do
    begin
    //C := F^;
      C := CurrentChar(F); 
      Readln;
    end;
  CloseFile(F);
end;
``` 

:::note info
この手法が使えるのはファイルが Text (またはエイリアスの TextFile) 型の場合のみです。
:::

ファイルバッファはデフォルトで 128 バイトありますが、`SetTextBuf()` を使ってもっと大きなバッファを割り当てる事もできます。

**See also:**

 - [System.TTextRec (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.TTextRec)
 - [System.TFileRec (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.TFileRec)
 - [System.SetTextBuf (DocWiki)](https://docwiki.embarcadero.com/Libraries/ja/System.SetTextBuf)


### (9.2.2.) ファイルの追記

`Rewrite()` の代わりに `Append()` を使うとテキストファイルに追記する事ができます。

|手続き|説明|
|:---|:---|
|[Append()](http://docwiki.embarcadero.com/Libraries/ja/System.Append)|既存のファイルを、末尾にテキストが追加されるように設定します。|

```pascal:
var
  F: Text;
begin
  // 新規作成 (上書き)
  AssignFile(F, 'noname.txt');
  Rewrite(F);
  Write(F, 'Hello,');
  CloseFile(F);
  // 追記
  AssignFile(F, 'noname.txt');
  Append(F);
  Writeln(F, 'world.');
  CloseFile(F);
end.
```

## (9.3.) バイナリファイル
いわゆる**バイナリファイル**を扱うには、`file of Byte` を使います。

```pascal
var
  F: file of Byte;
  Buf: Byte;
```

読み書きには `Read()` と `Write()` を使います。1 バイト単位の読み書きとなります。

```pascal
  Read(F, Buf); 
  Write(F, Buf); 
```

この際、`Eof()` は `0x1A` を EOF とはみなしません。

**See also:**

 - [バイナリ (Wikipedia)](https://ja.wikipedia.org/wiki/%E3%83%90%E3%82%A4%E3%83%8A%E3%83%AA)

## (9.4.) 型なしファイル
Delphi には**型なしファイル型**というものがあります。バイナリファイルを扱う**ファイル型**です。**”型なし"** なので、複数の任意の型を使ってファイルを読み書きする事ができます。

```
型なしファイル型 =
  [packed] file.
```

|手続き|説明|
|:---|:---|
|[BlockRead(F)](http://docwiki.embarcadero.com/Libraries/ja/System.BlockRead)|ファイルから 1 つ以上のレコードを読み取って<br>変数に格納する。|
|[BlockWrite(F)](http://docwiki.embarcadero.com/Libraries/ja/System.BlockWrite)|1 つ以上のレコードを変数から<br>開いているファイルに書き込む。|

型なしファイルを使う場合、`Reset()` と `Rewrite()` の**第二パラメータにブロック転送用のレコードサイズを指定**する必要があります。指定しない事も可能ですが、その際のレコードサイズは 128 バイトであり、このサイズ以下での読み書きはできません。

型なしファイルでは `Read()` と `Write()` の代わりに **BlockRead()** および **BlockWrite()** という 2 つの手続きを使用します。これにより、高速なデータ転送を行うことができます。

`BlockRead()` および `BlockWrite()` の 3 番目のパラメータ `Count` は読み書きするレコードサイズの数を指定します (レコードサイズが 1 でない限り、バイト数ではありません)。

:::note
`BlockRead()` はファイル F から「Reset() で指定したレコードサイズ * Count」バイトのデータを Buf に読み込みます。
`BlockWrite()` は Buf の先頭から「Rewrite() で指定したレコードサイズ * Count」バイトのデータをファイル F へ書き出します。
:::

型なしファイルでは `EOF()` の判定が難しい事があります。その場合には、実際に読み出されたサイズとバッファのサイズを比較してファイル終端を判定します。

```pascal
var
  F: File;
  Buf: Byte;
  NumRead: Integer;
begin
  AssignFile(F, 'noname.bin');
  Reset(F, SizeOf(Buf));
  repeat
    BlockRead(F, Buf, 1, NumRead);
    if NumRead > 0 then
      Writeln('Read: ', Buf);
  until NumRead < SizeOf(Buf);
  CloseFile(F);
end.
```

**See also:**

 - [型なしファイル (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E6%A8%99%E6%BA%96%E3%83%AB%E3%83%BC%E3%83%81%E3%83%B3%E3%81%A8%E5%85%A5%E5%87%BA%E5%8A%9B#.E5.9E.8B.E3.81.AA.E3.81.97.E3.83.95.E3.82.A1.E3.82.A4.E3.83.AB)


## (9.5.) 標準入出力ルーチン

Delphi の標準入出力ルーチンです。

| ルーチン | 説明 | 型付き | 型なし | テキスト |
|:---|:---|:---:|:---:|:---:|
|[Append](https://docwiki.embarcadero.com/Libraries/ja/System.Append)        | 既存のファイルを、末尾にテキストを追加するように設定します。                                       |   |   | 〇 |
|[AssignFile](https://docwiki.embarcadero.com/Libraries/ja/System.AssignFile)| 外部ファイルの名前をファイル変数に関連付けます。                                                   | 〇 | 〇 | 〇 |
|[BlockRead](https://docwiki.embarcadero.com/Libraries/ja/System.BlockRead)  | 開いているファイルから 1 つ以上のレコードを読み取って変数に格納します。                            |   | 〇 |   |
|[BlockWrite](https://docwiki.embarcadero.com/Libraries/ja/System.BlockWrite)| 1 つ以上のレコードを変数から、開いているファイルに書き込みます。                                   |   | 〇 |   |
|[CloseFile](https://docwiki.embarcadero.com/Libraries/ja/System.CloseFile)  | ファイル変数と外部ディスクファイルとの関連付けを終了します。                                       | 〇 | 〇 | 〇 |
|[Eof](https://docwiki.embarcadero.com/Libraries/ja/System.Eof)              | ファイルポインタがファイルの終わりに達したかどうかを検査します。                                   | 〇 | 〇 | 〇 |
|[Eoln](https://docwiki.embarcadero.com/Libraries/ja/System.Eoln)            | ファイルポインタが行末に達したかどうかを検査します。                                               |   |   | 〇 |
|[Erase](https://docwiki.embarcadero.com/Libraries/ja/System.Erase)          | 外部ファイルを削除します。                                                                         | 〇 | 〇 | 〇 |
|[FilePos](https://docwiki.embarcadero.com/Libraries/ja/System.FilePos)      | 現在のファイルポインタの位置を返します。                                                           | 〇 | 〇 | 〇 |
|[FileSize](https://docwiki.embarcadero.com/Libraries/ja/System.FileSize)    | ファイル内のレコード数を返します。                                                                 | 〇 | 〇 | 〇 |
|[Flush](https://docwiki.embarcadero.com/Libraries/ja/System.Flush)          | 出力用に開いたテキストファイルのバッファを空にします。                                             |   |   | 〇 |
|[Read](https://docwiki.embarcadero.com/Libraries/ja/System.Read)            | ファイルからデータを読み取ります。                                                                 | 〇 | 　 | 〇 |
|[Readln](https://docwiki.embarcadero.com/Libraries/ja/System.Readln)        | ファイルからテキストを 1 行読み取ります。                                                          |   |   | 〇 |
|[Rename](https://docwiki.embarcadero.com/Libraries/ja/System.Rename)        | 外部ファイルの名前を変更します。                                                                   | 〇 | 〇 | 〇 |
|[Reset](https://docwiki.embarcadero.com/Libraries/ja/System.Reset)          | 既存のファイルを開きます。                                                                         | 〇 | 〇 | 〇 |
|[Rewrite](https://docwiki.embarcadero.com/Libraries/ja/System.Rewrite)      | 新しいファイルを作成して開きます。                                                                 | 〇 | 〇 | 〇 |
|[Seek](https://docwiki.embarcadero.com/Libraries/ja/System.Seek)            | 現在のファイルポインタを指定の位置に移動します。                                                  | 〇 | 〇 |   |
|[SeekEof](https://docwiki.embarcadero.com/Libraries/ja/System.SeekEof)      | ファイルの EOF（end-of-file）ステータスを返します（ホワイトスペースは無視）。                      |   |   | 〇 |
|[SeekEoln](https://docwiki.embarcadero.com/Libraries/ja/System.SeekEoln)    | ファイルの行末ステータスを返します（ホワイトスペースは無視）。                                     |   |   | 〇 |
|[SetTextBuf](https://docwiki.embarcadero.com/Libraries/ja/System.SetTextBuf)| テキストファイルに入出力バッファを割り当てます。                                                   |   |   | 〇 |
|[Truncate](https://docwiki.embarcadero.com/Libraries/ja/System.Truncate)    | 現在のファイルポインタから後のすべてのレコードを削除します。                                       | 〇 | 〇 |   |
|[Write](https://docwiki.embarcadero.com/Libraries/ja/System.Write)          | 型付きファイルまたはテキストファイルにデータを書き込みます。                                       | 〇 |   | 〇 |
|[WriteLn](https://docwiki.embarcadero.com/Libraries/ja/System.Writeln)      | テキストファイルに書き込んで、行末マーカーを追加します。                                           |   |   | 〇 |


# 索引
[ [← 8. 集合型](https://qiita.com/ht_deko/items/50bac0de384d66208944) ] [ [↑ 目次へ](https://qiita.com/ht_deko/items/e2796788c65c2cda1de5) ] [ [→ 10. ポインタ型](https://qiita.com/ht_deko/items/745ad4a792d277d33831) ] [:sushi:](https://qiita.com/ht_deko/items/0e012da26f46b915307d)