- ベストアンサー
Visual C++を 用いたテキストファイル読み込み(応用)
Microsoft Visual C++ 2008 Express Editionを使っています。 テキストファイルは 約5000行×6列の数値(のみ)になっております。(列間にスペースあり) いくつかある5000×6行テキストファイルの中から、ファイル名を入力することで任意のテキストファイルにアクセスし、さらに6列のデータをそれぞれ別の配列に格納するコンソールプログラムを考えていますが行き詰まっています。例えば、1列目を配列1、2列目を配列2、・・・といった具合です。 詳しい方、どうかよろしくお願いいたします。
- みんなの回答 (14)
- 専門家の回答
質問者が選んだベストアンサー
>配列の1行ごとに意味ももちろんあるのですが、列の数値をまとめて >ひとつの配列に入れておき、後で数値として計算させたいという意図 >があります。つまり1列分5000個の成分をもつ配列が6つつくりたいで >す。 今までのやり取り流れをみると「文字列を文字列のまま取得」しておられるようですね。 おそらく、数値型と文字型の型の違いの理解があやふやなのだと推察します。 このままでは後で計算する際に「数値」に直す必要が発生します。 なので、「文字列を数値として取得」すれば「数値」に変換する必要がなくなります。 一応、取得部分のコードを以下に記載しますが、「数値型と文字型の型の違いの理解」をしっかりしておきましょう。 (エラー処理等は入っていません。) #include "stdafx.h" #define MAX_LINE_LEN 100 #define MAX_COL_NUM 6 #define MAX_ROW_NUM 5000 int _tmain(int argc, _TCHAR* argv[]) { FILE *fp; char szLineBuff[MAX_LINE_LEN]; int iLineCount = 0; double Data[MAX_ROW_NUM][MAX_COL_NUM]; fp = fopen("data.txt","r"); while ((fgets(szLineBuff, MAX_LINE_LEN - 1, fp)) != NULL) { sscanf(szLineBuff, "%lf %lf %lf %lf %lf %lf\r", &Data[iLineCount][0], &Data[iLineCount][1], &Data[iLineCount][2], &Data[iLineCount][3], &Data[iLineCount][4], &Data[iLineCount][5] ); iLineCount++; } fclose(fp); return 0; } ちなみに >0x5fffa189 でハンドルされていない例外が発生しました: > 0xC0000005: 場所 0xcccccccc に書き込み中にアクセス違反が発 >生しました。 は配列のインデックスが1から始まっているので、配列の最後で確保していない領域に書き込んでいるのではないでしょうか? 例:int iData[5]; と宣言されている場合、iData[5]に書き込んでいる
その他の回答 (13)
- goosyu
- ベストアンサー率58% (36/62)
・サンプル書いてみました。参考になれば幸いです。 #include <stdio.h> #include <malloc.h> #include <string.h> #define D_TABLE_LINE_MAX (5000) // 行数の最大値を定義 #define D_TABLE_ROW_MAX (6) // 列の数を定義 // テーブルのバイト数 #define D_TABLE_SIZE (sizeof(double) * D_TABLE_LINE_MAX * D_TABLE_ROW_MAX) int main(int argc, char *argv[]) { double (*table)[D_TABLE_ROW_MAX];// 2次元配列のポインタ char filePath[FILENAME_MAX + 1];// ファイル名を含むファイルパス(+1は文字列の終端文字考慮) FILE *fp; // ファイルポインタ int y; // 行ループのカウンタ table = malloc( D_TABLE_SIZE ); // サイズが大きいのでmalloc()関数でメモリ取得 memset( table, 0, D_TABLE_SIZE );// 念のためtableを0で埋める。 do { // ループ開始(ファイルオープンが成功するまで繰り返す。CTRL+Cで中断) printf( "ファイル名を入力:" ); scanf( " %s", filePath ); fp = fopen( filePath, "r" );// 読み込みとしてファイルをオープン,オープン結果が失敗の場合はfpにNULLが設定される } while ( NULL == fp ); // fopen()が失敗した場合,do~whileループを繰り返し,ファイル名を再入力させる // ループ,最大5000回(yが0~4999まで)繰り返す for ( y = 0; y < D_TABLE_LINE_MAX; y++) { if ( EOF == fscanf( fp, "%lf %lf %lf %lf %lf %lf", &table[y][0], &table[y][1], &table[y][2], &table[y][3], &table[y][4], &table[y][5] ) ) break; } fclose(fp); // ファイルをオープンした場合必ずクローズする。 // 配列確認用 { int yy; // 格納された配列の検証用の行ループカウンタ for ( yy = 0; yy < y; yy++ ) { printf( "table[%d][6]={%3.2f, %3.2f, %3.2f, %3.2f, %3.2f, %3.2f}\n", yy, table[yy][0], table[yy][1], table[yy][2], table[yy][3], table[yy][4], table[yy][5] ); } } free(table); // メモリの解放を行う return 0; }
- hiochi
- ベストアンサー率50% (1/2)
簡単にいうとstdafx.hはVisualStudio系でプロジェクトを作成すると概ね勝手に作成されるインクルードファイルです。 作成するプロジェクトの形式によって中身は変わります。 やり取り見る限りfopenもsprintfが使えているようですので、以前と同じインクルードファイルでいいはずです。(stdafx.hは無視して構いません。) ちなみにソースコードは貼り付けただけで動くことを考慮して記載していません。 (環境やらライブラリやら言語が確定できなかったため) ソースコードの内容を理解した上で必要な形に修正して組み込んでみてください。
補足
ありがとうございます。 まだ完成しておりませんが皆様のおかげでここまで進めることができました。 この場をお借りしてお礼申し上げます。
- nesnes
- ベストアンサー率33% (1/3)
こちらで独自に用意した、 5000行6列のTEXTファイルで実験してみましたが 特に問題なく動作しています。 一度、 ReadErrorのメッセージが表示されるときの iとjの値とbuf[i][j]の内容を表示するコードを付け加えて見てください。
お礼
fprintf(stderr,"FileReadError\n");の直下に、 printf("%d\n",i); printf("%d\n",j); を書き込んでiの値とjの値を見ましたが、i=5001,j=1でした。 ところが for(i=1;i<=6;i++){ printf("%c",buf[1][i]); } を用いると、左上の6桁の数字が取り出せ、 for(i=9;i<=16;i++){ printf("%c",buf[5000][i]); } とすれば5000行目の左から2列目の数字が読み取れていました。 このあといろいろな場所でやってみてデータが取れているか確認しなければいけませんが、一部は確実に配列にいれられているみたいでした。 読み取りエラーが表示される原因は解明できませんでした。 しかし少しずつ前進できています。本当にありがとうございます。
- nesnes
- ベストアンサー率33% (1/3)
すいませんでしたm(-_-)m 先日書き込んだコードに誤りがありました。 バグ原因は fclose(fp);が一つ目のfor文の中に入ってることなので、 それをfor文の外に書き直して下さい。 たぶんこれで、大丈夫だと思います。
お礼
ありがとうございます。早いレスポンスで本当に感謝しています。 if((fp=fopen(fname,"r")) != NULL){ for(i=1;i<=LINE;i++){ for(j=1;;j++){ if(buf[i][j-1] == '\n') break; if(fread(&buf[i][j],sizeof(char),1,fp) == 0){ fprintf(stderr,"FileReadError\n"); fclose(fp); return 1; } } } fclose(fp); } このようにfclose(fp);の場所を最初のif文の中に置換して作動させてみましたが、依然FileReadErrorとなってしまいます。 このテキストファイルを得るプログラムは、あるセンサー1のデータを列1(スペース換算の列1~6)に、 センサー2のデータを列2(スペース換算の列9~16)に・・・・ センサー6のデータを列6(スペース換算の列39~45) と時系列に置いているだけなため、列の末端部に改行コードが無いのかもしれないと着想しました。そのため、何列読み込んでも改行コードが見つからず、100列まで達してしまいループを抜け出せていないためのバグだと考えました。 こういった場合はどのように次の行へ、読み取りを移動させるのがベターなのでしょう?ちなみにスペースを含めて列数は合計45列(1文字1スペース換算)あります。まったくの見当外れの考察かもしれませんが、どうかよろしくお願いいたします。
- hiochi
- ベストアンサー率50% (1/2)
sscanfを使用してみてはいかがでしょう? 一行分読み込んでstrBuffに格納しているとして、視覚的に分かりやすく書くと・・・。 int col1[5000], col2[5000], col3[5000], col4[5000], col5[5000], col6[5000]; sscanf(strBuff, "%d %d %d %d %d %d", &col1[i], &col2[i], &col3[i], &col4[i], &col5[i], &col6[i]); でループでiをインクリメントという感じです。 一行ごとに意味があるなら、受け取る変数は構造体にして、その構造体の配列の方が扱いやすいと思います。 (メモリや速度に問題ないなら、動的配列にしておけば行が増えても修正しないいいようにつくれるのでちょっとうれしいかも) また、一行を改行まで取得するのならば、sscanfにも改行コードを追加する必要があります。(どんな種類のファイルか分からないので改行コードをあえて記載していません。)
お礼
アドバイスありがとうございます。返事が遅くなって申し訳ありませんでした。 配列の1行ごとに意味ももちろんあるのですが、列の数値をまとめてひとつの配列に入れておき、後で数値として計算させたいという意図があります。つまり1列分5000個の成分をもつ配列が6つつくりたいです。 現在は5000×100行列にとりあえず数値をbuf[5000][100]に読み込ませるところまでは達しています。そのため、for(i=1;i,<=6;i++){printf(buf[1][i]);}とすると123.45が出力できます。 123.45 678.90 123.45 345.67 890.12 345.67(配列の一行目) 111.11 ・ ・ ・ ・ ・ (配列の二行目) ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ・ ここで char *str[5000]; for(i=1;i<=5000;i++){ for(j=1;j<=6;j++){ sprintf(str[i],"%c",buf[i][j]); } } として5000個の成分を持つ配列に123.45 111.11 ・・・といれていきたいのですが、バグが発生してしまいます。コードの書き方がおかしいのでしょうか? バグ内容 0x5fffa189 でハンドルされていない例外が発生しました: 0xC0000005: 場所 0xcccccccc に書き込み中にアクセス違反が発生しました。 hiochiさんのおっしゃるsscanf関数とどちらがスマートであるかということが判断できません。このような状況の場合どう処理すれば、列ごとに6桁の数値を格納し、後に計算できるようにするにはどうすればよいのかということで専門家のhiochさんに質問させていただきました。 c++は初学者なため、とても苦戦しています。どうか解決の糸口をいただけたらと思います。どうかよろしくお願いいたします。
- nesnes
- ベストアンサー率33% (1/3)
1列~6列をくっつけて>sprintfを用いるのが簡単だと思います。 他にもstrcatなどを用いる方法もありますが、 これだとコードが無駄に長くなったり やや複雑になったりします。 ですから、ここではsprintfを用いた方法を紹介します。 例えば、 char *str; for(i=1;i<=6;i++){ sprintf(str,"%c",buf[1][i]); } これで、strに123.56という文字列が格納されます。 sprintfの詳しい機能は自分で調べて下さい。
お礼
ありがとうございます。 頑張ってプログラムに組み込んでうまくやりたいことができるかどうか頑張ってみます。 もしかしたら、またnesnesさんの助言をいただくことになるかもしれませんが、よろしくお願いいたします。
補足
nesnesさん FileReadErrorがでてしまいます。ファイルはオープンできているのですが、5001×100の行列に格納する作業がうまくできません。 if((fp=fopen(fname,"r")) != NULL){ for(i=1;i<=LINE;i++){ for(j=1;;j++){ if(buf[i][j-1] == '\n') break; if(fread(&buf[i][j],sizeof(char),1,fp) == 0){ fprintf(stderr,"FileReadError\n"); fclose(fp); return 1; } } fclose(fp); } } これはnesnesさんのコードのコピーですが、 if(buf[i][j-1] == '\n') break; がファイルの条件とあっていないのかもしれないです。改行コードによるループ抜け以外の方法はないでしょうか? 純粋にi,jのインクリメント終了によって読み込みが終わるといった都合のよいことはありえないのですか? 50001×100行列にしたあとはなんとかsprintf関数をうまく使う方針が見えているのであとひと踏ん張り頑張りたいのでよろしくお願いいたします。
- nesnes
- ベストアンサー率33% (1/3)
バイナリデータについて知識がないと 少々理解しがたいかもしれませんが、 まず123.45という数字は 1,2,3,4,5と少数点を含めた6文字で構成されています。 そしてこの123.45をバイナリデータ、 つまりアスキーコードの16進数に変換すると 31 32 33 2E 34 35 となります。 私が昨日書き込んだコードでは、 ファイルから一文字ずつ読み込んでそれを bufに格納するようになっております。 なので、bufの内容は buf[1][1] = 1 buf[1][2] = 2 buf[1][3] = 3 buf[1][4] = . buf[1][5] = 4 buf[1][6] = 5 という具合なります。 なお、 例として123.45を表示するコードは for(i=1;i<=6;i++){ printf("%c",buf[1][i]); } といった感じになります。 一応説明しておきますが、 printfの出力変換指定子が%dではなく%cなのは %dですと、アスキーコードの10進数で表示されてしまうからです。 アスキーコード表が載ってあるサイトと、 バイナリデータについて解説がしてあるサイトを紹介しておきます。 以上です。
- 参考URL:
- http://e-words.jp/p/r-ascii.html,http://www.geocities.co.jp/SiliconValley-SanJose/5780/data-07.html
お礼
毎度丁寧にありがとうございます。だいぶ理解が進みました。 このテキストファイルから123.45という数値を取り出して使用するには まだ処理が必要なんですね。 他言語のように(こっちが主流だが)1列~6列をくっつけて123.45を作るといった関数はあるのでしょうか?なければ今のところ自力では考え付かないのですが・・・ いつも早く的確な助言に大変助けられております。これが最後の質問になることを期待してよろしくお願いいたします。
- Tacosan
- ベストアンサー率23% (3656/15482)
char fname[256]; ではなく char fname[FILENAME_MAX]; とした方が安全かも>#3. 本当なら「パス長の最大値」が使いたいんだけど, ISO C にはないかもしれない. あと, #1 の 5~7 は fscanf を使った方が簡単でしょう. 「各行に数値が 6個ずつあり, かつ数値以外のものはない」という前提が崩れると破たんしますが, 手抜きプログラムでよければ scanf系を回避しなければならないってこともないだろう.
お礼
アドバイスありがとうございます。 現時点ではTacosanさんの指摘の素晴らしさが理解できませんので、 勉強後に理解した上でぜひ参考にさせていただきます。
- nesnes
- ベストアンサー率33% (1/3)
すいません さっきの回答に誤りがありましたm(-_-)m >値が5001になった時点で処理を行わずループを抜けますので オーバーフローは起こりません。 これは誤りでした。 確保してあるのは5000なので使用できる範囲は 0~4999なのに 5000に書き込んでしまうとオーバーフローを起こしますので 最低でも5001は確保して置いてください
- nesnes
- ベストアンサー率33% (1/3)
質問1>freadがファイルを読み込むのに失敗して 戻り値に0を返した時です。 freadの機能についてもう一度調べれば分かるともいます 質問2>ファイルの末端(EOF)を読み込んだら ループを抜けるようにすればいいと思います。 これにはfeof(FILE *fp)などを用いることで可能です feofの機能については自分で調べて下さい。 質問3>一番最初のデータはbuf[0][0]ではありません for(i=1;i<=5000;i++){ for(j=1;;j++){ if(buf[i][j-1] == '\n')break; fread(&buf[i][j],sizeof(char),1,fp); } } 昨日書き込んだコードの一部です。 まず一つ目のfor文で、 i=1で、i<=5000となっており そして二つ目のfor文では j=1になっていて、改行コードを読み込んだらループを抜けるようになってます。 ですから、 一つ目のループを抜けたときのiの値は5001になりますが、 値が5001になった時点で処理を行わずループを抜けますので オーバーフローは起こりません。 二つ目のループでは、 実際スペースなども一文字と数えられ正確な文字数が分かりませんので 列データは多めに確保しており、 改行コードを番兵としてループを抜けるようにしています。 結果最後のデータはbuf[5000][最後の列の文字数]となります。 質問4> その下にあるfreadがbuf[i][j]にファイル内容を格納しています 一応、 標準関数について簡単な解説がのってあるサイトを紹介しておきます 以上です。
お礼
詳しい解説ありがとうございます。 標準関数については参考ページを見てしっかり勉強します。 最後の質問なのですが・・・ テキストファイルの第一行目はだいたいこのようになっています。 123.45 678.90 123.45 345.67 890.12 345.67 以下もこのように続いていくのですが、一列目(buf[1][1])には123.45が格納されて、2列分の半角スペースがあいて、4列目の最初(buf[1][4])に678.90が格納されるという解釈でよろしいでしょうか? 長く付き合っていただいて感謝しています。
- 1
- 2
お礼
ご丁寧にありがとうございます。 新たな疑問なのですが、 "stdafx.h"をインクルードできませんというエラーが生じます。 プロジェクトは、コンソールアプリケーションを空のプロジェクトを追加して作っています。それは後にGLUTによる開発を行いたいからです。自身で勉強したところによると、構成プロパティの文字セットをマルチにすれば、 int _tmain(int argc, _TCHAR* argv[])この部分をint main(int argc, char* argv[])と書き直せるということ。また"stdafx.h"をインクルードしないとsscanfが使えないこと。空のプロジェクトからだとGlut開発がしやすい事がわかりました。すでにGlutのコードはできており、こっちと組み合わせる段階です。 私が目標としている形は int main(int argc, char *argv[]) { } このmainにテキストファイルの処理とGlutの処理をまとめたいです。"stdafx.h" をどうインクルードすればよいでしょうか?ネット上にも同じ疑問が多くありましたが、状況が少し違うかもしれないのでhiochiさんにお伺いしました。 よろしくお願いいたします。