<12> テキストファイルの入出力 (標準 Pascal 範囲内での Delphi 入門)

# 12. テキストファイルの入出力

**標準 Pascal** の**外部ファイル**の扱いについては[ファイル型](https://qiita.com/ht_deko/items/986fa01c9e6cfc3fd673)の章で述べた通りで、現在のシステムでは使い物になるとは思えません。Delphi やその他の Pascal では **AssignFile()** 等の拡張が行われているものが殆どです。

Delphi の場合、ファイルの入出力については、外部ライブラリを使う事になりますが **TStringList** クラスや **TMemoryStream** クラス、**TStreamReader** クラスを使うのが便利だと思います...身も蓋もないのですが。

**See also:**

 - [System.Classes.TStringList (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Classes.TStringList)
 - [System.Classes.TMemoryStream (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Classes.TMemoryStream )
 - [System.Classes.TStreamReader (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Classes.TStreamReader)
 - [Delphi 10.3 Rio で CSV を処理する (Qiita)](https://qiita.com/ht_deko/items/2802677fe1cfaa4d1332)


## 12.1. 標準ファイル入出力 (Standard Files Input and Output)
標準テキストファイル変数 **Input** と **Output** は、コンピュータの標準入力 (キーボード) と標準出力 (ディスプレイ) に割り当てられています。

これらの 2 つのテキストファイル変数は頻繁に使用するため、標準入出力を操作する時にはファイル変数の指定が省略できるようになっています。

```pascal
program Console(ABC, Output, Input, DEF);
var
  ABC: file of Text;
  DEF: file of Text;
  { Input と Output は定義する必要はない }
begin
  ...
```

ファイル変数の指定は省略できるのですが、**標準 Pascal** ではそれでも[プログラムヘッダ](https://qiita.com/ht_deko/items/2d5996cbfc82d734b247) に **Input** と **Output** を記述しなければなりません。

```pascal
program Console(Input);
var
begin
  Writeln('Hello,world.'); { NG: 標準出力が使えない }
end.
```

標準入出力操作時の手続き/関数のパラメータには次の関係が成り立ちます。

```
Write(Ch)  = Write(Output, ch)
Read(Ch)   = Read(Input, ch)
Writeln    = Write(Output)
Readln     = Readln(Input)
Eof        = Eof(Input)
Eoln       = Eoln(Input)
Page       = Page(Output)
```


Delphi ではプロジェクトオプションでコンソールアプリケーションの生成を行うようになっているか `{$APPTYPE Console}` コンパイラ指令があれば、標準テキストファイル変数 **Input** と **Output** が自動的に使えるようになっています。

## 12.2. 手続き Read と Readln

Read() と Readln() のパラメータの渡し方による解釈は次のようになります。

```
Read(V1, ..., Vn)     = Read(Input, V1, ..., Vn)
Read(F, V1, ..., Vn)  = begin Read(F, V1); ... Read(F, Vn) end
Readln(V1, ..., Vn)   = Readln(Input, V1, ..., Vn)
Readln(F, V1, ..., Vn)  = begin Readln(F, V1); ... Readln(F, Vn) end
```

|手続き|説明|
|:---|:---|
|[Read()](http://docwiki.embarcadero.com/Libraries/ja/System.Read)|ファイルからデータを読み込む。|
|[Readln()](http://docwiki.embarcadero.com/Libraries/ja/System.Readln)|ファイルからテキストを 1 行読み込む。|

**標準 Pascal** で Read() を使って電卓プログラムを書くと次のようなコードになります。

```pascal:calc.pas
program Calc(input, output);
var
  op: Char;
  ans, num: Real;
begin
  op := '+';
  ans := 0;
  repeat
    Read(num);
    case op of
      '+': ans := ans + num;
      '-': ans := ans - num;
      '*': ans := ans * num;
      '/': ans := ans / num;
    end;
    Read(op);
  until op = '=';
  Writeln(ans: 1 : 4);
end.
```

次のように入力すると

```
100+100=〔Enter〕
```

このような結果が得られます。

```
200.0000
```

しかしながら Delphi ではうまく動作しません。Delphi で Read() による入力を確定させるためには 〔Space〕(空白)・〔Tab〕(タブ)・〔Enter〕(行末マーカ) のいずれかの**ブランク文字**が必要だからです。では、次のように〔Space〕で区切って入力すればいいのでしょうか？

```
100 + 100 =〔Enter〕
```

これでもエラーになってしまいます。何故なら `Read(op);` でブランク文字を読んでしまうからです。正しく動作させるためには `Read(op);` の部分を次のように書き換え、ブランク文字をスキップするようにします。

```pascal
    repeat
      Read(op);
    until (op in ['+', '-', '*', '-', '=']);
```

このような処理にしても、数値と演算子の間の〔Space〕を忘れるとエラーになってしまうので使い勝手は悪いです。

Delphi における Read() 手続きには制約があるため、Readln() で文字列として一行読み取り、自前で入力行を処理した方がいいでしょう。

**See also:**

 - [コンソールアプリケーション２（Delphi Advent Calendar 2012-12-18）(.delphi maniacs)](http://delphimaniacs.blogspot.com/2012/12/delphi-advent-calendar-2012-12-18.html)

## 12.3. 手続き Write と Writeln

Write() と Writeln() のパラメータの渡し方による解釈は次のようになります。

```
Write(P1, ..., Pn)     = Write(Output, P1, ..., Pn)
Write(F, P1, ..., Pn)  = begin Write(F, P1); ... Write(F, Pn) end
Writeln(P1, ..., Pn)   = Writeln(Output, P1, ..., Pn)
Writeln(F, P1, ..., Pn)  = begin Writeln(F, P1); ... Writeln(F, Pn) end
```

|手続き|説明|
|:---|:---|
|[Write()](http://docwiki.embarcadero.com/Libraries/ja/System.Write)|ファイルにデータを書き込む。|
|[Writeln()](http://docwiki.embarcadero.com/Libraries/ja/System.Writeln)|ファイルにテキストを 1 行書き出す。|

この Write() と WriteLn() は特殊な手続きで、実パラメータリストが通常の手続きとは異なる**書き出しパラメータ (書き込みパラメータ)** リストになっています。このパラメータの形式は見慣れないので面食らってしまうと思います。

```
書き出しパラメータリスト =
  "(" (ファイル変数 | 書き出しパラメータ) { "," 書き出しパラメータ} ")".
書き出しパラメータ =
  式 [":" 整数式　[":" 整数式] ].
```

### (12.3.1.) 書き出しパラメータ (書き込みパラメータ / Write-parameters)
**書き出しパラメータ (書き込みパラメータ)** は次の形式のいずれかとなります。

```
e
e : w
e : w : f
```

e, w, f はいずれも式です。**e** は書き出したい値で、**w** はフィールドの最小幅、**f** は e が[実数型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#25-real-%E5%9E%8B)の時の小数部の長さです。型可変で可変長パラメータな特殊な構文です。

e が[実数型](https://qiita.com/ht_deko/items/5c04bb2b42947e29d94b#25-real-%E5%9E%8B) の時、f を省略すると浮動小数点表現 (Floating Point Representation)、指定すると固定小数点表現 (Fixed point Representation) となります。

```pascal:FormatText.pas
program FormatText(Output);
begin
  Writeln(True     : 10);     { Boolean }
  Writeln(10000    : 10);     { Integer }
  Writeln('A'      : 10);     { Char    }
  Writeln('ABC'    : 10);     { String  }
  Writeln(1234.567 : 10 : 2); { Real    }
end.
```

上記コードの実行結果は次の通りです。

```
      True
     10000
         A
       ABC
   1234.57
```

この書式は処理系依存な所もありますし、Delphi ならちゃんとした文字列も使えますので、外部ライブラリを使う事になりますが **Format()** 関数を使った方が柔軟に出力できると思います。

**See also:**

 - [書き出しパラメータの説明 (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Write#.E8.AA.AC.E6.98.8E)
 - [System.Str (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.Str)
 - [System.SysUtils.Format (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/System.SysUtils.Format)

## 12.4. 手続き Page()
Delphi に **Page()** 手続きはありません。Page(F) の挙動も処理系依存です。

|手続き|説明|
|:---|:---|
|Page()|ページ送りを行う。|

恐らく多くの処理系では 0x0C (FF: Form Feed) が出力されるのだろうと思われます。

**See also:**

 - [制御文字 (Wikipedia)](https://en.wikipedia.org/wiki/Control_character)

## (12.5.) コマンドラインパラメータ
そういえば [3 章](https://qiita.com/ht_deko/items/2d5996cbfc82d734b247) で、

> C 言語の main() 関数の argc / **argv をご存知でしたら一旦完全に忘れてください。

と書いたきりでした。もうお気付きかと思いますが、**標準 Pascal では実行ファイルに渡されたコマンドラインパラメータを処理する方法がありません**。

`C 言語の main() 関数の argc / **argv` というのは、

```c
#include <stdio.h>

int main(int argc, char *argv[]) {

  ...

  return 0;
}
```

こんな感じの引数の事です。

argc がコマンドラインパラメータ（実行可能ファイル自体の名前も含む）の数、argv が文字列のポインタの配列です。argv[0] には実行プログラムのフルパスファイル名が入ります。argv[1] にはコマンド ラインでプログラム名の後に入力された最初の文字列のポインタが、argv[2] には 2 番目の文字列のポインタが入ります。

Delphi でコマンドラインパラメータを処理したい場合には、**ParamCount()** 関数と **ParamStr()** 関数を使います。

|関数|説明|
|:---|:---|
|[ParamCount()](http://docwiki.embarcadero.com/Libraries/ja/System.ParamCount)|コマンドラインで渡されたパラメータの数を返す。|
|[ParamStr()](http://docwiki.embarcadero.com/Libraries/ja/System.ParamStr)|指定されたパラメータをコマンドラインから<br>取得して返す。|

ParamCount() はコマンドラインパラメータが渡されていない時は 0 を返します。
ParamStr(0) は実行プログラムのフルパスファイル名を返します。


**See also:**

 - [コマンドライン サポート メンバ (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%83%A9%E3%82%A4%E3%83%B3_%E3%82%B5%E3%83%9D%E3%83%BC%E3%83%88_%E3%83%A1%E3%83%B3%E3%83%90)
 - [Main() の引数 (DocWiki)](http://docwiki.embarcadero.com/RADStudio/ja/Main()_%E3%81%AE%E5%BC%95%E6%95%B0)


# 索引
[ [← 11. 手続きと関数](https://qiita.com/ht_deko/items/b93ac03bfee002f17137) ] [ [↑ 目次へ](https://qiita.com/ht_deko/items/e2796788c65c2cda1de5) ] [ [→ 付録](https://qiita.com/ht_deko/items/15b27e00fac9d723d9f0) ] [:sushi:](https://qiita.com/ht_deko/items/bbbd35ac89dfb2268c88)