質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.10%

[C] fputcがうまく書き込みしてくれないです

解決済

回答 2

投稿

  • 評価
  • クリップ 0
  • VIEW 1,701

score 42

とても見辛く、メソッドも利用しないクソコードで申し訳ありません。
これは、"人の名前(空白)点数"というものが20人分記録されているファイルを点数順にソートして新しいファイルに記述するプログラムです。
これをコンパイルしたところうまくいくのですが、ファイルには何も書き込まれません。
指定したファイル名は何度も確認したのでそこのミスではないと思います。また、途中で選択ソートを行なっていますが、ここの実行はうまくいっています。

また、いろいろ試行錯誤したところ一番最後の二重for文、つまりfputcをするところをとても単純に

while ((ch = fgetc(sfp)) != EOF) {
    fputc(ch, dfp);   /* コピー先ファイルにコピー元ファイルの内容を書き込む*/
  }


に書き換えたところうまくいきましたので、おそらくエラーを吐いているのは最後のfor文のところだと思うのですが皆目見当つきません。

どうか回答の程よろしくお願い致します。

#include <stdio.h>
#define N 20

int main(int argc, char *argv[])
{



  int line[N][N];
  int score[N][2];
  int newScore[N];
  int ch;
  int k = 0,m = 0, tmp = 0, min = 0;
  int s = 0, i = 0, j = 0, t = 0;
  FILE *sfp, *dfp;

  // コマンドライン引数が2つ指定されているかどうか確認する 
  if (argc != 3) {
    printf("missing file argument\n");
    return 1;
  }

  if ((sfp = fopen(argv[1], "r")) == NULL) {   //コピー元ファイルのオープン 
    printf("can't open %s\n", argv[1]);
    return 1;
  }

  if ((dfp = fopen(argv[2], "w")) == NULL) {   // コピー先ファイルのオープン 
    printf("can't open %s\n", argv[2]);
    fclose(sfp);   //コピー元ファイルのクローズ
    return 1;
  }

  while ((ch = fgetc(sfp)) != EOF) {  //数字を見つけたらscoreに記録
      if(ch <= '9' && ch >= '0'){
          score[s][t] = ch;
          t++;
      }
      else if(ch == '\n'){  //改行を見つけたらscoreを改行
          s++;
          t = 0;
      }
      else if(ch >= 'a' && ch <= 'Z'){  //文字を見つけたらlineに記録
          line[i][j] = ch;
          j++;
      }
      else if(ch ==' '){  //空白を見つけたらlineを改行
          i++;
          j = 0;
      }
  }

  for(i = 0; i < sizeof(score) / sizeof(score[0]); i++){
      newScore[i] = score[i][0] *10 + (score[i][1]);  //scoreを10進法にしてnewScoreに記録

  }

 int length = 20;

     for(i = 0; i < length-1; i++){    //選択ソート
        min = newScore[i];
        k = i;
        for(j = i+1; j< length; j++){
            if(min > newScore[j]){
                min = newScore[j];
                k = j;
            }
        }
        tmp = score[i][0];    //newScoreとscoreをswap
        score[i][0] = score[k][0];
        score[k][0] = tmp;
        tmp = score[i][1];        
        score[i][1] = score[k][1];
        score[k][1] = tmp;
        tmp = newScore[i];
        newScore[i] = newScore[k];
        newScore[k] = tmp;
        for(m = 0; m < N; m++){    //同時に名前もswap
            tmp = line[i][m];
            line[i][m] = line[k][m];
            line[k][m] = tmp;
        }
    }

      for(i = 0; i < sizeof(line) / sizeof(line[0]) ;i++)    {
          for(j = 0; j < sizeof(line[0]) / sizeof(line[0][0]) ;j++){        //名前入力
            fputc(line[i][j], dfp);
        }
        fputc('\t', dfp);            //スペース
        fputc(score[i][0], dfp);        //スコア入力
           fputc(score[i][1], dfp);    
           fputc('\n', dfp);            //改行
    }

  fclose(dfp);   // コピー先ファイルのクローズ 
  fclose(sfp);   // コピー元ファイルのクローズ

  return 0;
}
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

checkベストアンサー

+2

お疲れ様です。3箇所なおしたら動きました。

まず、43行目の

else if(ch >= 'a' && ch <= 'Z'){  //文字を見つけたらlineに記録

は文字コードで小文字の'a' と大文字の'Z' の間の場合のみ成功するようになっていますが、小文字の'a' のコードのほうが大文字の'Z' のコードより大きいのでいかなる文字も読み込まれません。
小文字だけでよければ、

else if(ch >= 'a' && ch <= 'z'){  //文字を見つけたらlineに記録


とすることで読み込まれるようになります。大文字と小文字を両方扱いたければ、|| で条件を増やせば良いでしょう。

あと、line の配列の中は初期化されていないので、どのようなデータが入っているかは不定になりますが、86行目からの

for(j = 0; j < sizeof(line[0]) / sizeof(line[0][0]) ;j++){        //名前入力
  fputc(line[i][j], dfp);
}

では、すべてのデータを出力しており、入力時にデータを入れていない部分も出力してしまっています。
入力時に空白を見つけた時点でデータの終わりを示す 0 を入れておき、出力時は0を見つけたら止まるようにします。

47行目からの入力処理を以下のように変更

else if(ch ==' '){  //空白を見つけたらlineを改行
    line[i][j] = 0;
    i++;
    j = 0;
}

86行目からの出力処理を以下のように変更

for(j = 0; j < sizeof(line[0]) / sizeof(line[0][0]) ;j++){        //名前入力
    if (line[i][j] == 0) break;
    fputc(line[i][j], dfp);
}

これで動くようになると思いますが、このプログラムでは入力に不正なデータが入ってきた場合に対して弱すぎると思います。
通常は、状態を管理し、氏名入力中→スコア開始待ち→スコア入力中→終了というふうに状態遷移させ、
状態毎に不正な文字を弾くようにします。

今のプログラムだと

a2b 1

ab 21

と同じに扱われてしまいます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/10/24 21:45

    できました!
    ただ、僕の勘違いだったようで、(名前)(空白)(点数)ではなく、(名前)(タブ)(点数)でした。そのため
    if(ch ==' ')
    のところを
    if(ch == '\t')
    として実行してみたのですが
    Segmentation fault: 11
    のエラーを吐かれてしまいました。
    なぜでしょうか
    確保していないメモリ領域にアクセスした時に出るっぽいのですが\t自体はメモリ領域とか関係ないような気がします。。。

    キャンセル

  • 2016/10/24 23:52

    名前の文字数が20文字ちょうどのデータがあると、終わりの印の0がオーバーフローします。
    もちろん、20文字を超えていたり、区切り文字が間違っていてもオーバーフローします。
    間違った入力データでも割り込まないようにエラー処理を入れて行くべきです。名前の文字数や点数の文字数が想定を超えたらエラーにするような処理を入れることをおすすめします。

    キャンセル

0

mit0223さんのコメントに補足します。

「タブ文字にしたからエラーが出た」「空白文字だからエラーが出ない」
起きた内容は左であって、右ではありません。

テストデータを拝見していないので推測の域を出ませんが、空白文字のときはバグが「偶然」表面化しなかっただけで、バグがないわけではないと思います。
例えば「空白文字が含まれた長い名前の人」はいませんか?
もしいれば、区切りが空白文字だったからオーバーランしてないように見えただけかもしれません。

蛇足ですが、私が書くとこうなります。

#include <stdio.h>
#include <string.h>
#define MAXNUM 20
#define MAXLEN 31

int main(int argc, char *argv[])
{
  FILE *sfp, *dfp;

  char names[MAXNUM][MAXLEN+1];
  int score[MAXNUM];
  int n;

  int i, j, k, m, t;
  char buf[MAXLEN+1];
  char fmt[63];
  char del = '\t';

  if (argc < 3) {
    printf("missing file argument\n");
    return 1;
  }

  if ((sfp = fopen(argv[1], "r")) == NULL) {
    printf("can't open %s\n", argv[1]);
    return 1;
  }
  if ((dfp = fopen(argv[2], "w")) == NULL) {
    printf("can't open %s\n", argv[2]);
    fclose(sfp);
    return 1;
  }

  sprintf(fmt, "%%%d[^%c]%%*[^%c]%c%%d\n", MAXLEN, del, del, del);
  // prepare format string to truncate long names

  for (n = 0; n < MAXNUM; n++) {
    if (fscanf(sfp, fmt, names[n], &score[n]) < 2) break;
  }

  // sort
  for (i = 0; i < n-1; i++) {
    min = score[i];
    k = i;
    for (j = i+1; j < n; j++) {
      if (min > score[j]) {
        min = score[j];
        k = j;
      }
    }
    t = score[i];
    score[i] = score[k];
    score[k] = t;
    strcpy(buf, names[i]);
    strcpy(names[i], names[k]);
    strcpy(names[k], buf);
  }

  for(i = 0; i < n; i++){
    fprintf(dfp, "%s%c%d\n", names[i], del, score[i]);
  }

  fclose(dfp);
  fclose(sfp);
  return 0;
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.10%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る