標準入出力

標準出力(stdout)

すでにputs関数やprintf関数を何気なく使っていますが、標準出力について説明していませんでした。
標準出力とは、プログラムが書き出すデータのストリームのことで、とくに指定しない限り端末に出力されます。
と、書けば察しがつくと思いますが、標準出力は変えられます。リダイレクトでググってください。
それ故、puts, printf関数などは「端末に文字を表示する関数」としばしば誤解されます。
なお、stdoutとはSTanDard OUTputの略です。C++は違いますが、他の言語でもこの言葉が使われることがおおいです。

よく使う標準出力関係の関数

int puts(const char *str );
int printf(const char *format [,argument]...);
int fprintf(FILE *stream, const char *format [,argument ]...);
int fputc(int c, FILE *stream);
int putchar(int c );

C言語の標準出力関数、とくにprintf関数はとても高機能です。それ故にセキュリティ上注意すべきこともあります。

標準エラー出力(stderr)

はっきりいって出番は少ないです。が、標準出力をたとえばファイルにリダイレクトした時、エラーはコンソールに出す、みたいなときには便利ですし、習慣的にエラーは標準エラー出力に出力することになっています。

よく使う標準出力関係の関数

int fprintf(FILE *stream, const char *format [,argument ]...);

標準入力(stdin)

C言語の文字列操作ははっきり言ってクソです。どれくらいクソかというと、バッファオーバーランを無視する関数が平気であります。
その代表格がgets関数です。文字列を受け取る関数なのですが、あまりの危険性から、C99では非推奨、そしてついにC11では使用禁止になりました。
そんなC言語でどうにかこうにか安全に標準入力を扱おう、というのが今回の目標です。

まさかの愚痴スタートです、すみませんでした。標準入力とは、プログラムに入ってくるテキストデータのストリームです。
殆どの場合、キーボードから文字入力を受けます。言語を問わず数値入力を受けるためには一度文字列として読み込み、数値に変換する、という作業を行いますが、他の言語ではその作業を意識することはありません。 先程から、C言語の標準入力は糞だ、と言っていますが、少なくとも文字列の入力を受けることに関しては改善されてきています。
MSVCではVisual Studio 2005の頃から、C標準ライブラリーを置き換える関数群を提供しています。scanf_sなどのように末尾に「_s」が付きます。
またC11でこれに追従する(というよりほぼパクリ・・・)ように同名の関数を提供しています。
まあ、実例を見て行きましょうか。

2通り目と3通り目の文字列読み込みでは、文字入力で大概の参考書で真っ先に使うscanf関数を紹介しましたが、個人的にはscanf関数は使いにくいなぁという印象です。
私はfgetsで改行文字ごと読み込んで改行文字をNULL文字に置き換えるほうが好きです。理屈が単純ということはそれだけバグを減らせるので。
もちろんscanfにはそれなりの良さがあるわけですが、だとしても3番目のように一度読み込んでからsscanfしますね。

get_integer_num関数については簡単に説明すると、まず、fgets関数で文字列を受け、文字列をstrtol関数で数値に変換しています。

というわけでポインターと文字列について何一つ説明していないのにばんばん使っていますが、すみません。すぐに解説します。

練習問題1

以下のソースコードは標準入力から数値を受け取り、指定範囲内でない数値に対しては再度入力を促すプログラムである。

しかし、これは意図した動作をしない。問題点を可能な限り多くあげよ(getnum関数はget_integer_num関数のもとになった関数でエラー対策に難がありますが、そこはスルーしてください)

#include<stdio.h>
#include<stdlib.h>
int getnum(void){
	char s[100];
	long t;
	char *endptr;
	fgets(s, 100, stdin);
	errno = 0;
	t = strtol(s, &endptr, 10);
	if (errno != 0 || *endptr != '\n' || (t < INT_MIN || INT_MAX < t))
		return -1;
	return t;
}
int getnum_customized(const int max, const int min){
	if (max < min)	return -1;
	int flag0;
	do{
		flag0 = getnum();
		if (flag0 < min || flag0 > max)
			system("cls");
			printf("再入力してください。\n");
	} while (flag0 < min || flag0 > max + 1);
	return flag0;
}
int main(void){
	printf("値を入力してください。\n");
	const int flag = getnum_customized(100, 0);
	printf("取得した値は%dです。\n", flag);

	return 0;
}

練習問題2

ここでは、コンピュータがランダムに考えた数字を、回答とそれに対するヒントにより当てて行くゲームを数当てゲームと呼ぶことにします。
とりあえず乱数の範囲は5~80とし、10回まで回答できるものとします。
応答としてはこんなかんじです。

イメージは湧いたでしょうか?というわけでこれを作ってください・・・といっても穴埋めです。以下のmain関数をいじり、必要なら関数を追加してプログラムを完成させてください。
input関数の使い方は
http://www.biboro.org/snippet/398
を参照。