C言語編 第45章 コマンドライン 解答ページ

先頭へ戻る

このエントリーをはてなブックマークに追加

問題@

問題@ エラーメッセージは、標準出力ストリームではなく、標準エラーストリームへ出力すべきです。 その理由を答えて下さい。


標準出力は、リダイレクトによって出力先を変更できます。 これに対し、標準エラーはリダイレクトできません。

環境によっては、標準エラーでもリダイレクトできることがありますが、C言語の規格上は不可能です。

ユーザが見逃してはならない重要なメッセージは、確実にコンソールの画面上に表示させるべきですから、 標準エラーへ出力するようにします。

問題A

問題A コマンドライン引数から、0個以上の整数を受け取り、その合計を標準出力に出力するプログラムを作成して下さい。


#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>

int my_strtol(const char* str, int radix, long int* result);


int main(int argc, char *argv[])
{
	int i;
	long int num, sum;

	if( argc < 2 ){
		fputs( "コマンドライン引数が不足しています。\n", stderr );
		exit( EXIT_FAILURE );
	}

	sum = 0;
	for( i = 1; i < argc; ++i ){
		if( my_strtol( argv[i], 10, &num ) == 0 ){
			fprintf( stderr, "%s は整数ではありません。\n", argv[i] );
			exit( EXIT_FAILURE );
		}
		sum += num;
	}

	printf( "合計:%ld\n", sum );

	return 0;
}

/*
	strtol関数のラップ。
	引数:
		str:	変換元の文字列。strtol関数の第1引数と同じ。
		radix:	基数。strtol関数の第3引数と同じ。
		result:	変換結果を受け取るアドレス。NULL は不可。変換に失敗した場合には、何も格納されない。
	戻り値:
		変換が成功したら 0以外、失敗したら 0 が返される。
*/
int my_strtol(const char* str, int radix, long int* result)
{
	long int num;
	char* end;

	assert( str != NULL );
	assert( result != NULL );

	errno = 0;
	num = strtol( str, &end, radix );
	if( errno == ERANGE && (num == LONG_MAX || num == LONG_MIN) ){
		return 0;    /* 変換結果が表現できない */
	}
	else if( str == end ){
		return 0;    /* 1文字も変換できない */
	}

	*result = num;
	return 1;
}

コマンドライン引数

30 55 -20

実行結果

65

渡されるコマンドライン引数の個数は不明ですから、勝手に 3個であるとか、5個以下であるとか想定してはいけません。 これに関しては、argc の値を調べるだけの話なので、それほど難しくは無いでしょう。

コマンドライン引数は文字列として渡されるので、整数化しないと合計を計算できません。 文字列から整数への変換には、strtol関数(⇒リファレンス)を使います。 本編で解説したように、strtol関数はエラーチェックが行えるものの、非常に面倒ではあるので、ラップした関数を使っています。

ちなみに、strtol関数は "-20" のように負数を与えられても、正しく機能します。 一方で、"20a" のように余計な文字が含まれた文字列が渡されても、変換できる部分までで結果を返すので、こういうものをエラーとしたいのなら、 第2引数の結果を使って、より詳細にチェックしなければなりません。

問題B

問題B コマンドライン引数に、

myprogram 15 * -3

のように、「整数」「演算子」「整数」を渡したとき、全体を計算式とみなして計算結果を標準出力へ出力するプログラムを作成して下さい。 (環境によっては、* の部分がうまく解釈されないかも知れません。その場合は、

myprogram 15 '*' -3

のように、' ' で囲むことを試してみてください。


#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <assert.h>

int my_strtol(const char* str, int radix, long int* result);


int main(int argc, char *argv[])
{
	long int left, right;
	long int ans = 0;

	if( argc < 4 ){
		fputs( "コマンドライン引数が不足しています。\n", stderr );
		exit( EXIT_FAILURE );
	}

	if( my_strtol( argv[1], 10, &left ) == 0 ){
		fprintf( stderr, "%s は整数ではありません。\n", argv[1] );
		exit( EXIT_FAILURE );
	}
	if( my_strtol( argv[3], 10, &right ) == 0 ){
		fprintf( stderr, "%s は整数ではありません。\n", argv[3] );
		exit( EXIT_FAILURE );
	}

	switch( argv[2][0] ){
		case '+':
			ans = left + right;
			break;
		case '-':
			ans = left - right;
			break;
		case '*':
			ans = left * right;
			break;
		case '/':
			if( right == 0 ){
				fputs( "除算において、右側の数値が 0 であってはいけません。\n", stderr );
			}
			ans = left / right;
			break;
		case '%':
			if( right == 0 ){
				fputs( "除算において、右側の数値が 0 であってはいけません。\n", stderr );
			}
			ans = left % right;
			break;
		default:
			fputs( "演算子が無効です。\n", stderr );
			exit( EXIT_FAILURE );
			break;
	}

	printf( "ans: %ld\n", ans );

	return 0;
}

/*
	strtol関数のラップ。
	引数:
		str:	変換元の文字列。strtol関数の第1引数と同じ。
		radix:	基数。strtol関数の第3引数と同じ。
		result:	変換結果を受け取るアドレス。NULL は不可。変換に失敗した場合には、何も格納されない。
	戻り値:
		変換が成功したら 0以外、失敗したら 0 が返される。
*/
int my_strtol(const char* str, int radix, long int* result)
{
	long int num;
	char* end;

	assert( str != NULL );
	assert( result != NULL );

	errno = 0;
	num = strtol( str, &end, radix );
	if( errno == ERANGE && (num == LONG_MAX || num == LONG_MIN) ){
		return 0;    /* 変換結果が表現できない */
	}
	else if( str == end ){
		return 0;    /* 1文字も変換できない */
	}

	*result = num;
	return 1;
}

コマンドライン引数

15 * -3

実行結果

-45

strtol関数を使えば、数値化するのは簡単なので、結局のところ、それほど難しくはないはずです。

この解答例では、演算子の部分はやや手を抜いています。 2つ目のコマンドライン引数の1文字目だけを参照しているので、"*a" のように余分な文字が付いていても無視されています。 こういう部分もしっかりエラーチェックしてみても良いでしょう。


参考リンク

更新履歴

'2014/1/19 OS X に対応。

'2010/7/23 新規作成。



第45章のメインページへ

C言語編のトップページへ

Programming Place Plus のトップページへ