9.5 標準入出力
9.5.1 デフォルトの入出力先
これまで printf() 関数は画面に文字列を表示させ、scanf() 関数はキーボードから文字列を入力させることができると解説してきました。これは、基本的には間違いではありませんが正確な表現ではありません。入出力について正しく理解している技術者であれば「printf() 関数は標準出力に文字列を出力し、scanf() 関数は標準入力から文字列を入力する」と表現することでしょう。
実は、C言語では画面やキーボードに対するデータ入出力をファイルと同じように操作できます。前回説明したように、入出力は全てがストリームという概念に統一されているためです。開発者は、文字列の出力先がファイルでも画面でもネットワークでも、同一の手法で開発することができるのです。しかし、頻繁に入出力が行われる可能性の高いデバイス、つまり、画面への出力やキーボードからの入力は特別扱いされます。多くのシステムでは、ディスプレイを標準出力、キーボードを標準入力として定めています。
標準出力とは、プログラムがアプリケーション利用者にメッセージを表示させたい場合に、文字列を出力するべき場所を表し、標準入力は何らかの情報を入力させる場合に対象となるデバイスを表します。このほかに、エラーメッセージを表示させる専用の標準エラーと呼ばれるデバイスも用意されます。標準エラーも、通常はディスプレイです。
標準出力や標準入力、標準エラーはプログラムが定めるべきものではありません。これらは、システムの利用者が設定で定めるべき問題です。ある利用者はプログラムが画面に出力する文字をプリンターやログファイルに保存したいと思うかもしれません。この場合は、プログラムを書き直すのではなく、標準出力を変更すればよいのです。通常、標準出力や標準入力はシステムの設定で変更することができるのです。これをリダイレクトと呼びます。リダイレクトについては使っているシステムのヘルプを参照してください。
printf() 関数は、画面に文字を表示させる関数ではなく、標準出力に文字列を表示させる関数であり、scanf() 関数は標準入力に入力を求める関数だったのです。例えば、Microsoft MS-DOS を使っている場合はコマンドラインから次のようにプログラムを起動させると、標準出力を変更することができます。
C:\...>dir > dir.txt
これは、dir コマンドを使ってカレントディレクトリ内の情報を表示させていますが、画面に表示されるはずの文字列が表示されず、dir.txt というファイルが生成されます。このファイルが標準出力として扱われ、文字列が保存されます。
このように、文字列を結果として出力するコマンドレベルのアプリケーションの場合は、ファイル操作をする必要はそれほどないのです。強制的にファイル操作を行うよりも、むしろ利用者に出力先を委ねた方が、柔軟で扱いやすいソフトウェアになるでしょう。
標準出力や標準入力、標準エラーもストリームなので、ファイル関連関数で扱うことができます。ファイル関連関数で入出力を行うには、FILE 構造体へのポインタが必要ですが、stdio.h ヘッダファイルで以下のストリームポインタが提供されているため、これらをつかって標準ストリームにアクセスすることができます。
FILE *stdin;
FILE *stdout;
FILE *stderr;
これらのポインタは定数のため、新しい値を代入できません。stdin 定数は標準入力、stdout 定数は標準出力、stderr 定数は標準エラーとなっています。関数などがエラーを返し、プログラムが正常に実行できないことをユーザーに通知する場合、これまでは printf() 関数を使いましたが、正確には stderr に出力するべきであると考えられます。
#include <stdio.h> int main(int argc , char *argv[]) { char chError[0xFF]; fputs("出力するエラー文字列を入力してください>" , stdout); fgets(chError , 0xFF , stdin); fputs(chError , stderr); return 0; }
コード1は、stdout、stdin、stderr を利用して対話処理を行っています。例えば最初の fputs() 関数は標準出力に文字列を表示しています。これは、普段ならば printf() 関数などを使いますが、このようにファイル入出力関数と定義済みの標準入出力ストリームを使ってプログラムすることも可能なのです。
しかし、これらの関数を使った場合は文字列単位の入出力しか行えません。printf() 関数や scanf() 関数のような書式制御をファイル入出力関数で行うことができれば便利だと考えるでしょう。実は、ストリームを指定できる fprintf() 関数と fscanf() 関数というものが用意されています。
int fprintf( FILE *stream, const char *format [, argument ]...);
int fscanf( FILE *stream, const char *format [, argument ]... );
基本的に、第1引数に入出力先のストリームを指定できること以外は printf() や scanf() 関数と同じです。fprintf() 関数は成功すると出力した文字数を、失敗すると負数を返します。fscanf() 関数は成功すると入力されたデータの個数を、失敗すると EOF を返します。これらの関数を使えば、ファイル入出力においても printf() や scanf() 関数のようにプログラムすることができます。逆に
printf("Kitty on your lap");
は
fprintf(stdout , "Kitty on your lap");
に等しいと考えることができます。
#include <stdio.h> int main() { char chSelect; fprintf(stdout , "出力先を選択してください o/e>"); fscanf(stdin , "%c" , &chSelect); switch(chSelect) { case 'e': case 'E': fprintf(stderr , "Kitty on your lap\n"); break; case 'o': case 'O': fprintf(stdout , "Kitty on your lap\n"); break; } return 0; }
コード2は、fscanf() 関数で stdin 定数をパラメータに指定しているため scanf() 関数と同じように標準入力からデータを読み込みます。プログラムはユーザーに標準出力か標準エラーのどちらに出力するかを選択させています。その後 fprintf() 関数を用いて標準出力、または標準エラーに文字列を出力します。
このプログラムでは単純に標準入力や標準出力に対して入出力していますが、パラメータにファイルを指す FILE 構造体のポインタを設定すれば、書式指定を用いてデータの書き込みや読み込みを行えます。