9日目 11日目

10日目

目標

課題

課題 10

キーボードからの入力

今までに学習した事に基づき、 起動時に与えられたファイルに含まれる文字の出現回数を数えるプログラムを書いてみる。 ただし、起動時にファイルが一つも与えられなければ、画面に質問を表示し、ユーザからの 指示をうけるものとする。

日本語のメッセージを出したいという希望があったので、今回から printf 等で 出力する文字列に日本語を含める事とした。 ただし、最初に確認したように、日本語のコード体系は複数あるため、 プログラミングEの環境では、 プログラムのソースが「ユニコード (Unicode, UTF-8)」で記述されている必要がある。 (逆に同じソースからコンパイルしたプログラムの出力が他の環境では読めない場合、 文字コードを適切に変換する必要がある。)

/* count.c */
/*
 * 起動時に与えられたファイルに含まれる文字を調べ、
 * それぞれの文字ごとに現れた数を表示する
 */

#include <stdio.h>
#include <strings.h>

/*
 * 実際に文字を解析する関数
 *   引数はファイル名とする
 *   引数が NULL の場合は標準入力を処理する
 */
int parse_file(char *);

int main(int argc, char *argv[])
{
        FILE *fp;
	char filename[80];
	int type;

        argc --; /* argc, argvには、実行ファイル名の情報も含まれているので
                  * 1 減らすことで、引数の数となる。
                  */
        switch (argc) {
        case 0:
		printf("どのような処理をしますか ? (番号で選択してください)\n");
		printf("1: 標準入力の処理\n");
		printf("2: ファイル名を指定する\n");
		scanf("%d", &type); /* 標準入力から整数を読み込む */
		if (type == 1) {
			parse_file(NULL);
		} else {
			printf("ファイル名 -> ");
			scanf("%s", filename); /* 標準入力から文字列を読み込む */
			parse_file(filename);
		}
                break;
        default:
                while (argc) {
                        argv++;
			parse_file(*argv);
                        argc--;
                }
        }
        return 0;
}


/*
 * ファイルに含まれる「文字」を調べ、
 * 種類ごとに出現回数を表示する
 */
int parse_file(char *name)
{
        int ch, i;
        int counter[256] = { 0 };  /* char 型は 0-255 の 256 通りの可能性がある */
        int total = 0;
	FILE *fp;

	if (name != NULL) {
		if(NULL == (fp = fopen(name, "r"))) {
			fprintf(stderr, "ファイル %s を開くことができません。\n", name);
			return -1;
		}
	} else {
		fp = stdin;
	}

        while(EOF != (ch = fgetc(fp)))
                counter[ch]++;   /* 該当する配列要素の中身をインクリメントする */
        fclose(fp);

        for(i = 0; i < 256; ++i) {
                if (counter[i] > 0) {
                        total += counter[i];
                        printf("%#04x: %5d\n", i, counter[i]);
                }

        }
        printf("\ntotal: %5d\n", total);
        return 0;
}

scanf関数

キーボードから整数を読み込んだり、ファイル名を読み込んだりするのに、新しく scanf関数を用いている。 scanf関数は、printf関数と同様な書式を用いて、 与えられた内容を変換して変数に格納する。scanf の書式は次の通りである。

  #include <stdio.h>

  int scanf(書式文字列, 格納先変数のポインタ, ...);

上の例では、最初の scanf は、入力を "%d"、すなわち10進数としての整数として 変換し、int 型の変数 type に格納している。 格納先は、アドレスで指定する必要があるため、通常変数である type に格納するためには "&"演算子を適用して変数のアドレスを求めている。 2番目のファイル名を入力している scanfでは、変換方法を "%s"、 すなわち文字列を、格納先には配列 filename を指定している。

scanfは入力が標準入力に固定されているが、FILE 型のポインタで示される ストリームから入力を行う関数として fscanfがある。fscanf は

  #include <stdio.h>

  int fscanf(ストリーム, 書式文字列, 格納先変数のポインタ, ...);

という形式で使用する。

scanf関数は、書式で指定された内容と異なる入力が与えられた場合、 変換を行わないなど期待にそぐわないの結果を与えたることもある上、 格納先の変数の大きさの確認を行わない(特に文字列の場合)ので、 使用には十分注意を払う必要がある。

count.cをコンパイル後、実行してみると たとえば、次のような出力を得るはずである。

[user99@proge1]~/kadai10% ./count 
どのような処理をしますか ? (番号で選択してください)
1: 標準入力の処理
2: ファイル名を指定する
2
ファイル名 -> count.c
0x09:    91
0x0a:    82
0x20:   136
0x21:     2
0x22:    20
...
(途中略)
...
0xe3:   167
0xe4:     2
0xe5:    41
0xe6:    29
0xe7:    10
0xe8:    15
0xe9:     7

total:  1902

さて、これで、文字の出現回数は数えられたが、 どの文字が一番多く出現したのかという事に着目したときに、 必ずしも判りやすいとは言えない。 しかし、

[user99@proge1]~/kadai10% ./count2 count.c
09:   91|***************************************
0a:   82|***********************************
20:  136|**********************************************************
21:    2|*
22:   20|*********
...
(略)
...
e3:  167|**********************************************************************
e4:    2|*
e5:   41|******************
e6:   29|*************
e7:   10|*****
e8:   15|*******
e9:    7|***

total:  1902

などと表示されると、視覚的に捉えやすい。 そこで、count.cをもとに簡単なグラフが出るようにプログラムを改造してみよ。

条件は次の通りとする。

プログラムするためのヒント

問題 10

count.ccount2.cという名前でコピーした後、 上のヒントにしたがってグラフ出力ができるように修正せよ。


9日目 表紙 11日目

tacha@tack.fukui-med.ac.jp
$Id: 10.html,v 1.1 2007/01/12 13:50:21 tacha Exp $