アプリ版:「スタンプのみでお礼する」機能のリリースについて

テキストファイルを読み込み、入力したデータとの重複がないかどうかを調べたいのですが、
わからない点があるため、質問させていただきます。

--------------------------------------------------------
#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となっているのか、わかりましたら教えていただけますでしょうか。

A 回答 (7件)

>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が漏れて不可解な挙動を…ということに。


まぁ、そんなワケで……
いわゆる「マジックナンバー」の使用は控えた方がいい。
ということで。
# プログラミング続けていればマジックナンバーで余計な手間を経験することもあるでしょう。
# そういう、(ある意味)痛い目を見ないと理解できないかも知れませんね。
    • good
    • 0
この回答へのお礼

何度も回答いただきありがとうございます。

fgets(str, 256, stdin); の256をマジックナンバーということを初めて知りました。意味のある定数を直接数字で記述することはよくないのですね。
define定義も初めて知りましたが、define定義をしておけば、strの大きさが変更になった際もdefineの数値を変更すればいいため、わかりやすいですね。
まだ勉強を始めたところで知らないことばかりでしたので、大変勉強になりました。ありがとうございました。

お礼日時:2014/11/25 12:13

いや, 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にする必要はないのでしょうか?

補足日時:2014/11/25 01:24
    • good
    • 0

>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するのは同じです。
    • good
    • 0
この回答へのお礼

失礼いたしました。
回答NO.2の補足コメントを書き込んだ後で調べたところ、
間違っていることに気がつき、御礼コメントの欄で訂正させていただきました。
インクリメント演算子に前置きと後置きがあることを教えていただき、
大変勉強になりました。
何度も回答いただきましてありがとうございました。

お礼日時:2014/11/24 01:57

あ, バグがいた.


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文字以内と制限しているためだと思います。

補足日時:2014/11/24 01:03
    • good
    • 0

動きがわかりにくい場合は、


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にしておく
このような一般的でない書き方は、余程の理由がない限り絶対にやってはいけません。バグの温床になります。
    • good
    • 0
この回答へのお礼

回答いただきありがとうございます。

strcpy(data[data_c++], buff);のところで、
data_c++は2回目にループしたところでdata_cに1を加えると思ったため、
わけがわからず質問させていただいたところ、
strcpy(data[data_c], buff);
data_c++;
と分解できることを知り、大変勉強になりました。
配列の添え字にインクリメント演算子を使うのは分かりずらいですね。

配列につきましては、回答NO.2様の御礼コメントにかかせていただきましたが、配列は配列[0]からデータを入れていかなければならないと思っておりましたが、やはりそうしたほうがいいのですね。
この場合、読み込んだデータの配列の添え字にデータの個数を使用していたためわかりにくくなっていたのですが、上記のように2文に分解すると理解することができました。配列に関するご指摘も重ねてありがとうございました。

お礼日時:2014/11/24 01:34

>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++ はdata_cに1ずつ足していく、
++data_cはdata_cを足していく、ということでよろしいでしょうか。

補足日時:2014/11/24 00:20
    • good
    • 0
この回答へのお礼

すみません、上の補足コメントは間違っておりました。
data_c++は最後にインクリメント演算子の処理を行い、
++data_cは最初にインクリメント演算子の処理を行うということだったのですね。
++data_cは初めて見た形でしたので、教えていただき、勉強になりました。
配列に関しては、配列[0]からデータを入れていかなければならないと思っていましたが、この場合は配列[1]から入れていくほうがわかりやすいですね。
何度も解答いただきまして、ありがとうございました。

お礼日時:2014/11/24 00:51

ややこしいソースですね。



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 となるということでよろしいでしょうか。

補足日時:2014/11/23 20:12
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!