情報処理4レポート課題(4)の解答例

更新:2008年10月14日


  1. file_fgets1.cを改訂して, 次のように、 ASCIIのみで1行に1単語が書かれた入力テキストファイル内に対して, 特定の単語がある行を画面表示して下さい.
    1. /usr/share/dict/wordsから、単語Japanを探す。単語Japaneseなどに引っかかってはいけない。
    2. freqword-linux.txtから,単語printfを探す。単語fprintfなどに引っかかってはいけない。
    ただし,fgets関数1回で必ず1行読み込むことができると仮定して下さい. (ヒント:適切な文字列関数を用いて下さい。 また例えば、単語Japanを探す際に実際には、文字列"Japan"を探してはいけません。 文字列"Japan"に一致する行は/usr/share/dict/words内にありません。 文字列"Japan"を含む行を探そうとすると単語Japanを見付けることができますが、 単語Japanseも見付けてしまいます。 )
    report4-1.c
    #include<stdio.h>
    #include<stdlib.h>
    
    #define BUF_SIZE 1024
    
    int main(void){
    	 FILE *fp;
    	 const char *filename1="/usr/share/dict/words";
    	 const char *filename2="freqword-linux.txt";
    	char buf[BUF_SIZE];
    	char *ch;
    
    	fp=fopen(filename1, "r");
    	if(fp==NULL){
    		printf("File:%s can't open!", filename1);
    		exit(1);
    	}
    	while(1){
    		ch=fgets(buf, BUF_SIZE, fp);
    		if(ch==NULL){
    			break;
    		}
    		if(strcmp(buf, "Japan\n")==0){
    			/********↑"Japan"を探すのでなく、"Japan\n"を探す*****/
    			printf("%s", buf);
    		}
    	}
    	fclose(fp);
    
    	fp=fopen(filename2, "r");
    	if(fp==NULL){
    		printf("File:%s can't open!", filename2);
    		exit(1);
    	}
    	while(1){
    		ch=fgets(buf, BUF_SIZE, fp);
    		if(ch==NULL){
    			break;
    		}
    		if(strcmp(buf, "printf\n")==0){
    			/********↑"printf"を探すのでなく、"printf\n"を探す*****/
    			printf("%s", buf);
    		}
    	}
    	fclose(fp);
    	return 0;
    }
    
  2. 1.において、文字列関数を用いて単語を探す代用として 比較演算子==を用いることができない理由を説明してください。
    例えば次のプログラムを用いて単語を探そうとしてみます。
    report4-2.c
    #include<stdio.h>
    #include<stdlib.h>
    
    #define BUF_SIZE 1024
    
    int main(void){
    	 FILE *fp;
    	char *filename="Japan.txt";
    	char buf[BUF_SIZE];
    	char *target_string="Japan\n";
    	char *ch;
    
    	fp=fopen(filename, "r");
    	if(fp==NULL){
    		printf("File:%s can't open!", filename);
    		exit(1);
    	}
    	while(1){
    		ch=fgets(buf, BUF_SIZE, fp);
    		if(ch==NULL){
    			break;
    		}
    		if(buf==target_string){
    			printf("%s", buf);
    		}
    		/****
    		printf("target_string:%s target_string's address:%p\n", target_string, (void *)target_string);
    		printf("buf:%s buf's address:%p\n", buf, (void *)buf);
    		****/
    	}
    	fclose(fp);
    	return 0;
    }
    
    実行結果から、探せないことが分かります。 詳しく調べるために、プログラム中のコメントをオンにして実行すると、 検索元target_stringと検索先bufの正体が画面表示されます。 これらはchar型のアドレスを表し、その具体的な値はそれぞれ異なることが分かります。 それらのアドレスにはいずれも文字列"Japan\n"が格納されているという意味では 同じですが、比較演算子ではアドレスを比較するので一致を検出できません。
  3. BUF_SIZE文字以上ある行を読み込もうとした時点で、 エラーメッセージを表示してその場で(exit関数を用いて) 終了するようにしてください。 (ヒント:1行BUF_SIZE文字未満の行を読み込むと、 読み込んだ文字列中に、必ずある文字が含まれています。)
    report4-3.c
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    
    #define BUF_SIZE	25
    
    int main(void){
    	FILE *fp;
    	const char *filename="freqword-linux.txt";
    	char buf[BUF_SIZE];
    	char *ch;
    	int line=1;
    	char *check_whole_line;
           
    
    	fp=fopen(filename, "r");
    	if(fp==NULL){
    		printf("Can't Open File %s\n", filename);
    		exit(1);
    	}
    	while(1){
    		ch=fgets(buf, BUF_SIZE, fp);
    		if(ch==NULL){
    			break;
    		}
    		check_whole_line=strchr(buf, '\n');
    		if(check_whole_line==NULL){
    			printf("%d行目:1行読み込めていません。\n", line);
    			  exit(1);
    		}
    		line++;
    	}
    	fclose(fp);
    	return 0;
    }
    
以下は皆さんの幾つかの考察・感想に対するコメントです:
文字列関数なしでこのプログラムは作れるのでしょうか?
作れますが結局は文字列関数を自作するのと同じです。
strcmpの後のbufをchにしてあっても出来たが、なんでかは理解出来なかった。
fgets関数は、読み込みに成功すると戻り値が第一引数と一致するからです。
関数をifの中で突然呼び出せるとは思わなかったので関数の結果をいれたnと比較してし まった。
それでもかまいませんよ。
関数はポインタを用意しなければいけないかと思いきや、文字列をそのまま使用 できて驚きました。 最初に"Japan\n"をいれる*sを用意してしまったのでそのままにしま した。
それでもかまいませんよ。 なぜかについては、文字列がプログラム中で何を表すかについて復習しましょう。
あとbufの必要性が理解できなかったのでもう一度説明していただけませんか? chもポインタとして用意しているのだからbufはいらないのではとおもいました。
むしろ、chが何故必要かと疑問に思って欲しいです。 fgets関数を用いてファイルから読み込まれた文字列をプログラム中で保持するためには bufは必ず必要です。 正しく読み込まれている限りにおいてはbufさえあればchは不要です。 しかし、fgets関数がファイルの終端を検知するためにはその戻り値が必要になります。 どうせfgets関数には戻り値が必要なのだから、 正しく読み込まれているときに、bufと同じ値を返すことによって、例えば、
if(strcmp(fgets(buf, BUF_SIZE, fp), "Japan\n")==0){
のような短い記述ができるようになっています。
その上のch==NULLのところではchを使って,下ではbufを使ってこの二つの使い分けがわか りません。
通常、正しく読み込まれたか否かについてはchを用い、 正しく読み込まれた際の文字列についてはbufを使います。
ソースプログラムを合体させてしまったのですが、こういう提出は大丈夫ですか?
大丈夫です。
関数を使ってとのことだったので、授業時間内に教わった関数を使ったreturn0;の後にin t strcmp(const char *s1, const char *s2)のような文を付け加えなくていいのか疑問に思った。関数の内容みたい なものを普段は付け加えているような気がしたので先生の画面を見るまでは自分のプログ ラムに付け加えていたが、付け加えても付け加えなくても実行できたし結果はかわらなか ったので先生の画面の通り付け加えなかったがどうして付けなくてもパソコンが理解する のかわからなかった。
システムが用意している関数(ライブラリ関数という)を用いる場合には、 必要なヘッダファイルをincludeするだけで呼び出すことができます。 自作の関数を用いる場合には、宣言と定義が必要になります。
授業の時に話していたBUFの動作が今一つ分かりにくかった。具体例を挙げるとBUF_SIZE が6の時は、一回目の呼出で要素数が5だから5文字入る事は分かるのですが、BUF_SIZEが1 0の時は要素数が9にも関わらず\nの改行が入って来たりするのが分からなかった。
改行文字はfgets関数が自動的に挿入するのでなく、 もともとファイルにあったものですよ。
もし1行に単語1つでなく文章が書いてあって、その中からある単語を探すとなったらど うすればいいのかなぁと思った。
適当な文字列関数を用いることになると思います。
strcmpの様な関数は、人によっては覚えているものなのだろうか。
普段からC言語を、特に文字列を扱っている人は覚えていると思います。
なかなかできないとイライラしてしまいます。イライラしない方法はないものかと格闘し てます。
私もプログラミングがうまくいかなくて、よくイライラします。 イライラするのはしかたないと諦めています。
#define BUF_SIZE 10の数字を1にした時、実行結果が何も表示しなかった。また 0にした場合も『1013.c:9: 警告: ISO C は サイズ 0 の配列 `buf' を禁じます』 のようなエラーがでたのだが原因が分からなかった。
BUF_SIZEが1の場合にfgets関数は0文字読み込みます。 BUF_SIZEが0の場合にchar型配列bufの要素数を0にすることになってしまいます。
問題3において、BUF_SIZEの文字数を多く設定したままだとエラーが出てしまうことに気 づかず何回もやり直した。自分で調べたらBUF_SIZEが1または、30から128までの間は読み 込むが何も表示されず、2から29の場合は読み込んだ後に指定したエラーメッセージが表 示された。また129以降はプログラム上にエラーが出てしまう事が分かった。
変数iをcharで宣言していることが原因です。charは-128から127までの数しか扱えないので、 BUF_SIZEを129に設定するとBUF_SIZE-1、すなわち128とiが同じかどうかの判定は 常に否となるからです。
問題3について:isspace関数を使用。
...
if(isspace(*buf)!=0)
              {
                printf("EROR\n");
                exit(1);
              }
...
これでは、読み込んだ文字列の先頭文字が空白類文字の場合に検知することになってしまい、 題意とは異なってしまいます。


何かありましたら、 まで。