9. ファイル型 (File Types)
標準 Pascal で言うファイルとは同じ型の要素の列からなる固定長ファイルを指します。これとは別の特殊な可変長ファイルはテキストファイルとして区別されています。
”ファイル”と聞くとストレージに保存される外部ファイルを想像すると思いますが、とりあえずその固定観念を捨てましょう。
9.1. ファイル構造 (File Structure)
ファイル型は型の値をまとめた構造です。
ファイル型 =
  [packed] file of 基底型.
基底型は任意の型ですが、固定サイズの型に限定されます。他のファイル型やポインタ型等も使えません。
ファイル変数 F を宣言すると、その要素型を持つバッファ変数 (Buffer Variable) が自動的に定義されます。バッファ変数は F^ で表されます。バッファ変数はいわゆるファイルポインタです。
| 手続き | 説明 | 
|---|---|
| Get() | ファイルを次の要素まで進め、 その値をバッファ変数 F^ に格納する。 要素が存在しなければ Eof() は True になり F^ は不定となる。 | 
| Put() | ファイル F にバッファ変数 F^ の値を追加する。 実行後は Eof() は True になり F^ は不定となる。 | 
| Read() | ファイルからデータを読み込む | 
| Reset() | ファイル F を先頭に位置付け、 F が空でなければ F の最初の要素が F^ に代入され、Eof() は False を返す。 | 
| Rewrite() | ファイル F を生成する。 F は空になり、Eof() は True を返す。 | 
| Write() | ファイルにデータを書き込む | 
| 関数 | 説明 | 
|---|---|
| Eof() | ファイルの終わりの場合に True を返す。 | 
- 
Reset()はファイルを検査モード (Inspection Mode) で開きます。いわゆる読み取りです。
- 
Rewrite()はファイルを生成モード (Generation Mode) で開きます。いわゆる書き込みです。
次のコードは Str12 型 (長さ 12 の文字列型) のファイル型 F を使って 2 要素を書き込み後、読み出して画面に表示しています。外部ファイルはどこにも存在していません。
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); に置き換えることができるので、先程のコードは次のように書く事もできます。
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 では次のようにして外部ファイルを扱う事ができます。
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 という名前のファイルができる事になります。
ここまで標準 Pascal の検証には PASCAL-P5 を使ってきたのですが、残念ながら "任意の" 外部ファイルのアクセスには対応していません。
次のようにプログラムヘッダの DATAFILE をコメントアウトすると PASCAL-P5 でも実行できますが、DATAFILE が外部ファイルとみなされないため、DATAFILE というファイルは当然生成されません。
program FileTest3(Output{, DATAFILE});
...
何故外部ファイルがこんな扱いになっているのかと言うと、クラシック Pascal は外部ファイルの扱い方も環境依存と考えていたからです (外部ファイルが磁気テープのようにファイル名の使えないデバイスでもいい)。標準 Pascal においても、プログラム引数 (ファイル型) の外部実体への結び付け方は処理系定義となっています。
この仕様だとファイルシステムのある OS の場合にはパラメータ名や変数名にドット . が使えないため、拡張子のあるファイル名は使えず、別のディレクトリにあるファイルも扱うことができない事になってしまいます。この問題を解決するには何らかのバインド機構が必要となります。いずれにせよ、完全なファイル名やファイル位置をプログラムから指定する事は不可能です。
Delphi だと必ずファイル変数と外部ファイルを関連付けて扱う必要があり、そのための AssignFile() という手続きが用意されています。
| 手続き | 説明 | 
|---|---|
| AssignFile() | 外部ファイルの名前をファイル変数と関連付ける。 Turbo Pascal では Assign()。 | 
| CloseFile() | ファイル変数と外部ファイル名の関連付けを終了する。 Turbo Pascal では Close()。 | 
ANSI 版 Delphi だと標準 Pascal との違いは AssignFile() の記述だけです。
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 だとちょっと変更が必要です。
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)
- 標準ルーチンと入出力 (DocWiki)
- ファイル入出力のサポート (DocWiki)
- ファイル記述子 (Wikipedia)
- 12. プログラム引数 (ファイル型) の外部実体への結び付け方 - Delphi における Pascal の処理系定義 (Qiita)
9.2. テキストファイル (Textfiles)
テキストファイルは文字の列からなる可変長の行のファイルです。テキストファイル型を宣言するには定義済みの型 Text を使います。
テキストファイル型 =
  [packed] file of Text.
テキストファイル型は基底型 Char のファイル型に (仮想の) 改行を加えて拡張したものによって定義されます。テキストファイル型は "(packed) file of Char" と同義ではありません。改行は次のテキストファイル型用手続き / 関数を使って生成したり検出したりできます。
| 手続き | 説明 | 
|---|---|
| Readln(F) | テキストファイル F を次の行の先頭まで読み飛ばす。 F^ には次の行の最初の文字が入る。 | 
| Writeln(F) | テキストファイル F に改行を追加する。 | 
| 関数 | 説明 | 
|---|---|
| Eoln(F) | テキストファイル F が改行位置の時に True を返す。 | 
テキストファイル型は基底型 Char のファイル型を拡張したものですから、文字の読み書きには Read(F, Ch) と Write(F, Ch) を使います。Ch は Char 型の変数です。
プログラムヘッダに記述することのできる Input と Output は標準入力と標準出力に割り当てられた宣言済みのテキストファイル型変数です。Delphi には標準エラー出力に割り当てられた ErrOutput もあります。
See also:
- テキストファイル (DocWiki)
- シーケンシャルアクセス (Wikipedia)
- ランダムアクセス (Wikipedia)
- System.Input (DocWiki)
- System.Output (DocWiki)
- System.ErrOutput (DocWiki)
(9.2.1.) バッファ変数の代替
Delphi 6 以降だと、TTextRec を使って、バッファ変数の代替を実装できます。次の関数はファイルポインタを進めずに 1 文字読み込みます。
  function CurrentChar(var F: Text): AnsiChar;
  begin
    Eoln(F);
    Result := (TTextRec(F).BufPtr + TTextRec(F).BufPos)^;
  end;
次のコードはファイルポインタが指す文字を読みますが、ファイルポインタは移動しないため、無限ループとなります。
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;
この手法が使えるのはファイルが Text (またはエイリアスの TextFile) 型の場合のみです。
ファイルバッファはデフォルトで 128 バイトありますが、SetTextBuf() を使ってもっと大きなバッファを割り当てる事もできます。
See also:
(9.2.2.) ファイルの追記
Rewrite() の代わりに Append() を使うとテキストファイルに追記する事ができます。
| 手続き | 説明 | 
|---|---|
| Append() | 既存のファイルを、末尾にテキストが追加されるように設定します。 | 
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 を使います。
var
  F: file of Byte;
  Buf: Byte;
読み書きには Read() と Write() を使います。1 バイト単位の読み書きとなります。
  Read(F, Buf); 
  Write(F, Buf); 
この際、Eof() は 0x1A を EOF とはみなしません。
See also:
(9.4.) 型なしファイル
Delphi には型なしファイル型というものがあります。バイナリファイルを扱うファイル型です。”型なし" なので、複数の任意の型を使ってファイルを読み書きする事ができます。
型なしファイル型 =
  [packed] file.
| 手続き | 説明 | 
|---|---|
| BlockRead(F) | ファイルから 1 つ以上のレコードを読み取って 変数に格納する。 | 
| BlockWrite(F) | 1 つ以上のレコードを変数から 開いているファイルに書き込む。 | 
型なしファイルを使う場合、Reset() と Rewrite() の第二パラメータにブロック転送用のレコードサイズを指定する必要があります。指定しない事も可能ですが、その際のレコードサイズは 128 バイトであり、このサイズ以下での読み書きはできません。
型なしファイルでは Read() と Write() の代わりに BlockRead() および BlockWrite() という 2 つの手続きを使用します。これにより、高速なデータ転送を行うことができます。
BlockRead() および BlockWrite() の 3 番目のパラメータ Count は読み書きするレコードサイズの数を指定します (レコードサイズが 1 でない限り、バイト数ではありません)。
BlockRead() はファイル F から「Reset() で指定したレコードサイズ * Count」バイトのデータを Buf に読み込みます。
BlockWrite() は Buf の先頭から「Rewrite() で指定したレコードサイズ * Count」バイトのデータをファイル F へ書き出します。
型なしファイルでは EOF() の判定が難しい事があります。その場合には、実際に読み出されたサイズとバッファのサイズを比較してファイル終端を判定します。
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:
(9.5.) 標準入出力ルーチン
Delphi の標準入出力ルーチンです。
| ルーチン | 説明 | 型付き | 型なし | テキスト | 
|---|---|---|---|---|
| Append | 既存のファイルを、末尾にテキストを追加するように設定します。 | 〇 | ||
| AssignFile | 外部ファイルの名前をファイル変数に関連付けます。 | 〇 | 〇 | 〇 | 
| BlockRead | 開いているファイルから 1 つ以上のレコードを読み取って変数に格納します。 | 〇 | ||
| BlockWrite | 1 つ以上のレコードを変数から、開いているファイルに書き込みます。 | 〇 | ||
| CloseFile | ファイル変数と外部ディスクファイルとの関連付けを終了します。 | 〇 | 〇 | 〇 | 
| Eof | ファイルポインタがファイルの終わりに達したかどうかを検査します。 | 〇 | 〇 | 〇 | 
| Eoln | ファイルポインタが行末に達したかどうかを検査します。 | 〇 | ||
| Erase | 外部ファイルを削除します。 | 〇 | 〇 | 〇 | 
| FilePos | 現在のファイルポインタの位置を返します。 | 〇 | 〇 | 〇 | 
| FileSize | ファイル内のレコード数を返します。 | 〇 | 〇 | 〇 | 
| Flush | 出力用に開いたテキストファイルのバッファを空にします。 | 〇 | ||
| Read | ファイルからデータを読み取ります。 | 〇 | 〇 | |
| Readln | ファイルからテキストを 1 行読み取ります。 | 〇 | ||
| Rename | 外部ファイルの名前を変更します。 | 〇 | 〇 | 〇 | 
| Reset | 既存のファイルを開きます。 | 〇 | 〇 | 〇 | 
| Rewrite | 新しいファイルを作成して開きます。 | 〇 | 〇 | 〇 | 
| Seek | 現在のファイルポインタを指定の位置に移動します。 | 〇 | 〇 | |
| SeekEof | ファイルの EOF(end-of-file)ステータスを返します(ホワイトスペースは無視)。 | 〇 | ||
| SeekEoln | ファイルの行末ステータスを返します(ホワイトスペースは無視)。 | 〇 | ||
| SetTextBuf | テキストファイルに入出力バッファを割り当てます。 | 〇 | ||
| Truncate | 現在のファイルポインタから後のすべてのレコードを削除します。 | 〇 | 〇 | |
| Write | 型付きファイルまたはテキストファイルにデータを書き込みます。 | 〇 | 〇 | |
| WriteLn | テキストファイルに書き込んで、行末マーカーを追加します。 | 〇 | 
索引
[ ← 8. 集合型 ] [ ↑ 目次へ ] [ → 10. ポインタ型 ] 

