] > Input

第 12 回 入力

本日の内容


このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。

12-1. 標準入出力(Standard I/O)

MS-DOS プロンプトやコマンドプロンプトでは、キーボードを押すと字が入力 され、画面に字が表示されます。 しかし、このキーボードや画面の関係を変えることが可能です。 例えば、プログラムの実行結果をファイルに残したり、プログラムにキーボー ドからではなくファイルの内容を入力したりできます。 コンピュータの入出力は基本的にはキーボードと画面が対応していますが、特 定のファイルに変更が可能になっています(この入出力の変更のことをリ ダイレクトと言います)。

標準出力

標準出力を変更するにはコマンドを入力した後、「> 出力ファイル名」 を付け足します。 例えば、ディレクトリの内容をファイルに保存するには次のようにします。

dir > file1

このようにするとディレクトリの内容は画面に表示されず、 file1 に保存さ れます。 echo コマンドは「画面に文字を出すコマンド」として紹介しましたが、 実際は標準出力に対して文字を出すコマンドです。 次のようにすると、 file2 に abc という内容が書かれます。

echo abc > file2

演習12-1

以下の問いに答えなさい。各ファイル名は自分で任意に指定しなさい。

  1. コマンド verの出力をファイルに保存しなさい。
  2. コマンド dir /wの出力をファイルに保存しなさい。
  3. 次のプログラムの出力をファイルに保存しなさい。
    #include <stdio.h>
    main(){
      printf("Hello World!\n");
    }
    

標準入力

標準入力を変更するにはコマンドを入力した後、「< 入力ファイル名」を つけます。 例を説明する前に、次のファイルを作って下さい。ファイル名は file3 にして下さい。

3
1
2

このファイルを sort コマンドに入力するには次のようにします。

sort < file3

すると、画面に与えた数が小さい順に出力されます。

EOF

sort コマンドはファイルの内容を小さい順に並べるコマンドです。 ファイルの最後に一番小さい値が来たら、その値を先頭に表示する必要がある ため、 sort の処理はファイルを全て読み終えてからでないと実行できません。 sort に限らず、ファイルを処理するプログラムはファイルの終りを知る必要 があります。 そのため、 OS はファイルが終るとEOF記号(End of File)という 特殊な記号(番兵)をプログラムに与えます。これにより、プログラムはファイ ルが終ったことを知ることができます。 MS-DOS や Windows では Ctrl-Z という記号になります(UNIX 系では Ctrl-D)。 例えば、次のようにすると、 sort コマンドはキーボードから入力した数字を 並べ替えます。

例
sort[Enter]
3[Enter]
1[Enter]
2[Enter]
[Ctrl-Z][Enter]

その他

コマンドの出力をファイルに「追加」したい時は >> を使用 します。

例
echo abc > file1
echo def >> file1
echo aaa >> file1
type file1
sort < file1

コマンドの出力を、他のコマンドの入力にしたい時は |(縦棒) を使用しま す。このように複数のコマンドをつなげて処理することをパイプ処理 と言います。

例
dir | sort
dir | more
dir | sort | more

12-2. 入力

getchar 関数

C 言語で OS が提供している標準入出力(Standard Input Output) を使用する には、 #include <stdio.h> をプログラムから指定する必要があります。 そして、 printf 関数は標準出力に文字を書く関数です。 一方、標準入力から一文字得るには getchar() 関数を使います。 この関数を使用すると、入力された文字が文字型の値として得ることができま す。 つまり c=getchar() とすると、一文字得ることができます。 ファイルの終りに達すると EOF という値になります。

ファイルの終りまで一文字ずつ読みながら処理をするには、一文字読む毎に EOF かどうか判定する必要があります。 つまり、プログラムは「(A)一文字読み、読んだ文字が EOF でないとき→(B) 読んだ文字を処理をする」ということ繰り返すことになります。 つまり概念的には次のようなプログラムになります。

while(一文字読み、読み込んだ文字が EOF でないとき){
  読んだ文字を処理する
}

while 文の条件は式の値を利用すると次のように書けます。

while((c=getchar())!=EOF){
  読み込んだ文字の入っている変数 c に対する処理
}

次は標準入力を標準出力にただ書き写すプログラムです。

#include <stdio.h>
main(){
  char c;
  while((c=getchar())!=EOF){
    printf("%c",c);
  }
}

なお、Windows 98 や Windows Me の MS-DOS プロンプトはキーボードから EOF(Ctrl-Z) を受けとると次の出力文が無効になるというバグがあります。 入力を受けとり終ってから何か出力する際は、空の行を打つ printf 文が必要 になります。但し、Windows 2000 や Windows XP などのコマンドプロンプト では大丈夫です。

演習12-2

次のプログラムを完成させ、ファイルの文字数を数えるようにしなさい。 また、このプログラムが何文字あるか、このプログラムの標準入力に与えて調 べなさい(つまり 「.\a.exe < このプログラム.c」 を行う)。

#include <stdio.h>
main(){
  char c;
  int n=0;
  while((c=getchar())!=EOF){
    /* 一文字受けとったら、 n を一増やす */
  }
  printf("\n"); /* MS-DOS プロンプトのバグ対策 */
  printf("合計 %d 文字\n",n);
}

演習12-3

ファイルの行数を数えるプログラムを書きなさい。 また書いたプログラムが何行あるか作ったプログラムで調べなさい。

ヒント

実は正確にやろうとすると難しい。 「改行の数を数える」という方法で妥協して良い。


演習12-4

変数 x に文字を与えておき、その文字がファイル中に何文字現れたかを数え るプログラムを書きなさい。


演習12-5

入力ファイルの空白を全て取り除いたものを出力するプログラムを作りなさい。


演習12-6

ファイルの最初から 5 行だけを表示するプログラムを作りなさい (ファイルが全部で 5 行以下の時にどうするかも考えなさい)。

ヒント

今までの問題では while 文の条件として単純に EOF を検出すれば良かったわ けですが、今回は 5 行表示したら終了しても良いので while 文の条件として 別のものも考えることができます。 素朴に考えれば、従来とおりの while 文を書いて、繰返し文の中で if 文を 使い、 5 行以内の時だけ出力するように書けます。 しかし、while 文の条件として「 EOF ではなく、しかも 5 行以内」という条 件を書けば、 while 文の中は単純に入力された文字を出力するだけになりま す。


演習12-7

ファイルに行番号をつけるプログラムを作りなさい。 例えば次のようなファイルがあったとします。

abc
def
ghi

このときこのファイルを入力すると次の出力が出るようにしなさい。

1: abc
2: def
3: ghi

(なお余裕があったら、ファイルが空の時に何も出力しないように工夫しなさ い)。


演習12-8

入力ファイルに対して、改行を無視し(全く無視するほか、空白を代わりに一 つ入れても良い)、幅 10 文字で出力を四角く表示するプログラムを作りなさ い。 そして、作成したプログラムにこのソースコードを入れて出力が予想通りにな るか確かめなさい。


演習12-9

入力ファイルに対して、一行が 10 文字以上の場合、 10 文字ずつ折り返すプ ログラムを作りなさい。

ヒント

改行するのは、改行文字が来た時と、 10 文字を越えた時です。同時に起こっ ても単に改行するだけです。 そこで、入力文字が改行文字かそうでないかと、文字数を数え 10 文字かそう でないかで場合分けします。

文字数改行文字その他の文字
10 文字??
9 文字まで?文字を印字し、文字数を増やす

この空欄にはその時プログラムが何をするかを埋めなさい。 そして、 while 文の中で if 文 で場合分けをしてそれぞれの動作をするようなプログラムを作りなさい。


scanf

C 言語で変数に数値を入力するには scanf を使います。 使い方は printf とほぼ同様ですが、変数の指定では変数名の前に & 記 号を書きます(理由は詳しい本に譲ります)。 例えば変数 i に整数を入力する時は次のようにします。


scanf("%d",&i);

float に対しては %f を、 double に対しては %lf を、 char に対しては %c を指定します。 また、 % と 文字の間に *(アスタリスク)を入れると読み飛ばしを意味し、対 応する変数は必要無くなります。 一文字読み飛ばすには次のようにします。


scanf("%*c");

scanf は同時に複数の変数の値を得ることができます。 scanf は代入が成功した変数の数を値として返します。 つまり、例えば、数字を入力させたいのに文字を入れられたらその値は代入さ れず、入力された数には数えられません。 ここで注意したいのは、変数への代入に失敗した場合、入力された値は保持さ れたままになります。 したがって、同じ読み込みを繰り返すと同じ失敗を繰り返すので、入力が失敗 したままプログラムは止まらなくなります。 そこで、入力に失敗した時は文字を読み飛ばすように別の scanf を指定する 必要があります。

なお、ファイルの終りを検出すると EOF を返します。

例12-1

次は入力した値を 2 倍して返すプログラムです。

#include <stdio.h>
main(){
  float x;
  int c;
  while((c=scanf("%f",&x))!=EOF){
    if(c==1){ /* x に正しく値が代入された時だけ */
      printf("%f\n",2*x);
    }else{/* データはあるが数値でない時*/
      scanf("%*c"); /*一文字読み飛ばし*/
      /* while の条件より EOF でない */
    }
  }
}

なお scanf は空白や改行を読み飛ばしますので、複数の数値を入力する時は、 適宜空白や改行で区切ります。 また、プログラムにキーボードから直接値を入力する時は、 Enter キーを押 すまではプログラムに値が渡らないことに注意して下さい。

演習12-10

ファイルの中の数を合計するプログラムを書きなさい。 そして次の値の入っているファイルを作り、プログラムに与え、正常に動作す るかどうか確認しなさい。

1.4 3.8
2.5
1.1

演習12-11

ファイルの中の数の平均を計算するプログラムを書きなさい。


演習12-12

ファイルの中の数の最大値を出力するプログラムを書きなさい。

12-3. ファイルの入出力

標準入出力は UNIX の文化です。 UNIX のコマンドは基本的に標準入出力を利用するように作られていて、コマ ンド同士をパイプで結合して使用するようになっています。 しかし、標準入出力ではなくファイル名を指定したファイルの取り扱い方をこ こでは紹介します。

ファイルの取り扱い

プログラム中ではファイルはファイル名ではなくファイルハンドル で管理されます。 これは FILE* 型の変数に入れて使用します。

ファイルは使用前に fopen 関数でオープンします。 そして、読み込み、書き込みなどをした後、fclose 関数でクローズ します。

なお、標準入力は stdin、標準出力は stdout、通常の出力と分けてエラーメッ セージを出力するための標準エラー出力は stderr という名前のファイルハン ドルを持ちます。

書き込み

ファイルを作成してデータを書き込むには書き込みモードでオープンする必要 があります。 fopen にはファイル名とファイルのモードを指定しますが、ファイルモードに は "w" を指定します。 その時、ファイルを作成できないような状況(ディスクフルや、書き込み専用 ドライブ、不正なファイル指定)では、ファイルハンドルの代わりに NULL と いう特別な値が得られます。

なおファイルに文字を書くには fprintf 関数を使います。 最初の引数はファイルハンドルを書き、後は printf 同様、文字列と必要に応 じて変数列を書きます。

例12-2

次は fprintf という関数を使って abc というファイルに Hello, World とい う文字を書き込みます。 fprintf は書き込みに失敗するとマイナスの値を返します。


#include <stdio.h>
main(){
  char filename[]="abc";
  FILE* fh;
  if((fh=fopen(filename,"w"))==NULL){
    fprintf(stderr,"ファイル%sを作成できません。",filename);
  }else{
    if(fprintf(fh,"Hello World\n")<0){
      fprintf(stderr,"書き込みに失敗しました。");
    }
    fclose(fh);
  }
}

これを実行した後に type abc と打つと、ファイルが作成されて 正しくデータが書き込まれたことがわかると思います。 なお、 fprintf(stdout,...) と printf(...) は同じ意味になります。


また既存のファイルの後ろにデータを追加する場合、fopen のファイルモード を "a" にします。 指定したファイルが存在しない場合は新たに作ります。

例12-3

次は先ほどの abc ファイルに "How are you doing?" を付け加えます。


#include <stdio.h>
main(){
  char filename[]="abc";
  FILE* fh;
  if((fh=fopen(filename,"a"))==NULL){
    fprintf(stderr,"ファイル%sをオープンできません。",filename);
  }else{
    if(fprintf(fh,"How are you doing?\n")<0){
      fprintf(stderr,"書き込みに失敗しました。");
    }
    fclose(fh);
  }
}

読み込み

ファイルを読み込む場合、 fopen のファイルモードは "r" にします。 読み込みではファイルが存在しない場合はエラーになります。

ファイルから一文字を得る関数は fgetc 関数です。 引数にファイルハンドルを指定します。

なお、scanf に対応する fscanf もあります。

例12-4

以下はファイルを読み込 んで画面に表示するプログラムです。


#include <stdio.h>
main(){
  char filename[]="abc";
  FILE* fh;
  char c;
  if((fh=fopen(filename,"r"))==NULL){
    fprintf(stderr,"ファイル%sをオープンできません。",filename);
  }else{
    while((c=fgetc(fh))!=EOF){
      printf("%c",c);
    }
    fclose(fh);
  }
}

坂本直志 <sakamoto@c.dendai.ac.jp>
東京電機大学工学部情報通信工学科