- 締切済み
C言語 ファイル内のデータと入力したデータの重複
テキストファイルを読み込み、入力したデータとの重複がないかどうかを調べたいのですが、 わからない点があるため、質問させていただきます。 -------------------------------------------------------- #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { FILE *fp; char datafile[];= "sample.txt"; char buff[512]; //読み込んだ1行分のデータを格納 char *data[1000]; //読み込んだデータを格納 int data_c = 0; //データの数 char str[256]; //入力された文字列を格納 int i; int check; //重複チェック (中略) //ファイルを1行ずつ読み込み、その長さのメモリを確保し、値をコピー while(fgets(buff, sizeof buff, fp) != NULL) { data[data_c] = (char*)malloc(strlen(buff) + 1); strcpy(data[data_c++], buff); } (中略) //文字列を入力 fgets(str, 256, stdin); check = 0; //すでにあるデータと入力したデータの重複を調べる for(i=0; i<data_c; i++) { if(strcmp(data[i], str) == 0) { check = 1; break; } } (中略) -------------------------------------------------------- 例えば読み込むファイルに5行書かれていた場合、 data[0]からdata[4]に確保したメモリの先頭アドレスが格納されますよね? ということはdata_cの値は4となるのですが、 その後のファイルデータと入力したデータの重複を調べるところで、 for(i=0; i<data_c; i++) となっており、data[0]からdata[3]までの4行分しか調べられないことになります。 なぜ、i<=data_cではなく、i<data_cとなっているのか、わかりましたら教えていただけますでしょうか。
- みんなの回答 (7)
- 専門家の回答
みんなの回答
- Wr5
- ベストアンサー率53% (2173/4061)
>fgets(str, sizeof str, stdin); としたほうがバランスがいいのでしょうか。 >ちなみに、変数宣言のところでchar str[256];としているため、 >改めてfgets(str, 256, stdin); と読み込み文字数を256にする必要はないのでしょうか? char str[256]; では足りなかったから char str[384]; に拡張しよう! というのが、この先「絶対に」発生しないならいいんじゃないですか? # 私はdefine定義で対応することの方が多いですけど。(wcharとか使うコトほとんどないし) 256から384に拡張したからgrepで256探して書き換えればオッケー♪ とか思っていた場合は、今回の254が漏れて不可解な挙動を…ということに。 まぁ、そんなワケで…… いわゆる「マジックナンバー」の使用は控えた方がいい。 ということで。 # プログラミング続けていればマジックナンバーで余計な手間を経験することもあるでしょう。 # そういう、(ある意味)痛い目を見ないと理解できないかも知れませんね。
- Tacosan
- ベストアンサー率23% (3656/15482)
いや, while の条件では fgets(buff, sizeof buff, fp) としてるのに標準入力から読み込むときに fgets(str, 256, stdin); としてるのがバランス悪いなと思ったんだけど.... 「入力する文字数を254文字以内と制限しているため」だとしても, sizeof を使わない理由にはならないんじゃない? そして 2つの fgets で指定した長さが違うので, 「長い行」があると「同じもの」が見付けられないという不都合が生じてしまう.
補足
何度も回答いただきありがとうございます。 テキストファイルの1行は区分と内容という形になっており、 (例:hito 日本人) 区分は数字で指定し、内容をコマンドから入力するようにしています。 そのため、データ1行分を格納するbuff[512]と入力した内容を格納するstr[256]は長さが異なっています。 実際のソースコードでは、入力した区分と内容をセットにして別の変数に格納したものとbuffを比較して重複がないか調べていますが、strのままのほうがわかりやすいと思い、このように記述しました。 sizeof を使わない理由についてですが、本に載っているソースコードをほぼそのまま載せているため、前回の回答の通り「入力する文字数を254文字以内と制限しているため」とお答えするしかないのですが、 fgets(str, sizeof str, stdin); としたほうがバランスがいいのでしょうか。 ちなみに、変数宣言のところでchar str[256];としているため、 改めてfgets(str, 256, stdin); と読み込み文字数を256にする必要はないのでしょうか?
- maiko0318
- ベストアンサー率21% (1483/6969)
>data_c++ はdata_cに1ずつ足していく、 >++data_cはdata_cを足していく、ということでよろしいでしょうか。 違います。 data_c++ はdata_c を渡してから+1 +data_c は+1してから data_c を渡します。 data_cが2のとき、 data[data_c++] はdata[2]を、 data[++data_c] はdata[3]を渡します。渡したあと+1するのは同じです。
お礼
失礼いたしました。 回答NO.2の補足コメントを書き込んだ後で調べたところ、 間違っていることに気がつき、御礼コメントの欄で訂正させていただきました。 インクリメント演算子に前置きと後置きがあることを教えていただき、 大変勉強になりました。 何度も回答いただきましてありがとうございました。
- Tacosan
- ベストアンサー率23% (3656/15482)
あ, バグがいた. char datafile[];= "sample.txt"; はおかしい. まあタイポだろうけど. あとねんのため確認ですが, fgets が必ずしも 「1行読み込む」とは限らない というのは大丈夫でしょうか? ちなみにですが fgets(str, 256, stdin); のところ, sizeof を使わないのはなぜ?
補足
回答いただきありがとうございます。 char datafile[];= "sample.txt"; は、 char datafile[]= "sample.txt"; でした。失礼いたしました。 fgetsは、この場合入力された文字が255文字までをstr変数に格納するということですよね?(改行文字が現れたらその時点で読み込み終了) fgets(str, 256, stdin); のところで"sizeof"を使わないのは、上では記述していないんですが、入力する文字数を254文字以内と制限しているためだと思います。
- myuki1232
- ベストアンサー率57% (97/170)
動きがわかりにくい場合は、 strcpy(data[data_c++], buff); を strcpy(data[data_c], buff); data_c++; のように分解するのはとても良いです。 無暗に式の評価順に依存する書き方をしないようにしましょう。 データ数 N の配列について、値が入っているのは [0] ~ [N-1] で、for ループを回すときは (i = 0; i < N; i++) と書く、のは極めて一般的なイディオムなので覚えましょう。 > 配列0は使わずに、1から5にデータを入れて個数は5にしておく このような一般的でない書き方は、余程の理由がない限り絶対にやってはいけません。バグの温床になります。
お礼
回答いただきありがとうございます。 strcpy(data[data_c++], buff);のところで、 data_c++は2回目にループしたところでdata_cに1を加えると思ったため、 わけがわからず質問させていただいたところ、 strcpy(data[data_c], buff); data_c++; と分解できることを知り、大変勉強になりました。 配列の添え字にインクリメント演算子を使うのは分かりずらいですね。 配列につきましては、回答NO.2様の御礼コメントにかかせていただきましたが、配列は配列[0]からデータを入れていかなければならないと思っておりましたが、やはりそうしたほうがいいのですね。 この場合、読み込んだデータの配列の添え字にデータの個数を使用していたためわかりにくくなっていたのですが、上記のように2文に分解すると理解することができました。配列に関するご指摘も重ねてありがとうございました。
- maiko0318
- ベストアンサー率21% (1483/6969)
>strcpy(data[4], buff); となり、 data_c++; の行で 4+1=5 となるということでよろしいでしょうか。 そうです。 data_c++ と ++data_c が違うことも頭に置きましょう。 データが0から4に入り、個数が5となっている。これはパッと見、わかりにくいです。 ので、配列0は使わずに、1から5にデータを入れて個数は5にしておくのが良いかと思います。
お礼
すみません、上の補足コメントは間違っておりました。 data_c++は最後にインクリメント演算子の処理を行い、 ++data_cは最初にインクリメント演算子の処理を行うということだったのですね。 ++data_cは初めて見た形でしたので、教えていただき、勉強になりました。 配列に関しては、配列[0]からデータを入れていかなければならないと思っていましたが、この場合は配列[1]から入れていくほうがわかりやすいですね。 何度も解答いただきまして、ありがとうございました。
補足
何度も回答いただきありがとうございます。 >data_c++ と ++data_c が違うことも頭に置きましょう。 data_c++ はdata_cに1ずつ足していく、 ++data_cはdata_cを足していく、ということでよろしいでしょうか。
- maiko0318
- ベストアンサー率21% (1483/6969)
ややこしいソースですね。 data[data_c] = (char*)malloc(strlen(buff) + 1); data[0]にアドレスを格納 strcpy(data[data_c++], buff); data[0]にデータを格納したあと+1 ので、5行の場合は0から4にデータが入っててdata_cは5になっています。
補足
strcpy(data[data_c++], buff);の行は strcpy(data[data_c], buff); data_c++; と書き換えられ、5行目を読み込んだ時 strcpy(data[4], buff); となり、 data_c++; の行で 4+1=5 となるということでよろしいでしょうか。
お礼
何度も回答いただきありがとうございます。 fgets(str, 256, stdin); の256をマジックナンバーということを初めて知りました。意味のある定数を直接数字で記述することはよくないのですね。 define定義も初めて知りましたが、define定義をしておけば、strの大きさが変更になった際もdefineの数値を変更すればいいため、わかりやすいですね。 まだ勉強を始めたところで知らないことばかりでしたので、大変勉強になりました。ありがとうございました。