第 12 回 入力

本日の内容


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

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

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

標準出力

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

dir > file1

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

echo abc > file2

標準エラー出力

コマンドは本来の出力の他、処理中のエラーも出力する必要があります。 そのため、出力チャネルに標準出力の他、標準エラー出力というエラー専用の 出力チャネルがあります。 これは通常は標準出力と同様画面にそのまま出力されます。 しかし、リダイレクトを指定すると エラーの出力と本来の出力を別ファイルに収集することも出来ます。 但し、リダイレクト可能なのは Windows 2000 以降のコマンドプロンプトだけ で、MS-DOS プロンプトでは外部ツールが無いとできないので注意が必要です。

例えば route コマンドは単体では使用法を出力しますが、これは標準エラー メッセージに出力されるため、route > ファイル名 としても ファイルには保存できず、画面にメッセージが出力されるだけです。 ところが、 route 2> ファイル名 とすると出力がファイルに 保存できます。 なお、標準エラー出力を標準出力にリダイレクトすることも出来ます。 それには 2>&1を指定します。 したがって、route 2>&1 > ファイル名とすると標準エ ラー出力への出力を標準出力経由でファイルにリダイレクトすることも出来ま す。

標準入力

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

route のように標準エラー出力に出すものをパイプ処理するには次のようにし ます。

route 2>&1 | more

12-2. 入力

getchar 関数

C 言語で OS が提供している標準入出力(Standard Input Output) を使用する には、 #include <stdio.h> をプログラムから指定する必要があります。 stdio.h には今まで使用した printf 関数もあります。 printf 関数は画面に出力すると説明しましたが、本来はは標準出力に文字を 書く関数です。 stdio.h には様々な関数が登録されていますが、標準入力から一文字得るとい う最も基本的な入力関数として getchar() 関数があります。 c=getchar() とすると、一文字得ることができます。 この関数はファイルの終りに達すると、文字でない EOF を返します。 つまり、 getchar 関数は文字あるいは(文字でない) EOF を返します。 そのため、getchar 関数は char 型ではなく int 型の変数で受ける必要があります。

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


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

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


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

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


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

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

文字コード

さて、文字は int で読み込むことを学びました。 一方 int に対しても printf で %c を指定するとそれが文字として出力され ます。 このように int と char は文字を扱う上では等価になっていて、代入なども 問題なくできます。

例12-1


#include <stdio.h>
main(){
  int a;
  char c;
  a='a';
  c=a;
  a=c;
  printf("%c %c\n",a,c);
}

これは一体どういう仕組みになっているのでしょうか? 実は、 char は int(整数型) の特別な型で、通常は 8bit (つまり -128 から 127) のみを表すことになっています。 また、文字とは実は文字を表す 文字コード という数値で取り扱 います。 したがって、 char 型でも計算は出来ますし、 %d を指定すると文字の代わり に文字コードを出力します。

例12-2


#include <stdio.h>
main(){
  char a,b;
  a='A';
  printf("%c の文字コードは %d\n",a,a);
  b=a+1;
  printf("%c の文字コードは %d\n",b,b);
}

コンピュータの最も基本的な文字コードは ASCII コードと呼ばれ ます。 ASCII コードは 7bit、つまり 0 から 127 までの数に文字が割り当てられて います。 なお、コード割り当ては 16 進数を意識して割り振られてますので、ここでは 16 進数を併記します。 C 言語で 16 進数を使うには数の前に 0x を付けます。 16 進数で 20 は C 言語では 0x20 と表します。

0 から 31 まで(16 進数で 0x00 から 0x1f)
コントロールコードと言い、改行記号 など画面に表示する文字ではなく、出力の制御に対してコードが割り当てられ ています。
32から(16 進数で 0x20 から 0x2f)
32 は空白で、その後は基本的な文字が割り当てられています。
48 から(16 進数で 0x30 から 0x3f)
48 は '0' が割り当てられ、その後、 '1', '2' と連続してコードが割 り当てられています。
64 から(16 進数で 0x40 から 0x5f)
64 には'@' が割り振られ、 65 には 'A' が割り振られます。 以降順に 'B', 'C' となってます。
96 から(16 進数で 0x60 から 0x7f)
96 には'`'(バッククォート) が割り振られ、 97 には 'a' が割り振られます。 以降順に 'b', 'c' となってます。
その他
32 から 47 までの領域の他、0〜9, A〜Z, a〜z 以外のコードには演算子やカッ コなどの記号が割り当てられています。 但し 127(0x7f) は一文字削除を表す DEL というコントロールコードが割り当 てられてます。

なお、ASCII コードは各国で通貨記号を加えるなど若干の変更が加えられて使 用されてます。これを地域化(Localization L10N)と言います。 例えば、JIS X201 という日本で使用されるコンピュータコードは基本的には ASCII コードと同様ですが、 93 番目の文字がバックスラッシュから円記号に、 126 番目の文字がチルダからオーバーラインに変えられてます。

例12-3

以下のプログラムは ASCIIコード(というより JIS X201)の文字割り当て表を 出力します。 なお、 printf で 16 進数を表示するには %x を使います。


#include <stdio.h>
main(){
  int c;
  for (c=0x20; c<=0x7f; c++){
    if(c%16==0){
      printf("%3d(0x%2x): ",c,c);
    }
    printf("'%c'",c);
    if(c%16==0xf){
      printf("\n");
    }else{
      printf(",");
    }
  }
}
出力例
 32(0x20): ' ','!','"','#','$','%','&',''','(',')','*','+',',','-','.','/'
 48(0x30): '0','1','2','3','4','5','6','7','8','9',':',';','<','=','>','?'
 64(0x40): '@','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O'
 80(0x50): 'P','Q','R','S','T','U','V','W','X','Y','Z','[','\',']','^','_'
 96(0x60): '`','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o'
112(0x70): 'p','q','r','s','t','u','v','w','x','y','z','{','|','}','~',''

scanf

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


scanf("%d",&i);

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


scanf("%*c");

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

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

例12-4

次は入力した値を 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-3. ファイルの入出力

次に、標準入出力ではなくファイル名を指定したファイルの取り扱い方をこ こでは紹介します。

ファイルの取り扱い

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

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

なお、標準入力は stdin、標準出力は stdout、標準エラー出力は stderr と いう名前のファイルハンドルを持ちます。

書き込み

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

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

例12-5

次は 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-6

次は先ほどの 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" にします。 読み込みではファイルが存在しない場合はエラーになります。

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

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

例12-7

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


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

12-4. 付録

練習問題

演習12-1

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

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

演習12-2

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

#include <stdio.h>
main(){
  int 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 文 で場合分けをしてそれぞれの動作をするようなプログラムを作りなさい。


演習12-10

文字コードは 0〜9, A〜Z, a〜z で連続しています。 したがって、大文字の 'B' と小文字の 'b' では それぞれ 'A' と 'a' から同じだけ離れています。 これを式に書くと次のようになります。

'B'-'A' = 'b'-'a'

したがって、 'A' を移行すると次のように小文字の 'b' を大文字 'B' に 変える式になります。

'B' = 'b' - 'a' + 'A'

さて、問題です。 小文字の文字列が含まれている文字配列 mojiretsu 中の文字を大文字に変換 して出力しなさい。


char mojiretsu[]="test";

演習12-11

次のように小文字以外の文字も含まれている文字列に対して、小文字の部分だ け大文字に変換するプログラムを書きなさい。


char mojiretsu[]="This is a pen";
ヒント

文字変数 c が小文字であるかどうかは、文字コードが 'a' 以上 'z' 以下で あるかを調べれば分かります。

演習12-12

標準入力に与えられたファイル中の英小文字を英大文字にして出力するプログ ラムを書きなさい。 また、そのプログラムに対して作成したプログラムのソースコードを入力して 出力を得なさい。

演習12-13

標準入力に与えられたファイル中の数字は全て '0'、 英大文字は 'A'、英小 文字は 'a' に置き換えるプログラムを書きなさい。 また、そのプログラムに対して作成したプログラムのソースコードを入力して 出力を得なさい。

演習12-14

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

1.4 3.8
2.5
1.1

演習12-15

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


演習12-16

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


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