講義第12回:Pascal プログラミング --- 文字列とレコード型

計算機というと、計算するもの、つまり数字を扱うものという感じがするが、 実際には計算機は文字(英字、漢字)の処理にも広く使われている。例えば、 みなさんが使っているエディタ(mule)や Latex、あるいはワープロソフトは、 まさにそのような文字を扱うプロ グラムである。今日は、そのような文字処理の基礎について学ぶ。

文字列

program printer(input, output);
var line    :varying[128] of char;
    filename:varying[128] of char;
    infile  : text;
begin
    write('Enter file name:');
    readln(filename);
    reset(infile, filename);
    while not eof(infile) do begin
        readln(infile, line);
        writeln(line);
    end;
end.
このプログラムは、ファイルの名前を入力すると、その中身を画面に表示する。

文字列

var line    :varying[128];
という変数宣言は、 line が文字列をしまえる配列であり、そこには最 大 128 文字まで入れられるという宣言である。 これは、 array [1..128] of charという配列宣言とほとんど同じ意味であるが、128文字 いっぱいではなくて途中で終るということを表現できるかどうかというあたり で違っている。以下、この varying 型の配列を文字列という。

varying 型の文字列も、 integer や real の変数と同じように、 readln でキーボード(またはファイル)から読み込んだり、 writeln や write で書 き出したりすることができる。

ファイルを使った入出力

これまで、プログラムの入力をファイルから読んだり、逆に出力をファイルに 書くのには「リダイレクト」を使っていた。つまり、実行時に
program < infile >outfile
としていた。これは簡単で便利だが、例えば対話的に処理して画面にいろいろ 書いたうえで、結果をファイルにしまおうという場合にはちょっと面倒である。 そのために、直接プログラムのなかでファイルを指定して読み書きすることが できる。

上でやっているのは読む場合であり、

    infile  : text;
で「テキスト型」の変数を宣言すると、これがファイルとして使える。 filename が文字列であると、
    reset(infile, filename);
とすることで、 infile という変数が filename で指定されたファイルに関係 付けられる。というと意味が良くわからないが、要するに、
        readln(infile, line);
というふうに、 infile をつけると、キーボードからではなくさっき filename で指定したファイルから読むということである。

ちなみに、ファイルに書くときには、例えば

    filename:varying[128] of char;
    outfile  : text;
begin
    ......
    rewrite(outfile, filename);
    writeln(outfile, 'This output goes to filename');
    ......
というような具合になる。

文字列定数

例えば writeln('Enter file name'); とか assign(infile, 'sample.dat'); とかいうふうに、「'」(シングルクォーテーション、単一 引用符)でかこった文字列は、変数名とかではなくその中身そのものというふう に解釈される。これは、「'」でかこった文字列は、「文字列定数」になるというふうに言い替えることもできる。 つまり、プログラムの中で、ある決まった文字列を使いたければ、「'」でか こった文字列を使う。例えば、 line という文字列型の変数に、 'abc' という値を入れるには line := 'abc'; とすればいい。 (ここで、「'」そのものは line には書き込まれないことに注意)

改良

program printer(input, output);
var line    :varying[128] of char;
    filename:varying[128] of char;
    infile  : text;
    lineno  : integer;
begin
    write('Enter file name:');
    readln(filename);
    reset(infile, filename);
    lineno := 1;
    while not eof(infile) do begin
        readln(infile, line);
        writeln(lineno:8, ':', line);
        lineno := lineno + 1;
    end;
end.

練習

  1. 最初にファイルの名前も書き出すように、プログラムをさらに改良 してみよう。
  2. さらに、自分のクラス、学生証番号、名前も書き出すようにし、レ ポート提出に使えるようなプログラムをつくってみよう。

レコード型

これまで、何回かソートのプログラムを扱ったが、数字だけが並べ変えられて もそれほど嬉しいわけではない。ソートのプログラムを実際に使うのは、例え ば、学生証番号と試験の点がペアになったデータがあって、試験の点の順に並 べ変えたいとかいう場合である。つまり、いくつかのフィールド(ここでは学 生証番号と試験の点)が組になったデータ(こういうものをレコードという) があって、それをあるフィールドを基準に並べ変えるわけである。そのような プログラムを効率良く記述するために、 Pascal ではレコード型というものが ある。

program record_sort(input, output);
type student_record = record
      name : varying[30] of char;
      id   : integer;
      score: integer;
     end;
var n : integer;
  student  : array[1..100] of student_record;
  i,j      : integer;
  work     : student_record;
  infile   : text;
procedure read_student(var student : student_record);
begin
    readln(infile,student.name);
    readln(infile,student.id);
    readln(infile,student.score);
end;
procedure write_student( student : student_record);
begin
    writeln(student.score:4,student.name:20, student.id:8);
end;

begin
    reset(infile, 'student.dat');

    n := 0;
    while not eof(infile) do begin
        n := n +1;
         read_student(student[n]);
    end;

    writeln('Input data');
    for i := 1 to n do write_student(student[i]);
    for i := 1 to n - 1 do begin
        for j := i + 1 to n do begin
            if student[i].score < student[j].score then begin
                work := student[i];
                student[i]:= student[j];
                student[j]:= work;
            end;
        end;
    end;
    writeln('Sorted data');
    for i := 1 to n do write_student(student[i]);
end.
type student_record = record
      name : varying[30];
      id   : integer;
      score: integer;
     end;
というところで、 student_record という名前のレコード型を宣言 (定義)している。ここでは、 student_recordというレコードは、 name id scoreという3個のフィールドを持ち、それぞれ は varying[30] of char, integer, integer 型であると宣言している。このよう にしておくと、このレコード型の変数というものを使える。 例えば、 var a_student : student_record; と宣言したとする。この時、それぞれのフィールドを、 a_student.score := 0; とか writeln(a_student.name); とかいっ たように、変数名{.}フィールド名という形式で使うことができる。 また、レコード型同士の代入もできる。(サンプルプログラム参照)

入力ファイルサンプル

Makino, J.
140391
49
Tsutsumi, E.
250011
80
Yamaguchi, K.
040124
95
Sugimoto, D.
999999
100
Ueda, K.
430011
70

余談

Pascal のレコード型は、配列とはまた違った形でデータに構造を与えること を可能にする。これをもう少し発展させたものが C++ やJava のような「オブ ジェクト指向」言語における「クラス」というものになっている。基本的には、 ある実体というか、プログラムのなかで扱いたい操作対象をまとめて一つの 「オブジェクト」として扱うということである。例えば、上のプログラムでは、 扱う対象は人(学生)で、このプログラムのなかでは人というのは名前、学籍 番号、試験の成績という3つの属性だけをもつものというふうになっているわ けである。

練習

  1. 学生証番号でもソートできるようにプログラムを改良してみよう。
  2. 現在のプログラムでは、名前が右詰めで出力される(はずである)。 これはあまり読みやすいとはいえない。名前だけは左詰めで出力されるように プログラムを改良するにはどうすればいいだろうか。
  3. (ちょっと難しい)名前のアルファベット順でソートするようにプログラムを改良して みよう。
    (ヒント) 文字列 x y がアルファベット順でどちらが先かを 調べるには、先頭の文字から順に見ていく。 x i 番目の文字 は、 x[i] (ただし、先頭は x[1])である。文字どうしは、整数 や実数と同じように、関係演算子 '<' や '>' を使って比較できる。なお、順 番は、空白文字、','などの記号、A, B, .... Z, a, b, .... z という順になっ ている。