Programming Place Plus トップページ – C言語編 – 第45章
問題① コマンドライン引数から、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[])
{
if( argc < 2 ){
( "コマンドライン引数が不足しています。\n", stderr );
fputs( EXIT_FAILURE );
exit}
long int sum = 0;
for( int i = 1; i < argc; ++i ){
long int num;
if( my_strtol( argv[i], 10, &num ) == 0 ){
( stderr, "%s は有効な整数値ではありません。\n", argv[i] );
fprintf( EXIT_FAILURE );
exit}
+= num;
sum }
( "合計:%ld\n", sum );
printf}
/*
strtol関数をラップしたもの
引数:
str: 変換元の文字列。strtol関数の第1引数と同じ。
radix: 基数。strtol関数の第3引数と同じ。
result: 変換結果を受け取るポインタ変数。変換に失敗した場合には、何も格納されない。
戻り値:
変換が成功したら 0以外、失敗したら 0 が返される。
*/
int my_strtol(const char* str, int radix, long int* result)
{
( str != NULL );
assert( result != NULL );
assert
= 0;
errno char* end;
long int num = strtol( str, &end, radix );
if( errno == ERANGE ){
if( num == LONG_MAX ){
( "変換結果が上限値を超えた。\n, stderr );
fputs}
if( num == LONG_MIN ){
( "変換結果が下限値を超えた。\n, stderr );
fputs}
return 0;
}
else if( str == end ){
( "1文字も変換できなかった。\n", stderr );
fputsreturn 0;
}
*result = num;
return 1;
}
コマンドライン引数
30 55 -20
実行結果
65
渡されるコマンドライン引数の個数は不明ですから、勝手に 3個であるとか、5個以下であるとか想定してはいけません。 これに関しては、argc の値を調べるだけの話なので、それほど難しくはないでしょう。 また、argv[0] はプログラムの名前が入る場所なので、見ないようにします。
コマンドライン引数は文字列として渡されるので、整数化しないと合計を計算できません。文字列から整数への変換には、strtol関数を使います。本編で解説したように、strtol関数はエラーチェックが行えるものの、非常に面倒ではあるので、ラップした関数を使っています。
問題② コマンドライン引数に、
15 * -3 test
のように、「整数」「演算子」「整数」を渡したとき、全体を計算式とみなして計算結果を標準出力へ出力するプログラムを作成してください。(環境によっては、* の部分がうまく解釈されないかもしれません。その場合は、
15 '*' -3 test
のように、’ ’ で囲むことを試してみてください。
#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[])
{
if( argc < 4 ){
( "コマンドライン引数が不足しています。\n", stderr );
fputs( EXIT_FAILURE );
exit}
long int left;
if( my_strtol( argv[1], 10, &left ) == 0 ){
( stderr, "%s は有効な整数値ではありません。\n", argv[1] );
fprintf( EXIT_FAILURE );
exit}
long int right;
if( my_strtol( argv[3], 10, &right ) == 0 ){
( stderr, "%s は有効な整数値ではありません。\n", argv[3] );
fprintf( EXIT_FAILURE );
exit}
long int ans = 0;
switch( argv[2][0] ){
case '+':
= left + right;
ans break;
case '-':
= left - right;
ans break;
case '*':
= left * right;
ans break;
case '/':
if( right == 0 ){
( "除算において、右側の数値が 0 であってはいけません。\n", stderr );
fputs}
= left / right;
ans break;
case '%':
if( right == 0 ){
( "除算において、右側の数値が 0 であってはいけません。\n", stderr );
fputs}
= left % right;
ans break;
default:
( "演算子が無効です。\n", stderr );
fputs( EXIT_FAILURE );
exitbreak;
}
( "ans: %ld\n", ans );
printf}
/*
strtol関数をラップしたもの
引数:
str: 変換元の文字列。strtol関数の第1引数と同じ。
radix: 基数。strtol関数の第3引数と同じ。
result: 変換結果を受け取るポインタ変数。変換に失敗した場合には、何も格納されない。
戻り値:
変換が成功したら 0以外、失敗したら 0 が返される。
*/
int my_strtol(const char* str, int radix, long int* result)
{
( str != NULL );
assert( result != NULL );
assert
= 0;
errno char* end;
long int num = strtol( str, &end, radix );
if( errno == ERANGE ){
if( num == LONG_MAX ){
( "変換結果が上限値を超えた。\n, stderr );
fputs}
if( num == LONG_MIN ){
( "変換結果が下限値を超えた。\n, stderr );
fputs}
return 0;
}
else if( str == end ){
( "1文字も変換できなかった。\n", stderr );
fputsreturn 0;
}
*result = num;
return 1;
}
コマンドライン引数
15 * -3
実行結果
-45
strtol関数を使えば、数値化するのは簡単なので、結局のところ、それほど難しくはないはずです。
この解答例では、演算子の部分はやや手を抜いています。 2つ目のコマンドライン引数の1文字目だけを参照しているので、“*a” のように余分な文字が付いていても無視されています。 こういう部分もしっかりエラーチェックしてみても良いでしょう。
問題③ C言語のソースファイルやヘッダファイルを読み込んで、コメント部分を除去した結果を出力するプログラムを作成してください。読み込むファイルの名前は、コマンドライン引数から受け取るようにしてください。
たとえば、次のようになります。
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
bool getNextChar(FILE* fp, char* c);
bool skipToNextLine(FILE* fp);
int main(int argc, char* argv[])
{
if( argc < 2 ){
( "コマンドライン引数が不足しています。\n", stderr );
fputs( EXIT_FAILURE );
exit}
FILE* fp = fopen( argv[1], "r" );
if( fp == NULL ){
( stderr, "%s のオープンに失敗しました。\n", argv[1] );
fprintf( EXIT_FAILURE );
exit}
int inComment = 0;
char c[2];
for( ;; ){
if( !getNextChar(fp, &c[0]) ){
break;
}
if( inComment ){
// コメントの終端かどうか
if( c[0] == '*' ){
// コメントの終端かどうかは、2文字読み取らないと判断できない
if( !getNextChar(fp, &c[1]) ){
// ファイルの末尾が '*' の可能性があるので、忘れずに c[0] の出力が必要
( c[0] );
putcharbreak;
}
if( c[1] == '/' ){
= 0;
inComment }
else{
// コメントの終端でなかったら、読みすぎた1文字を返す
( c[1], fp );
ungetc
(c[0]);
putchar}
}
else {
(c[0]);
putchar}
}
else{
// コメントの開始かどうか
if( c[0] == '/' ){
// コメントの開始かどうかは、2文字読み取らないと判断できない
if( !getNextChar(fp, &c[1]) ){
// ファイルの末尾が '/' の可能性があるので、忘れずに c[0] の出力が必要
( c[0] );
putcharbreak;
}
if( c[1] == '*' ){
// 「/* ~ */」形式のコメント開始
= 1;
inComment }
else if( c[1] == '/' ){
// 「//」形式のコメント開始
// 改行文字が現れるまで読み飛ばす
if( !skipToNextLine(fp) ){
break;
}
}
else{
// コメントの始端でなかったら、読みすぎた1文字を返す
( c[1], fp );
ungetc
( c[0] );
putchar}
}
else {
( c[0] );
putchar}
}
}
if( fclose( fp ) == EOF ){
( "ファイルクローズに失敗しました。\n", stderr );
fputs( EXIT_FAILURE );
exit}
}
/*
次の1文字を受け取る。
引数
fp: 対象ファイルのポインタ
c: 結果を受け取るメモリアドレス
戻り値
正常に文字を受け取れたら true、終端に達したら false。
false が返された場合、引数c が指す先に変化は起こらない。
エラー発生時は、exit関数によってプログラムが終了する。
*/
bool getNextChar(FILE* fp, char* c)
{
( fp != NULL );
assert( c != NULL );
assert
int tmp = fgetc( fp );
if( tmp == EOF ){
if( feof(fp) ){
return false;
}
else if( ferror(fp) ){
( "読み込み中にエラーが発生しました。\n", stderr );
fputs( EXIT_FAILURE );
exit}
else{
// 有効な文字なので、そのまま続行
}
}
*c = (char)tmp;
return true;
}
/*
改行文字まで読み飛ばす
引数
fp: 対象ファイルのポインタ
戻り値
終端に達した場合は false、達しなかった場合は true
エラー発生時は、exit関数によってプログラムが終了する。
*/
bool skipToNextLine(FILE* fp)
{
( fp != NULL );
assert
char c;
for( ;; ){
if( !getNextChar(fp, &c) ){
return false;
}
if( c == '\n' ){
// 改行文字自体は出力しないといけないので、押し戻す
( c, fp );
ungetc
return true;
}
}
}
実行結果
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
bool getNextChar(FILE* fp, char* c);
bool skipToNextLine(FILE* fp);
int main(int argc, char* argv[])
{
if( argc < 2 ){
fputs( "コマンドライン引数が不足しています。\n", stderr );
exit( EXIT_FAILURE );
}
FILE* fp = fopen( argv[1], "r" );
if( fp == NULL ){
fprintf( stderr, "%s のオープンに失敗しました。\n", argv[1] );
exit( EXIT_FAILURE );
}
int inComment = 0;
char c[2];
for( ;; ){
if( !getNextChar(fp, &c[0]) ){
break;
}
if( inComment ){
if( c[0] == '*' ){
if( !getNextChar(fp, &c[1]) ){
putchar( c[0] );
break;
}
if( c[1] == '/' ){
inComment = 0;
}
else{
ungetc( c[1], fp );
putchar(c[0]);
}
}
else {
putchar(c[0]);
}
}
else{
if( c[0] == '/' ){
if( !getNextChar(fp, &c[1]) ){
putchar( c[0] );
break;
}
if( c[1] == '*' ){
inComment = 1;
}
else if( c[1] == '/' ){
if( !skipToNextLine(fp) ){
break;
}
}
else{
ungetc( c[1], fp );
putchar( c[0] );
}
}
else {
putchar( c[0] );
}
}
}
if( fclose( fp ) == EOF ){
fputs( "ファイルクローズに失敗しました。\n", stderr );
exit( EXIT_FAILURE );
}
}
次の1文字を受け取る。
引数
fp: 対象ファイルのポインタ
c: 結果を受け取るメモリアドレス
戻り値
正常に文字を受け取れたら true、終端に達したら false。
false が返された場合、引数c が指す先に変化は起こらない。
エラー発生時は、exit関数によってプログラムが終了する。
bool getNextChar(FILE* fp, char* c)
{
assert( fp != NULL );
assert( c != NULL );
int tmp = fgetc( fp );
if( tmp == EOF ){
if( feof(fp) ){
return false;
}
else if( ferror(fp) ){
fputs( "読み込み中にエラーが発生しました。\n", stderr );
exit( EXIT_FAILURE );
}
else{
// 有効な文字なので、そのまま続行
}
}
*c = (char)tmp;
return true;
}
改行文字まで読み飛ばす
引数
fp: 対象ファイルのポインタ
戻り値
終端に達した場合は false、達しなかった場合は true
エラー発生時は、exit関数によってプログラムが終了する。
bool skipToNextLine(FILE* fp)
{
assert( fp != NULL );
char c;
for( ;; ){
if( !getNextChar(fp, &c) ){
return false;
}
if( c == '\n' ){
ungetc( c, fp );
return true;
}
}
}
実行結果は、このプログラム自体を指定して実行したものです。
このサンプルプログラムでは、1文字ずつ読み取りながら、コメントの開始を探しています。コメント部分が開始したら、変数inComment に真となる値を代入し、その後はコメント終端を探す挙動に変化するように制御しています。
コメントの開始を表す /* にせよ、終端を表す */ にせよ、2文字読み取らないと判断できません。今回は、/* を探しているときには / が、*/ を探しているときには * が現れたときにだけ、もう1文字多く読み込みを行っています。2文字目を読み込んでみた結果、コメントの始端や終端であることが分かったら、前述した、変数inComment の値を変更することで、挙動の制御を行います。
2文字目を読み込んでみた結果、コメントの始端や終端でなかった場合、余分に読み取った2文字目の方を、ungetc関数を使って押し戻しています。
問題④ コマンドライン引数から、ファイルパスを2つ指定し、1つ目のファイルの内容を、2つ目のファイルへ追記するプログラムを作成してください。 たとえば、
.txt out.txt test in
としたとき、in.txt の内容を、out.txt の末尾へ追記します。
追記書き込みを行うには、fopen関数の第2引数に “a” を指定します。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
if( argc < 3 ){
( "コマンドライン引数が不足しています。\n", stderr );
fputs( EXIT_FAILURE );
exit}
FILE* fpIn = fopen( argv[1], "r" );
if( fpIn == NULL ){
( stderr, "%s のオープンに失敗しました。\n", argv[1] );
fprintf( EXIT_FAILURE );
exit}
FILE* fpOut = fopen( argv[2], "a" ); // 追記モードで開く
if( fpOut == NULL ){
( stderr, "%s のオープンに失敗しました。\n", argv[2] );
fprintf( EXIT_FAILURE );
exit}
for( ;; ){
int c = fgetc( fpIn );
if( c == EOF ){
if( feof(fpIn) ){
break;
}
else if( ferror(fpIn) ){
( "読み込み中にエラーが発生しました。\n", stderr );
fputs( EXIT_FAILURE );
exit}
else{
// 有効な文字なので、そのまま続行
}
}
if( fputc( c, fpOut ) == EOF ){
( "書き込み中にエラーが発生しました。\n", stderr );
fputs( EXIT_FAILURE );
exit}
}
if( fclose( fpIn ) == EOF ){
( "ファイルクローズに失敗しました。\n", stderr );
fputs( EXIT_FAILURE );
exit}
if( fclose( fpOut ) == EOF ){
( "ファイルクローズに失敗しました。\n", stderr );
fputs( EXIT_FAILURE );
exit}
}
入力ファイル (in.txt)
すせそ
たちつてと
出力ファイル (out.txt) 実行前
あいうえお
かきくけこ
さし
出力ファイル (out.txt) 実行後
あいうえお
かきくけこ
さしすせそ
たちつてと
return 0;
を削除(C言語編全体でのコードの統一)≪さらに古い更新履歴を展開する≫
Programming Place Plus のトップページへ