• 締切済み

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

みんなの回答

  • Wr5
  • ベストアンサー率53% (2177/4070)
回答No.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が漏れて不可解な挙動を…ということに。 まぁ、そんなワケで…… いわゆる「マジックナンバー」の使用は控えた方がいい。 ということで。 # プログラミング続けていればマジックナンバーで余計な手間を経験することもあるでしょう。 # そういう、(ある意味)痛い目を見ないと理解できないかも知れませんね。

jet888
質問者

お礼

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

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.6

いや, while の条件では fgets(buff, sizeof buff, fp) としてるのに標準入力から読み込むときに fgets(str, 256, stdin); としてるのがバランス悪いなと思ったんだけど.... 「入力する文字数を254文字以内と制限しているため」だとしても, sizeof を使わない理由にはならないんじゃない? そして 2つの fgets で指定した長さが違うので, 「長い行」があると「同じもの」が見付けられないという不都合が生じてしまう.

jet888
質問者

補足

何度も回答いただきありがとうございます。 テキストファイルの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/6970)
回答No.5

>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するのは同じです。

jet888
質問者

お礼

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

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.4

あ, バグがいた. char datafile[];= "sample.txt"; はおかしい. まあタイポだろうけど. あとねんのため確認ですが, fgets が必ずしも 「1行読み込む」とは限らない というのは大丈夫でしょうか? ちなみにですが fgets(str, 256, stdin); のところ, sizeof を使わないのはなぜ?

jet888
質問者

補足

回答いただきありがとうございます。 char datafile[];= "sample.txt"; は、 char datafile[]= "sample.txt"; でした。失礼いたしました。 fgetsは、この場合入力された文字が255文字までをstr変数に格納するということですよね?(改行文字が現れたらその時点で読み込み終了) fgets(str, 256, stdin); のところで"sizeof"を使わないのは、上では記述していないんですが、入力する文字数を254文字以内と制限しているためだと思います。

  • myuki1232
  • ベストアンサー率57% (97/170)
回答No.3

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

jet888
質問者

お礼

回答いただきありがとうございます。 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/6970)
回答No.2

>strcpy(data[4], buff); となり、 data_c++; の行で 4+1=5 となるということでよろしいでしょうか。 そうです。 data_c++ と ++data_c が違うことも頭に置きましょう。 データが0から4に入り、個数が5となっている。これはパッと見、わかりにくいです。 ので、配列0は使わずに、1から5にデータを入れて個数は5にしておくのが良いかと思います。

jet888
質問者

お礼

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

jet888
質問者

補足

何度も回答いただきありがとうございます。 >data_c++ と ++data_c が違うことも頭に置きましょう。 data_c++ はdata_cに1ずつ足していく、 ++data_cはdata_cを足していく、ということでよろしいでしょうか。

  • maiko0318
  • ベストアンサー率21% (1483/6970)
回答No.1

ややこしいソースですね。 data[data_c] = (char*)malloc(strlen(buff) + 1);  data[0]にアドレスを格納 strcpy(data[data_c++], buff);           data[0]にデータを格納したあと+1 ので、5行の場合は0から4にデータが入っててdata_cは5になっています。

jet888
質問者

補足

strcpy(data[data_c++], buff);の行は strcpy(data[data_c], buff); data_c++; と書き換えられ、5行目を読み込んだ時 strcpy(data[4], buff); となり、 data_c++; の行で 4+1=5 となるということでよろしいでしょうか。

関連するQ&A

  • C言語 文字列の操作

    文字Cが含まれる個数を求めたいです #include <stdio.h> int str_chnum(const char str[],int c) { int i; int count=0; for(i=0;str[i]!="\0";i++) if (str[i]==c) count++; return(count); } int main(void) { char st[100]; printf("検索文字列を入力してください:"); scanf("%s",st) ; printf("検索文字列数は%uです。\n",st,int str_chnum(const str[],int c)); return(0); } コンパイルできません。なぜですか?printfの行がたぶん間違っていると思うんですが。。。

  • C言語についてなのですが・・・

    さきほども上げたのですがカテゴリが間違っていたのでもう一回書き込みました まだプログラムの勉強をはじめた初心者なのですが、 テキストファイルから文字を読みこみ、大文字ならば小文字に変換し辞書順に並びかえるプログラムを作っているのですがどうしてもうまくいきません。 例えばtest.txtに XXX YYY YY XX BBB aaa aa BB とあれば aa aaa bb bbb xx xxx yy yyy と表示されるよにしたいんです。 自分が作ったプログラむはこれです。 まだテキストファイルからでなくキーボードからの入力になっていますが・・・ #include<stdio.h> #include<stdlib.h> #include<string.h> #include <ctype.h> int soto( const void *x, const void *y); int main(int argc, char *argv[]){ FILE *input; char str1[1000]; int i, j; for (i = 1; i < argc; i++){ qsort(argv[i], 1000, sizeof( char *), soto); strcpy(str1, argv[i]); for(j = 0; j < 100; j++){ str1[j] = tolower( str1[j] ); } printf("%s\n", str1); } return 0; } int soto( const void *a, const void *b){ char *x, *y; x = (char*)a; y = (char*)b; return x-y; } これだと小文字にはなるんですがソートされずに表示されてしまいます・・・ どのようにすればいけるのかご指摘のほどおねがいします

  • C言語についてアドバイスをください。

    CSVファイルの内容をfreadで読み込み、strtokを使わずにbuffに格納した後、 buffから1文字ずつbuff2へコピーさせていって、コンマがきたら数字、 改行がきたら名前と判別して、自作関数に渡して表示させたいです。 CSVファイルの内容は 『11,名前1(改行) 15,名前2(改行) 18,名前3』 といった感じです。 ------------------------------------------------------- #define _CRT_SECURE_NO_DEPRECATE 1 #include <stdio.h> #include <string.h> #include <stdlib.h> #define NUM 256 struct kou { short nenrei; char namae[30]; }; void pri(struct kou *o) { printf("%d\n%s\n",o->nenrei,o->namae); } int main(void) { FILE *fp; // ファイルポインタ char buff1[NUM] = {0}; char buff2[NUM] = {0}; char *fname = "text1.csv"; // ファイル名を指定 short i = 0; int n = 0; struct kou p; fp = fopen(fname, "r"); if(fp == NULL) { printf("%sファイルをオープンできませんでした。\n",fname); } fread(buff, 1, NUM, fp); while(buff1[n] != NULL) { buff2[n] = buff1[n]; // buff2[0]からbuff1の中を一文字ずつコピーしていく。 if(buff2[n] == ',') // buff2に格納されていく中にコンマがきたら以下の作業を行う。 { i = (short)atoi(buff2); // char型からshort型への変換 p.nenrei = i; } if(buff2[n] == '\n') { strcpy(p.namae,buff2); // p.namaeにbuff2をコピー。 pri(&p); } n++; } fclose(fp); printf("ファイルをクローズしました。\n"); return 0; } ------------------------------------------------------- 今のままだと 『11 11,名前1 11 11,名前1 15,名前2』 という表示になってしまいます。 while 内で既に読み込んだ部分を読み込ませないよう(表示させないよう)にできたら良いと思うんですが、そういったやり方はあるのでしょうか? むしろやり方を変えたほうが良いでしょうか・・・。 まだC言語を学び始めて日が浅いので、色々間違っている部分もあると思いますが、 そういったことを含めてアドバイスをいただけたらと思います。 よろしくお願いします。

  • C言語でファイルの内容を strtok関数 を使って数字と文字を分けて

    C言語でファイルの内容を strtok関数 を使って数字と文字を分けて配列に格納したいのですが、うまくできません。 どこが駄目なのかご指摘をお願いします! ファイル内容 20 田中 10 鈴木 #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc,char *argv[]) { FILE *fp; char str[256]; char *tp; int i=0; int num[10]; char na[10]; fp=fopen(argv[1],"r"); while(fgets(str,sizeof str,fp)!=NULL); tp = strtok ( str, " " ); while(tp != NULL ) { num[i]=atoi(tp); tp = strtok( NULL," "); if ( tp != NULL ){ na[i]=*tp; } i++; } printf("%d\n%s",num[0],na[0]); printf("%d\n%s",num[1],na[1]); fclose(fp); return 0; }

  • C言語、ファイル操作、fgets()について

    次のプログラムは入力された行を読み込み、コマンドラインで指定されたファイルに書き込みます。 空白行が入力されたら、入力の終了とみなしてファイルを閉じます。続いてファイルを入力用に開き、 fgets()を使ってファイルの内容を表示するものです。 (ソースコードが長くてすみません) #include <stdio.h> #include <stdlib.h> #include <string.h> int main(int argc, char *argv[]) { FILE *fp; char str[80]; /* コマンドライン引数を検査する */ if(argc!=2) { printf("ファイル名を指定してください\n"); exit(1); } /* 出力用にファイルを開く */ if((fp = fopen(argv[1], "w"))==NULL) { printf("ファイルを開くことができません\n"); exit(1); } printf("終了するには空白行を入力してください\n"); do { printf(": "); gets(str); strcat(str, "\n"); /* 改行を追加する */ if(*str != '\n') fputs(str, fp); } while(*str != '\n'); fclose(fp); /* 入力用にファイルを開く */ if((fp = fopen(argv[1], "r"))==NULL) { printf("ファイルを開くことができません\n"); exit(1); } /* ファイルを読み込み直す */ do { fgets(str, 79, fp); if(!feof(fp)) printf(str); } while(!feof(fp)); fclose(fp); return 0; } 【質問】fgets()内のint型の数値「79」がどうして79なのかが分かりません。     80でも良いような気がするのですが・・・

  • C言語で、ファイルを読み込んで数字と名前に分けて配列に格納に関する質問

    C言語で、ファイルを読み込んで数字と名前に分けて配列に格納に関する質問です! ファイルを開いた後でエラーとなるのですが、何が足りないのでしょうか? ファイル内容 20 田中 10 鈴木 #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc,char *argv[]) { FILE *fp; char str[256]; char *tp; int k,i=0; int num[10]; char na[10][20]; fp=fopen(argv[1],"r"); if(fp==NULL){ printf("ファイルを開けません\n"); return 1; }else{ printf("開けた\n"); } while(fgets(str,sizeof str,fp)!=NULL){ tp=strtok(str," "); num[i]=atoi(tp); tp=strtok(NULL," "); strcpy(na[i],tp); i++; } printf("%d\n%s\n",num[0],na[0]); printf("%d\n%s\n",num[1],na[1]); fclose(fp); return 0; }

  • C言語 初心者です。

    今、英単語帳を作っているのですが、以下のソースではできません。 作ろうとしているプログラムは、a bを登録した場合、次がaabと来たら、 a aab bといったようにしたいのですが、できません。教えてください。 #include <stdio.h> #include <string.h> #define NUMBER 50 /*--- 単語帳の構造体*/ typedef struct { char *word; } words; /*--- 文字列strから文字列wordを検索する ---*/ char *str_chr(const char *str, int w) { for ( ; *str; *str++){ if (*str == w){ return ((char *)str); } } return (NULL); /*検索したが該当しないときはNULLを返す*/ } /*--- 単純交換ソート ---*/ void swap(int *x, int *y) { int temp = *x; *x = *y; *y = temp; } /*--- 配列dataの先頭n個の要素を昇順にソート ---*/ void sort(words data[], int n) { int k = n - 1; while (k >= 0){ int i, j; for (i = 1, j = -1; i <= k; i++) if (data[i - 1].word > data[i].word){ j = i - 1; swap(&data[i], &data[j]); } k = j; } } int main(void) { words word[NUMBER][20] = {{0},{0}}; char str[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; char w[128], *p; int count = 0; do{ printf("単語を入力してください。:"); /*単語を入力する*/ scanf("%s", w); p = str_chr(str, w); }while(p == NULL); count++; if(count >= NUMBER){ /*登録件数を調べる*/ printf("件数いっぱいです。\n"); } return (0); sort(word, NUMBER); return (0); }

  • C言語 複数ファイル操作について

    Cプログラミング初心者です。 論文などの何行も文章があるようなテキストファイル(ここでは1.txtとします)と、他に予め単語をいくつか登録しているテキストファイル(2.txt)を開き、1.txtを最初の行から一行ごとに読み込み、2.txtの中にある単語が1つでもその一行の文章中に含まれていたらその一行の文章を出力し、また次の行においても2.txtの中にある単語のいずれかが含まれているかどうかを調べて含まれている場合は出力…含まれていない場合は出力せずに次の行へ…といったようにこれを1.txt内の最後の行まで繰り返し行うプログラムを作りたいのですが、自分が作ったプログラムでは含む・含まない関係なく1.txt内の文章全てが出力されてしまいます。おそらく最初のwhile文あたりがおかしいのだろうという予想はつくのですがどのように直せばよいのかわからず悩んでいます。どなたか教えていただければ嬉しいです(;_:) #include <stdio.h> #include <stdlib.h> #include <string.h> #define N 1056 void delkaigyo(char *s1,char *s2){ char *p = s1; p=strstr(s1,s2); if(p!=NULL){ strcpy(p,p+strlen(s2)); delkaigyo(p+1,s2); } } int main(void){ FILE *fp; char *filename = "2.txt"; char str1[N]; char str2[N]; char kaigyo[] = "\n"; int i; int a=0; char fname[64]; printf("file:"); scanf("%s", fname);   ←ここで1.txtを入力するとします fp = fopen(fname, "r"); while(fgets(str1, N, fp) != NULL){ delkaigyo(str1, kaigyo); memset(str1, 0, N); fread(str1, 1, N-1, fp); if((fp = fopen(filename, "r")) == NULL){ fprintf(stderr, "%serror.\n", filename); exit(EXIT_FAILURE); } while(fgets(str2, N, fp) != NULL){ delkaigyo(str2,kaigyo); if(strstr(str1,str2)!=NULL){ a = 1; printf("%s\n", str1); break; } } if(a==0){ return 0; } fclose(fp); } return EXIT_SUCCESS; }

  • C言語の課題に取り組んでいるんですが・・・

    まだプログラムの勉強をはじめた初心者なのですが、 テキストファイルから文字を読みこみ、大文字ならば小文字に変換し辞書順に並びかえるプログラムを作っているのですがどうしてもうまくいきません。 例えばtest.txtに XXX YYY YY XX BBB aaa aa BB とあれば aa aaa bb bbb xx xxx yy yyy と表示されるよにしたいんです。 自分が作ったプログラむはこれです。 まだテキストファイルからでなくキーボードからの入力になっていますが・・・ #include<stdio.h> #include<stdlib.h> #include<string.h> #include <ctype.h> int soto( const void *x, const void *y); int main(int argc, char *argv[]){ FILE *input; char str1[1000]; int i, j; for (i = 1; i < argc; i++){ qsort(argv[i], 1000, sizeof( char *), soto); strcpy(str1, argv[i]); for(j = 0; j < 100; j++){ str1[j] = tolower( str1[j] ); } printf("%s\n", str1); } return 0; } int soto( const void *a, const void *b){ char *x, *y; x = (char*)a; y = (char*)b; return x-y; } これだと小文字にはなるんですがソートされずに表示されてしまいます・・・ どのようにすればいけるのかご指摘のほどおねがいします

  • ファイル入力方法(C言語)

    C言語のファイル入力について教えてください。 入力ファイルinput.txtが以下である時、 *********** abcdefg *********** 2 3 5 6 3 8 1 7 ●最初の3行を無視する ●4行目以降の数値を配列に格納し、表示する (4行目以降は2列で固定、行数は最大100列) 以上の条件でプログラムを作成したいのです。 最初の3行がない入力ファイルinput2.txt 2 3 5 6 3 8 1 7 を入力し、表示するソースコードは以下のように作成できましたが、「最初の3行を無視する」方法が分かりません。 お手数ですが、ご存じの方教えてください。 よろしくお願いいたします。 #include <stdio.h> #include <stdlib.h> int main() { int i,N,a[100],b[100]; FILE *fpi; if((fpi=fopen("input2.txt","r"))==NULL) { fprintf(stderr,"ファイルを開けません\n"); exit(1); } for (N=0; N<100 && fscanf(fpi,"%d %d",&a[N],&b[N]) != EOF; N++); printf("データ行数:%d\n\n",N); for(i=0;i<N;i++) { printf("%d %d\n",a[i],b[i]); } fclose(fpi); return 0; }

専門家に質問してみよう