連続的にデータを読み込み、意味のある文字列や数値を取り込んでいく 方法を実例とともにおぼえよう。
キーボードから何かを入力するときの基本的な手順は、
fgets関数でstdinから読み込む
 atoiで数値化したりする
となる。より複雑なデータを読む場合も基本的な手順は同じである。
たとえば、一行の中に複数の項目が含まれるデータを読むことを考えよう。 以下のような成績データがあったとしよう。
山田太郎 50 公益太郎 90 飯森花子 91 鶴岡一人 60 酒田三吉 52 三川一二三 12
このデータを読み込み、全員の平均点を求めて 表示するようなプログラムを作ってみよう。
平均点を計算するためには、まず全員のデータを読み込んで、得点と人数を 求める必要がある。
最初に、データファイルを全部読み、合計点を求めよう。 データを全部読むループは次のように書く。
#include <stdio.h>
#include <stdlib.h>
int main()
{
  char buffer[100];
    :
  while (NULL != fgets(buffer, sizeof buffer, stdin)) {
    :
  }
}
fgets 関数は、読み込みに成功した場合は読み込んだデータの
入っているアドレス(つまりbuffer)を返す。データの終了に
達した場合は特別なコード NULL を返す。したがって、
データがある限り読み続けたい場合は、fgets の値が
NULL でないことを確認する条件式を while に
与えてループを構成すると良い。
データを読み込んで行番号を付けて表示するだけのプログラム 
disp.c は以下のようになる。
#include <stdio.h>
int main()
{
  char buffer[100];
  int i=1;
  while (NULL != fgets(buffer, sizeof buffer, stdin)) {
    printf("%3d: %s", i++, buffer);
  }
}
実際にコンパイルして実行してみよう。
% gcc -o disp disp.c % ./disp やーやー 1: やーやー はろはろー 2: はろはろー [C-d]
キーボードからデータを入力する場合、データの最後を示すときには 行の先頭で C-d をタイプする。
データをその都度いちいち入れたのではたいへんなので、普通は
データとなる内容をファイルに保存しておいて、これをCで作成したプログラム
に流し込むようにする。fgets の第3引数に指定した
stdin は、標準入力(STandard INput) という意味で、
これはプログラムを直接起動すると、キーボードから、以下のようにパイプライ
ンの途中に起動すると、直前のプログラムが出力したものを入力として
受け取る。
% cat disp.c | ./disp
  1: #include <stdio.h>
  2: 
  3: int main()
  4: {
  5:   char buffer[100];
  6:   int i=1;
  7:   while (NULL != fgets(buffer, sizeof buffer, stdin)) {
  8:     printf("%3d: %s", i++, buffer);
  9:   }
 10: }
確認のため、cat disp.c だけで起動した場合の
結果も見ておくこと。
cat disp.c の代わりに、
cal
 ls -l
 ls
などのコマンドについて
./disp を起動
の両方について実行してみよ。
実際の試験データの1行分、
山田太郎 50
これを、文字列と数値に分解する方法にはいくつかあるが、
項目が空白で区切られているという前提のときに、最も手軽に利用できるのが
sscanf 関数である。
sscanf
      文字列の分解と変数への代入
#include <stdio.h> int sscanf(const char *str, const char *format, ...);
という書式で、文字列 str にある文字列を、
      与えられたフォーマット(format)をもとに解析して
      値を取りだす。取り出した値は、第3引数以後に指定したアドレスにある
      変数に格納する。
sscanf の利用例を挙げてみよう。元の文字列が変数
buffer に入っているとしよう。内容は以下のとおり。
山田太郎 50
これは、
文字列 空白(の連続) 数値(整数)
という並びである。これを解析するフォーマットはprintfの
ものとほぼ同じであり、sscanfで、以下のようにする。
sscanf(buffer, "%s %d", 文字列をしまうアドレス, 整数をしまうアドレス)
まず、「文字列をしまう」変数、と「整数をしまう」変数を用意しよう。
char name[50]; int point;
このように宣言したならば、sscanfは以下のようにする。
sscanf(buffer, "%s %d", name, &point);
ここで変数のアドレスを指定する方法 を思い出しておこう。
sscanf に与える第2引数は以下の意味を持つ。
%s
      空白以外のものが続く文字列
1個以上の空白文字(スペース・タブ)が続く
%d
      int型に変換できる整数
ただし、入力したデータから文字列を読み取るときは、
宣言したchar配列より長いものが入力されるとプログラムが破壊される
ことがある。これを防止するには%sに最大限の長さを指定し、
sscanf(buffer, "%49s %d", name, &point);
とするのが安全である。これで char name[50] には
最大49文字分の文字列と\0が納まることが保証される(はみ出た部分は切り捨て
られる)。
これをまとめて、連続する成績データを解析して、名前と得点を抜き出す プログラムは以下のように書ける。
#include <stdio.h>
int main()
{
  /* 1行は100バイト、氏名は50バイトあればいいだろう */
  char buffer[100], name[50];
  int point;
  while (NULL != fgets(buffer, sizeof buffer, stdin)) {
    sscanf(buffer, "%49s %d", name, &point);
    printf("%s さんは %d 点でした\n", name, point);
  }
  puts("以上");
}
データファイル(score.txt)を与えてこのプログラムを実行す
ると以下のようになる。
% cat score.txt | ./listup
山田太郎 さんは 50 点でした
公益太郎 さんは 90 点でした
飯森花子 さんは 91 点でした
鶴岡一人 さんは 60 点でした
酒田三吉 さんは 52 点でした
三川一二三 さんは 12 点でした
以上
文字列と整数(int)以外のデータをsscanfで読み込む場合の
フォーマット指定についてもまとめておく。
%d
      整数だと見なしてint型変数に値を取り込む
%ld   (エルディー)
      整数だと見なしてlong型変数に値を取り込む
%f
      浮動小数点数だと見なしてfloat型変数に値を取り込む
%lf   (エルエフ)
      浮動小数点数だと見なしてdouble型変数に値を取り込む
sscanfでは、入力した行に必要な情報が決められた順番で書か
れていることを仮定している。実際にはおかしな入力行が来る場合もある。
これを調べるには、sscanfが返す値(返却値)を確認する。
sscanfは、実際に書式指定子で取り込めた値の箇数を返す。
たとえば、
sscanf(buffer, "%49s %d", name, &point);
とした場合、sscanf の第3引数以後に指定した変数(のアドレス)
は2個である。両方が正しく読み込めた場合は
2を返すが、入力データが中途半端で name しか代入できなかった
場合は sscanf 自体の値は1となる。
これを必ず調べることで、異常データがあった場合に変な値を 取り込まずに済む。以上をふまえてプログラムを修正すると 以下のようになる。
#include <stdio.h>
int main()
{
  /* 1行は100バイト、氏名は50バイトあればいいだろう */
  char buffer[100], name[50];
  int point;
  while (NULL != fgets(buffer, sizeof buffer, stdin)) {
    if (2 == sscanf(buffer, "%49s %d", name, &point)) {
      printf("%s さんは %d 点でした\n", name, point);
    }
  }
  puts("以上");
}
実際に、変なデータをまぜて実行してみよう。
山田太郎 50 公益太郎 90 20 飯森花子 鶴岡一人 60 酒田三吉 52 三川一二三 12
このデータをプログラムに与えて実行する。
% cat bad-score.txt | ./listup (飯森花子さんも表示してしまう) % cat bad-score.txt | ./listup2 (飯森花子さんは表示しない)
scanfについてあらかじめ形式が決められた入力データから、文字列や数値を切り出すとき
にはfgets+sscanfを使うのが基本である。
ただし、初心者用のC言語の参考書では、標準入力から書式つき読み込み
を行なうのにscanf関数を利用するように説明していることが多い。
scanfは以下のように使う。
  char name[50];
  int  x, y;
  while (EOF != scanf("%s %d %d", name, &x, &y)) {
    /* nameに文字列、xとyに整数が入る */
    /* データ処理 がこのループに入る */
  }
一見、記述量が少なくて簡単に見えるが、scanf 関数は
入力データが期待とおりの書式でない場合にプログラムが暴走する
可能性を持っているので、実用的なプログラムでは使わない。
よって、この授業では scanf 関数は利用しないことを
約束とするが、試験(資格試験や期末試験)などには必ず登場するので
少なくとも読んで理解できるようにしておくこと。