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

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

新規登録して質問してみよう
ただいま回答率
85.50%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

2回答

3414閲覧

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

sakas1231

総合スコア42

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

0クリップ

投稿2016/10/24 09:39

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

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

C

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

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

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

C

1#include <stdio.h> 2#define N 20 3 4int main(int argc, char *argv[]) 5{ 6 7 8 9 int line[N][N]; 10 int score[N][2]; 11 int newScore[N]; 12 int ch; 13 int k = 0,m = 0, tmp = 0, min = 0; 14 int s = 0, i = 0, j = 0, t = 0; 15 FILE *sfp, *dfp; 16 17 // コマンドライン引数が2つ指定されているかどうか確認する 18 if (argc != 3) { 19 printf("missing file argument\n"); 20 return 1; 21 } 22 23 if ((sfp = fopen(argv[1], "r")) == NULL) { //コピー元ファイルのオープン 24 printf("can't open %s\n", argv[1]); 25 return 1; 26 } 27 28 if ((dfp = fopen(argv[2], "w")) == NULL) { // コピー先ファイルのオープン 29 printf("can't open %s\n", argv[2]); 30 fclose(sfp); //コピー元ファイルのクローズ 31 return 1; 32 } 33 34 while ((ch = fgetc(sfp)) != EOF) { //数字を見つけたらscoreに記録 35 if(ch <= '9' && ch >= '0'){ 36 score[s][t] = ch; 37 t++; 38 } 39 else if(ch == '\n'){ //改行を見つけたらscoreを改行 40 s++; 41 t = 0; 42 } 43 else if(ch >= 'a' && ch <= 'Z'){ //文字を見つけたらlineに記録 44 line[i][j] = ch; 45 j++; 46 } 47 else if(ch ==' '){ //空白を見つけたらlineを改行 48 i++; 49 j = 0; 50 } 51 } 52 53 for(i = 0; i < sizeof(score) / sizeof(score[0]); i++){ 54 newScore[i] = score[i][0] *10 + (score[i][1]); //scoreを10進法にしてnewScoreに記録 55 56 } 57 58 int length = 20; 59 60 for(i = 0; i < length-1; i++){ //選択ソート 61 min = newScore[i]; 62 k = i; 63 for(j = i+1; j< length; j++){ 64 if(min > newScore[j]){ 65 min = newScore[j]; 66 k = j; 67 } 68 } 69 tmp = score[i][0]; //newScoreとscoreをswap 70 score[i][0] = score[k][0]; 71 score[k][0] = tmp; 72 tmp = score[i][1]; 73 score[i][1] = score[k][1]; 74 score[k][1] = tmp; 75 tmp = newScore[i]; 76 newScore[i] = newScore[k]; 77 newScore[k] = tmp; 78 for(m = 0; m < N; m++){ //同時に名前もswap 79 tmp = line[i][m]; 80 line[i][m] = line[k][m]; 81 line[k][m] = tmp; 82 } 83 } 84 85 for(i = 0; i < sizeof(line) / sizeof(line[0]) ;i++) { 86 for(j = 0; j < sizeof(line[0]) / sizeof(line[0][0]) ;j++){ //名前入力 87 fputc(line[i][j], dfp); 88 } 89 fputc('\t', dfp); //スペース 90 fputc(score[i][0], dfp); //スコア入力 91 fputc(score[i][1], dfp); 92 fputc('\n', dfp); //改行 93 } 94 95 fclose(dfp); // コピー先ファイルのクローズ 96 fclose(sfp); // コピー元ファイルのクローズ 97 98 return 0; 99}

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

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

まず、43行目の

C

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

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

C

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

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

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

C

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

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

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

C

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

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

C

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

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

今のプログラムだと

a2b 1

ab 21

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

投稿2016/10/24 10:19

mit0223

総合スコア3401

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sakas1231

2016/10/24 12:45

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

2016/10/24 14:52

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

0

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

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

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

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

C

1#include <stdio.h> 2#include <string.h> 3#define MAXNUM 20 4#define MAXLEN 31 5 6int main(int argc, char *argv[]) 7{ 8 FILE *sfp, *dfp; 9 10 char names[MAXNUM][MAXLEN+1]; 11 int score[MAXNUM]; 12 int n; 13 14 int i, j, k, m, t; 15 char buf[MAXLEN+1]; 16 char fmt[63]; 17 char del = '\t'; 18 19 if (argc < 3) { 20 printf("missing file argument\n"); 21 return 1; 22 } 23 24 if ((sfp = fopen(argv[1], "r")) == NULL) { 25 printf("can't open %s\n", argv[1]); 26 return 1; 27 } 28 if ((dfp = fopen(argv[2], "w")) == NULL) { 29 printf("can't open %s\n", argv[2]); 30 fclose(sfp); 31 return 1; 32 } 33 34 sprintf(fmt, "%%%d[^%c]%%*[^%c]%c%%d\n", MAXLEN, del, del, del); 35 // prepare format string to truncate long names 36 37 for (n = 0; n < MAXNUM; n++) { 38 if (fscanf(sfp, fmt, names[n], &score[n]) < 2) break; 39 } 40 41 // sort 42 for (i = 0; i < n-1; i++) { 43 min = score[i]; 44 k = i; 45 for (j = i+1; j < n; j++) { 46 if (min > score[j]) { 47 min = score[j]; 48 k = j; 49 } 50 } 51 t = score[i]; 52 score[i] = score[k]; 53 score[k] = t; 54 strcpy(buf, names[i]); 55 strcpy(names[i], names[k]); 56 strcpy(names[k], buf); 57 } 58 59 for(i = 0; i < n; i++){ 60 fprintf(dfp, "%s%c%d\n", names[i], del, score[i]); 61 } 62 63 fclose(dfp); 64 fclose(sfp); 65 return 0; 66}

投稿2016/10/25 13:34

編集2016/10/26 03:54
majiponi

総合スコア1720

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問