わさっきhb

大学(教育研究)とか ,親馬鹿とか,和歌山とか,とか,とか.

入力のとり方:ハードコード,scanf,コマンドライン引数,ファイル読み出し

いきなりですが問題です.

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種類の入力のとり方について簡単に触れています.