いきなりですが問題です.
2つの整数を入力にとり,その和を計算して出力するプログラムを,Cで書きなさい.
さっそくですが解答の前に方針です.「入力のとり方」には,Cの基本的な内容を学習する段階でも,次の4つが考えられます.
- ソースファイルに,入力の値を書く
- scanf関数を使って,標準入力から獲得する
- コマンドライン引数から獲得する
- ファイルを読み出して獲得する
それぞれプログラムと実行結果,メリットとデメリット,注意点などを示していきます.
ソースファイルに,入力の値を書く
値をソースファイルに直接,記述するのは「ハードコード(hard code)」と呼ばれますので,ファイル名を,add_hardcode.cとします.
/* add_hardcode.c */ #include <stdio.h> int main(void) { int a = 5; int b = 3; int sum; sum = a + b; printf("%d\n", sum); return 0; }
変数sumについて,宣言と,和の計算を分けていますが,「int sum = a + b;」に置き換えても,かまいません.上のように書いたのは,和の計算を明確にしたかったのと,他のコードに合わせたかったからです.
実行結果は以下のとおりです.
$ cc -o add_hardcode add_hardcode.c $ ./add_hardcode 8
このプログラムでは,入力の値を変更したら,そのつどコンパイルする必要がある点を,デメリットとして挙げないといけません.加えて,これを「入力」とすることに,抵抗を感じる人も多いかもしれません.
その一方で,プログラムで使用可能な任意のデータ構造(配列でも,構造体でもかまいません)を入力として与えられることは,強みと言っていいでしょう.
「入力込み」のプログラムを用意し,実行してもらうといった形で,他の人による検証にも,役立ちます.
scanf関数を使って,標準入力から獲得する
ファイル名は,add_scanf.cにしましょう.
#include <stdio.h> int main(void) { int a, b, sum; scanf("%d %d", &a, &b); sum = a + b; printf("%d\n", sum); return 0; }
実行結果は以下のとおりです.「5 3」も,実行時に打ち込んでいます.
$ cc -o add_scanf add_scanf.c $ ./add_scanf 5 3 8
「C」で「入力をとる」というと,このscanfを思い浮かぶ人が最も多いのではないでしょうか.たしかに手軽ですし,入力として扱いたい値が,整数でも実数(浮動小数点数)でも,文字列でも,対応してくれるわけです.
「変数名の前に&をつける」のは,はじめはそういうものとして,のちにはポインタと対応づけながら,理解を深めていくのがいいと思います.
よく知られた注意点として,値の獲得に失敗すると,入力が「進まない」という点が挙げられます.%dなのに,標準入力では数字でない文字がある,なんてことが起こってはいけません.forやwhileなどの反復と,組み合わせて処理する際には,特に注意したいものです.
それと,実行するたびに,入力を打ち込むのは面倒ですね.上のコマンドについては,パイプを使って以下のように実行すれば,手間が省けます.
$ echo 5 3 | ./add_scanf
入力がもっと複雑になれば,ファイルに保存しておき,「< ファイル名」によるリダイレクションを活用するといいでしょう.
コマンドライン引数から獲得する
ファイル名は,add_commandline.cにしましょう.
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int a, b, sum; if (argc < 3) return 1; a = atoi(argv[1]); b = atoi(argv[2]); sum = a + b; printf("%d\n", sum); return 0; }
実行結果は以下のとおりです.
$ cc -o add_commandline add_commandline.c $ ./add_commandline 5 3 8
main関数の引数が,他の3つのプログラムを違っています.argcは実行コマンド名を含めた引数の数で,「./add_commandline 5 3」と実行したのなら,3が格納されます.argvは,コマンドライン引数に対応する,「文字列の配列」を指すポインタで,宣言の仕方はやや複雑に見えますが,char **型となります.
なので,この方法でプログラムを書く際には,文字列(がメモリにどのように格納されているか),そしてポインタのポインタについての知識が必要です.
また正味の入力は,文字列です(scanfとファイル読み出しは,ストリームであり,たとえば,ナル文字を考慮する必要がありません).なので「5」という文字列を,数値の5に変換する処理も,必要となりました.変換処理は,自作もできますが,バグが入りやすいので,標準ライブラリ関数のatoiを使用しました.その場合,「#include <stdlib.h>」を書いておく必要もあります.
引数の数が,実行に応じてさまざまに変わり得るような場合に,お勧めの書き方と言えます.
ファイルを読み出して獲得する
ファイル名は,add_file.cにしましょう.
#include <stdio.h> int main(void) { int a, b, sum; FILE *fp; if ((fp = fopen("add.txt", "r")) == NULL) return 1; fscanf(fp, "%d %d", &a, &b); fclose(fp); sum = a + b; printf("%d\n", sum); return 0; }
実行する前に,読み出すファイルを作っておきます.以下の内容で,ファイル名はadd.txtとします.
5 3
実行結果は以下のとおりです.
$ cc -o add_file add_file.c $ ./add_file 8
ファイルを開いて,読み出して,閉じるという処理が必要になり,それに先立って,ファイルポインタの変数も,宣言しておかないといけません.ですので,scanfよりも,コード量が多くなってしまいます.
また上のコードでは,scanfのところで指摘した,「値の獲得に失敗すると,入力が進まない」が当てはまります.これについては,fgets関数などで1行分を読み出してから,sscanf関数で値を獲得すれば,回避できます.
入力のファイル名がadd.txtと固定なのも,実用的ではありませんね.たとえばコマンドライン引数でファイル名を指定してもらい(その指定がない場合には,デフォルトのファイル名を"add.txt"として),開いて読み出すことにすれば,処理がより柔軟になります.
元ネタ
2003年度(2013年度ではなく)の授業が最初です.あのころは「ハードコーティング」と書いてあったりしました.
当ブログでは,以下の記事で,4種類の入力のとり方について簡単に触れています.