- 締切済み
csvファイルを構造体に格納したいです
ファイル内容 ******************** あいう,,さしす たちつ,なにぬ,はひふ まみむ,, あいう,win, ******************** #include <stdio.h> #include <string.h> #define MBF 256 struct tb{ char aaa[32]; char bbb[32]; char ccc[32]; }; int main(){ struct tb tbl[20]; struct tb *tp; int ntb,itb; FILE* fi; FILE* fo; char buff[MBF]; // 入力 fi = fopen("sample.csv","r"); // 検査省略 if( fi == NULL ){ printf( "%sファイルが開けません\n" ); return -1; } ntb = 0; while ( fgets(buff,MBF,fi ) != NULL ) { strcpy(tbl[ntb].aaa,strtok(buff,",")); strcpy(tbl[ntb].bbb,strtok(NULL,",")); strcpy(tbl[ntb].ccc,strtok(NULL,",")); ntb++; } fclose( fi ); // 出力 fo = fopen("csvo.csv","w"); if( fo == NULL ){ printf( "%sファイルが開けません\n" ); return -1; } for ( itb=0;itb<ntb;itb++ ) { tp = tbl+itb; fprintf(fo,"%s%s%s",tp->aaa,tp->bbb,tp->ccc); } fclose( fo ); return 0; } csvファイルないようが以下であれば格納できるけど、すごく困ってます。 ******************** あいう,かきく,さしす たちつ,なにぬ,はひふ まみむ,やゆよ,らりる ********************
- win09
- お礼率28% (4/14)
- C・C++・C#
- 回答数9
- ありがとう数9
- みんなの回答 (9)
- 専門家の回答
みんなの回答
FarEyesです。 すみません、#8の下記部分において訂正があります。 > この静的な作業領域は、共通なエリアですので、他の処理(マルチスレッド > などで並列処理を行っている場合も含めて)で、strtok関数が呼ばれてしまう > と、今まで保持していた文字列が、書き換えられてしまいます。 > その結果、意図しない結果が発生する可能性があります。 上記文中の、 (マルチスレッドなどで並列処理を行っている場合も含めて) の部分ですが、処理系によっては、 strtok関数などの静的作業領域を使用する関数において、 『これらの関数を同時に複数のスレッドから呼び出すことによって 障害が発生することはありません。』 のように謳われている処理系も存在します。 ですので、 (マルチスレッドなどで並列処理を行っている場合も含めて) の部分は無視して下さい。 どうも、申し訳ありませんでした。
こんにちは。 FarEyesです。 =========== 1)まず、先に「質問2」についての回答です。 当方でも、質問者さんと同様な箇所に、デバッグ用のprintf文を追加して、 かつ、CSVファイルも同様に以下のもの、 1,,さしす 2,a,b 3,c,d を使用して検証してみました。 結果は、質問者さんの検証結果と同じ結果となりました。 結論から申し上げると、これは「正常動作」となります。 自分でも、最初ちょっと戸惑いましたが、デバッガによりステップ実行しながら、 検証したみた過程で、正常動作だと気づきました。 printfの出力結果だけを見た場合、同じ状態が2度以上繰り返されているように 見えますが、これは、 strtok2関数の「1回」の呼び出しで、繰り返されている 訳ではありません。 これは、 strtok2関数の「複数回」の呼び出しの結果として、繰り返されている 「ように見える」 ということです。 言葉で説明すると解り辛いので、以下の検証を行ってみて下さい。 ■検証操作 デバッグ用に挿入したprintf文はそのままにしておいて、main関数の下記部分、 <変更前> /* 区切り文字(カンマ、改行)で区切って各列の文字列を取得 */ strtok2( buff, swk, ",\n", 1, 32 ); /*1列目*/ strtok2( buff, tbl[ntb].bbb, ",\n", 2, 32 ); /*2列目*/ strtok2( buff, tbl[ntb].ccc, ",\n", 3, 32 ); /*3列目*/ を、以下のように変更したのち、再ビルド&実行してみて下さい。 <変更後> /* 区切り文字(カンマ、改行)で区切って各列の文字列を取得 */ printf( "== %d行目、%d列目 ==\n", (ntb+1), 1 ); strtok2( buff, swk, ",\n", 1, 32 ); /*1列目*/ printf( "== %d行目、%d列目 ==\n", (ntb+1), 2 ); strtok2( buff, tbl[ntb].bbb, ",\n", 2, 32 ); /*2列目*/ printf( "== %d行目、%d列目 ==\n", (ntb+1), 3 ); strtok2( buff, tbl[ntb].ccc, ",\n", 3, 32 ); /*3列目*/ 以下は、上記変更後のプログラムの実行結果(コンソールへの出力ログ)です。 == 1行目、1列目 == p2=,,さしす == 1行目、2列目 == p2=,,さしす p2=,さしす == 1行目、3列目 == p2=,,さしす p2=,さしす p2= == 2行目、1列目 == p2=,a,b == 2行目、2列目 == p2=,a,b p2=,b == 2行目、3列目 == p2=,a,b p2=,b p2= == 3行目、1列目 == p2=,c,d == 3行目、2列目 == p2=,c,d p2=,d == 3行目、3列目 == p2=,c,d p2=,d p2= これを見ると、関数の1回の呼び出しで、重複している部分は無いことが解る と思います。 今回のご質問での、最初のオリジナルソースの下記部分、 strcpy(tbl[ntb].aaa,strtok(buff,",")); strcpy(tbl[ntb].bbb,strtok(NULL,",")); strcpy(tbl[ntb].ccc,strtok(NULL,",")); でのC言語ライブラリのstrtok関数の処理と比較すると、今回のstrtok2関数 では、無駄な処理が入っているように思えますが、それは下記に述べる現象 を回避するためです。 strtok関数は、ライブラリ内部に、静的な作業領域(ワークバッファ)を持って いて、そのバッファに引数で渡された文字列を格納して、その文字列を書き 換えながら、部分文字列(正確には、ワークバッファ上の部分文字列への ポインタ)を戻り値として返すようになっています。 この静的な作業領域は、共通なエリアですので、他の処理(マルチスレッド などで並列処理を行っている場合も含めて)で、strtok関数が呼ばれてしまう と、今まで保持していた文字列が、書き換えられてしまいます。 その結果、意図しない結果が発生する可能性があります。 今回のstrtok2関数では、上記のような静的な作業領域は設けず、常に引数 で渡された文字列の先頭から、指定位置(列位置)の部分文字列を取り出す ような処理を行っています。 =========== 2)質問1について > p2 = strpbrk( (const char*)p1, (const char*)strSep ); > if( p2 ){ > このif文の条件のところp2って記入されたけど理解できてないです。 > p2が条件?なんの条件ですか? strpbrk関数の結果、「区切り文字」が、 見つかった場合 → 戻り値が、見つかった位置のポインタで返される (= NULLではない → = 0 ではない) 見つからなかった場合 → 戻り値が、NULLで返される (= NULL → 数値で表すと = 0 ) (注:処理系により異なるかもしれません)、 ということになります。 その結果、p2 の値により、if文の条件が、 p2 != NULL → (p2 != 0) → if( 0以外 ) → 「真」 p2 = NULL → (p2 = 0) → if( 0 ) → 「偽」 ということになります。 =========== 3)質問3について > usize = (p2 - p1) / sizeof(char); > この行の意味もちょっと説明お願いします。 これは、usize に「部分文字列」の「文字数」をセットするための計算を行って います。 p1 = 文字列の先頭位置のポインタ p2 = 文字列中の最初に見つかった「区切り文字」位置のポインタ これから、 p2 - p1 として、「部分文字列」の「バイト数」が得られます。 そして、「バイト数」から「文字数」に変換するために、 / sizeof(char) として、「文字数」を計算しています。 ※これは、処理系により、char型のサイズが異なる可能性を考慮して行って います。 =========== 4)その他 > ソースみてもカンマの処理まで理解できないところあります。 > 日本語で箇条書きしていただけませんでしょうか??? 申し訳ありませんが、そこまでするサービス精神は、私にはありません、 これは、質問者さんご自身で、解析なさって下さい。 ※人から教えてもらうだけだと、身に付かないと思います。 ※あと、今回の内容でおおよその部分は、ご理解戴けるかと思います。 ※もしも、お気に触られた場合は、申し訳ありません。
こんばんは。 FarEyesです。 お礼をいただき有り難うございます。 問題が解決されたのであれば、こちらも嬉しく思います。 一応、疑問符"?"で、ご返信戴いたので、こちらも返信させて戴きました。 > また分からないところありましたら質問していいですか? 何時でもどうぞ。(ここは、そのための場所ですので。。。) なお、今回の件に直接関係ない御質問等であれば、新たに質問スレッドを 立てられた方が良いかと思います。 (すみません。御解りかと思いますが、念のため。。。)
補足
for(icnt=1; icnt<=nPos && *p1!='\0'; icnt++) { /* 区切り文字の位置取得 */ /* ※ポインタで返される。なければNULL */ p2 = strpbrk( (const char*)p1, (const char*)strSep ); if( p2 ){ /* 区切り文字あり */ if( icnt==nPos ){ /* 指定の区切り位置*/ usize = (p2 - p1) / sizeof(char); if( usize >= nSize ) usize = (nSize - 1); if( usize > 0 ){ /* 部分文字列あり */ strncpy( strDest, p1, usize ); strDest[usize] = '\0'; ians = 1; } else{ /* 部分文字列なし */ strDest[0] = '\0'; ians = 0; } } p1 = p2+1; /* 参照元の文字列ポインタを1列分進める */ } else{ /* 区切り文字なし */ if( icnt==nPos ){ /* 指定の区切り位置 */ usize = strlen( p1 ); if( usize >= nSize ) usize = (nSize - 1); if( usize > 0 ){ /* 部分文字列あり */ strncpy( strDest, p1, usize ); strDest[usize] = '\0'; ians = 1; } else{ /* 部分文字列なし */ strDest[0] = '\0'; ians = 0; } } else{ /* 指定の区切り位置でない */ strDest[0] = '\0'; ians = 0; } break; } } return ians; } FarEyesさん、上の処理が分からない箇所がありましてm(_ _)m また貴重な時間を~~~~~~~~~~m(_ _)m ! 質問1; p2 = strpbrk( (const char*)p1, (const char*)strSep ); if( p2 ){ このif文の条件のところp2って記入されたけど理解できてないです。 p2が条件?なんの条件ですか? 質問2; p2 = strpbrk( (const char*)p1, (const char*)strSep ); 下に printf("p2=%s",p2); を記入して p2の値を出力してみました、 ファイル内容が 1,,さしす 2,a,b 3,c,d の場合: p2=,,さしす p2=,,さしす p2=,さしす p2=,,さしす p2=,さしす p2= p2=,a,b p2=,a,b p2=,b p2=,a,b p2=,b p2= p2=,c,d p2=,c,d p2=,d p2=,c,d p2=,d p2= いうふうに表示されます。 無駄な処理があるということでしょうか? 実は以下になるようにするんですかね? それとも私の理解まちがいですか? p2=,,さしす p2=,さしす p2= p2=,a,b p2=,b p2= p2=,c,d p2=,d p2= 質問3; usize = (p2 - p1) / sizeof(char); この行の意味もちょっと説明お願いします。 ソースみてもカンマの処理まで理解できないところあります。 日本語で箇条書きしていただけませんでしょうか??? 本当に失礼いたします。
こんにちは。 FarEyesです。 ■補足1 > if( usize >= nSize ) usize = (nSize - 1); > if( usize >= nSize ) usize = (nSize - 1); > 上の2箇所警告が出ます、 > 警告 W8012 1116.cpp 126: 符号付き値と符号なし値の比較(関数 strtok2(char *,char *,char *,int,int) ) > なぜでしょうか? 申し訳ありません。当方の検証不足でした。 「警告」の理由は、表示出力されたメッセージのとおり、 「符号付き値と符号なし値の比較」 → ( usize >= nSize ) の部分です。 を行っているからです。 今回は、usize が符号なし(unsigned long)、nSize が符号付き(int)となっていますが、 実行時の値を比較する時点、 if( usize >= nSize ) usize = (nSize - 1); では、両方の値とも必ず 0 以上の値になっているので、処理上は問題なく実行される と思います。 しかしながら、厳密には型を合わせるのが基本ですので、正しくは、例えば、 if( usize >= (unsigned long)nSize ) usize = ((unsigned long)nSize - 1); のようにキャストするなどの記述をすべきでしたね。(すみませんでした。) ■補足2 > 上の処理を消しても正常に動いてます。ちょっとわかりませんので教えていただけますか? それは、実行時に以下のif文が「真」になる条件、 if( usize >= nSize ) usize = (nSize - 1); ( usize >= nSize )の状態になること( → usize = (nSize - 1); が実行される条件 )が、 発生しないために、この文をとっても処理が変わらなかったためだと思われます。 ちなみに、( usize >= nSize )が真になる条件とは、 入力CSVファイル上の、カンマ区切りの「部分文字列」の文字数(=usize)が、 それを格納する引数で指定された文字列バッファの指定サイズ(=nSize)を、 オーバーしてしまうとき。 となります。 ですので、このif文を入れずに、上記の状態になってしまった場合は、格納先の 文字列バッファの確保領域を越えて、文字列がコピーされてしまうことになります。 このif文は、それを防止するために入れてあります。 ■補足3 > もし > ファイル内容が > 1,あいうえお、かきく > 2,, > 3,,さしす > で 以下の構造体に格納したい場合どこを直したらいいですか? > どこでint型に変換するんですか? > > struct tb{ > int aaa[32]; > char bbb[32]; > char ccc[32]; > }; ひとつ確認があります。 struct tb{ int aaa[32]; char bbb[32]; char ccc[32]; }; の記述は、 struct tb{ int aaa; char bbb[32]; char ccc[32]; }; とするのが正しいのではないでしょうか? ※CSVデータの1列目のみ「数値」として扱うのであれば、構造体のメンバには、 対応する数値変数を1個、用意すればよく、配列にする必要はないと思われ ます。 上記で後者の方の構造体の場合だったとして、以下は修正案の一例です。 ■修正案 1)まず、以下のようにヘッダのインクルードと、main関数に作業用のchar型配列 と、char型ポインタを、 <追加部分> : #include <stdlib.h> /* strtol関数のためのインクルード */ : /*== main ==*/ int main(int argc, char *argv[]) { : char swk[32]; /* 作業用の文字列バッファ */ char *p1; /* 作業用のchar型ポインタ */ : のように追加しておきます。 2)次に、main関数のCSVデータ取り込み部分の <変更前> : /* 取得文字列のバッファを始めにクリア */ tbl[ntb].aaa[0] = '\0'; tbl[ntb].bbb[0] = '\0'; tbl[ntb].ccc[0] = '\0'; /* 区切り文字(カンマ、改行)で区切って各列の文字列を取得 */ strtok2( buff, tbl[ntb].aaa, ",\n", 1, TBF ); /*1列目*/ strtok2( buff, tbl[ntb].bbb, ",\n", 2, TBF ); /*2列目*/ strtok2( buff, tbl[ntb].ccc, ",\n", 3, TBF ); /*3列目*/ : の部分を、 <変更後> : /* 取得文字列のバッファを始めにクリア */ tbl[ntb].aaa = 0; tbl[ntb].bbb[0] = '\0'; tbl[ntb].ccc[0] = '\0'; /* 区切り文字(カンマ、改行)で区切って各列の文字列を取得 */ strtok2( buff, swk, ",\n", 1, 32 ); /*1列目*/ strtok2( buff, tbl[ntb].bbb, ",\n", 2, 32 ); /*2列目*/ strtok2( buff, tbl[ntb].ccc, ",\n", 3, 32 ); /*3列目*/ /* 1列目の数字文字列を整数値に変換して構造体メンバに格納 */ tbl[ntb].aaa = strtol( (const char*)swk, &p1, 10 ); : のように変更します。 注)ただし、数字文字列の正当性チェック&エラー処理、 →文字列が数値として解釈できない文字列だった場合の処理 は省略しています。 3)次に、同じくmain関数の出力部分の <変更前> : for ( itb=0; itb<ntb; itb++ ) { /*取得行数分ループ*/ tp = tbl + itb; /*出力対象の構造体のポインタ設定*/ /*1行分を出力*/ fprintf( fo, "%s,%s,%s\n", tp->aaa, tp->bbb, tp->ccc ); } : の部分を、 <変更後> : for ( itb=0; itb<ntb; itb++ ) { /*取得行数分ループ*/ tp = tbl + itb; /*出力対象の構造体のポインタ設定*/ /*1行分を出力*/ fprintf( fo, "%d,%s,%s\n", tp->aaa, tp->bbb, tp->ccc ); } : のように変更すれば良いと思います。 ■補足(コンパイル時の警告について) Borland系のコンパイラーを使用して、今回のサンプル(#3、及び、今回変更後のソース) のコンパイルを行った場合、 警告 W8004 tcsv21.c 148: 'p2' に代入した値は使われていない(関数 strtok2 ) のような「警告」が出る場合があります。 ※この「警告」は実行上、問題はありません。 この「警告」を出したくない場合は、コンパイル時に、 bcc32 -w-8004 tcsv21.c のように、オプション指定で該当の「warning」を無視する設定でコンパイルを行って 下さい。 以上です。
お礼
FarEyesさん: 有り難うございました! 本当にうれしいです。 struct tb{ int aaa[32]; char bbb[32]; char ccc[32]; }; 上は私の間違いです。int aaa; が正解です。 また分からないところありましたら質問していいですか? 本当に助かりました!!!
- toda hiro(@hiro_knigh)
- ベストアンサー率39% (59/151)
CSVの読み込みは何回も議題にあがっているため、一発プログラムを組んでみました。 CSVファイルは仕様自体が曖昧な所もありますが、一般的に知られていると思われる仕様でやっているつもりです。 冗長的なプログラムなので指摘等well comeです。 ---------------------------------------------- #include <stdio.h> #include <string.h> #include <stdlib.h> #define STRING_MAX 2048 #define FIELD_MAX 256 #define RECORD_MAX 16384 #define STRING_BUF 16384 typedef struct scsvrec{ char chr[FIELD_MAX][STRING_MAX]; }SCSVREC; typedef struct scsvdat{ long record_cnt; SCSVREC *dat[RECORD_MAX]; }SCSVDAT; bool csvFileRead(char *filename ,SCSVDAT *csvDat); SCSVREC* csvResolution(char *buf); bool isKanji(unsigned char c); int main() { bool bRet; SCSVDAT *cdat; char filename[1024] = "sample.csv"; cdat = (SCSVDAT*)malloc(sizeof(SCSVDAT)); bRet = csvFileRead(filename, cdat); return(0); } bool csvFileRead(char *filename ,SCSVDAT *csvDat) { FILE *fp; SCSVREC *onerec; bool ret = false; char buf[STRING_BUF]; memset(csvDat ,0 ,sizeof(SCSVDAT)); fp = fopen(filename,"r"); if(fp == NULL){ printf("ファイルが開けませんでした。\n"); return(false); } while(fgets(buf,sizeof(buf),fp) != NULL){ if (buf[strlen(buf)-1] == '\n'){ buf[strlen(buf)-1] = '\0'; } onerec = csvResolution(buf); csvDat->dat[csvDat->record_cnt] = onerec; (csvDat->record_cnt)++; } fclose(fp); return (true); } SCSVREC* csvResolution(char *buf) { int bcnt, rcnt, mcnt; bool dblfld; SCSVREC *onedat; onedat = (SCSVREC*)malloc(sizeof(SCSVREC)); memset(onedat ,0 ,sizeof(SCSVREC)); bcnt = rcnt = mcnt= 0; dblfld = false; for(;;bcnt++) { if (buf[bcnt] == '\0') break; if (mcnt == 0 && buf[bcnt] == '\"') { dblfld = true; continue; } if (isKanji((unsigned char)buf[bcnt])) { onedat->chr[rcnt][mcnt] = buf[bcnt]; mcnt++; bcnt++; onedat->chr[rcnt][mcnt] = buf[bcnt]; mcnt++; continue; } if (dblfld == false && buf[bcnt] == ',') { rcnt++; mcnt = 0; continue; } if (dblfld == true && buf[bcnt] == ',') { onedat->chr[rcnt][mcnt] = buf[bcnt]; mcnt++; continue; } if (dblfld == true && buf[bcnt] == '\"' && buf[bcnt+1] == '\"') { onedat->chr[rcnt][mcnt] = '\"'; mcnt++; bcnt++; continue; } if (dblfld == true && buf[bcnt] == '\"' && (buf[bcnt+1] == ',' || buf[bcnt+1] == '\0')) { rcnt++; mcnt = 0; bcnt++; dblfld = false; continue; } onedat->chr[rcnt][mcnt] = buf[bcnt]; mcnt++; } return onedat; } bool isKanji(unsigned char c) { bool ret = false; if ((0x81 <= c && c <= 0x9f) || (0xE0 <= c && c <= 0xEF)) { ret = true; } return (ret); }
お礼
hiro_knighさん: 本当にありがとうございました!! 参考にしました。
- titokani
- ベストアンサー率19% (341/1726)
strtokは区切り文字が連続する場合があるCSVには使えません。 この場合なら、sscanfが使えます。 sscanf(buff,"%[^,],%[^,],%[^,]",tbl[ntb].aaa,tbl[ntb].bbb,tbl[ntb].ccc); もし、CSVが 123,"456,789",999 などのようにカンマを含む文字列を持つなら、sscanfも使えません。一文字ずつ独自に処理する必要があります。 さらに、 123,"456 789",999 といった感じで改行を含む文字列も持つなら、fgetsすら使えません。
こんにちは。 strtok関数では、区切り文字が2文字以上、連続している場合は、1つの区切り区分 として処理されてしまうようです。 従って、入力ファイル上のCSVデータが、 あいう,,さしす のような場合、 1回目のstrtokでは、"あいう"の先頭ポインタが返され、 2回目のstrtokでは、空き文字列ではなく、"さしす"の先頭ポインタが返されます。 また、strtok関数では、次の区切り文字まで(文字列終端の'\0'のコードも区切り に含まれます)に文字列がなかった場合は、NULLが返されます。 ですので、#2の方が言われているように、この戻り値がNULLだった場合のチェック をしないで、ご提示のような、 strcpy(tbl[ntb].ccc,strtok(NULL,",")); のコードを実行してしまうと、NULLポインタから文字列をコピーしようとして、その 結果、例外エラーなどが発生してしまう可能性があります。 以上のような障害を回避するには、strtok関数は使用せずに、別な方法(専用の 関数を作るなど)をとる必要があります。 以上を踏まえて、ご提示のコードを変更したバージョンを作成してみました。 strtok関数の代わりに、専用の関数【strtok2】を作ってあります。 また、入力ファイル名と出力ファイル名は、実行時のコマンドラインで指定するよう に変更しています。 ■変更バージョン(サンプル) 注1)インデント等のため、全角スペースを入れています。 コピペの際は、半角スペースorタブに置換して下さい。 注2)一応、動作検証はしていますが、まだ不十分な部分があるかもしれません。 そのため、ご使用の環境で上手く動作しなかった場合は、すみません。 ========================= /* * tcsv20.c : CSVファイル読み込み&書き込みテスト */ #include <stdio.h> #include <string.h> #define MBF 256 /* 汎用の文字列バッファのサイズ */ #define TBF 32 /* 構造体用の文字列バッファのサイズ */ /* CSVデータ読み込み用のバッファ構造体 */ struct tb{ char aaa[TBF+1]; /* 1列目のデータ用 */ char bbb[TBF+1]; /* 2列目のデータ用 */ char ccc[TBF+1]; /* 3列目のデータ用 */ }; /* 関数プロトタイプ */ int strtok2( char *strSrc, char *strDest, char *strSep, int nPos, int nSize ); /*== main ==*/ int main(int argc, char *argv[]) { struct tb tbl[20]; /* CSVデータ読み込み用の構造体配列 */ struct tb *tp; /* ↑のアクセス用の構造体ポインタ */ int ntb,itb; /* 行カウンタ */ char szInpCSV[256]; /* 入力CSVファイル名 */ char szOutCSV[256]; /* 出力CSVファイル名 */ FILE* fi; /* 入力CSVファイル用のファイルポインタ */ FILE* fo; /* 出力CSVファイル用のファイルポインタ */ char buff[MBF]; /* 1行読み込み用バッファ */ //== コマンドラインのファイル名の有無チェック == if( argc < 3 ){ printf( "入力ファイル名(CSV)と出力ファイル名(CSV)を指定して下さい。\n" ); return -1; } //== コマンドラインの入力ファイル名、出力ファイル名を取得 == strcpy( szInpCSV, argv[1] ); strcpy( szOutCSV, argv[2] ); //== 入力CSVファイルからの読み込み処理 == fi = fopen( szInpCSV, "r" ); /*入力ファイルオープン*/ if( fi == NULL ){ printf( "入力ファイル\"%s\"が開けません\n", szInpCSV ); return -2; } ntb = 0; /*行カウンタの初期化*/ while ( fgets(buff, MBF, fi) != NULL ) /*1行毎に読み込み*/ { /* 取得文字列のバッファを始めにクリア */ tbl[ntb].aaa[0] = '\0'; tbl[ntb].bbb[0] = '\0'; tbl[ntb].ccc[0] = '\0'; /* 区切り文字(カンマ、改行)で区切って各列の文字列を取得 */ strtok2( buff, tbl[ntb].aaa, ",\n", 1, TBF ); /*1列目*/ strtok2( buff, tbl[ntb].bbb, ",\n", 2, TBF ); /*2列目*/ strtok2( buff, tbl[ntb].ccc, ",\n", 3, TBF ); /*3列目*/ /* * strcpy(tbl[ntb].aaa,strtok(buff,",")); * strcpy(tbl[ntb].bbb,strtok(NULL,",")); * strcpy(tbl[ntb].ccc,strtok(NULL,",")); */ ntb++; /*行カウンタ+1*/ } fclose( fi ); /*入力ファイルクローズ*/ //== 出力CSVファイルへの書き込み処理 == fo = fopen( szOutCSV, "w" ); /*出力ファイルオープン*/ if( fo == NULL ){ printf( "出力ファイル\"%s\"が開けません\n", szOutCSV ); return -3; } for ( itb=0; itb<ntb; itb++ ) { /*取得行数分ループ*/ tp = tbl + itb; /*出力対象の構造体のポインタ設定*/ /*1行分を出力*/ fprintf( fo, "%s,%s,%s\n", tp->aaa, tp->bbb, tp->ccc ); } fclose( fo ); /*出力ファイルクローズ*/ return 0; } /* * strtok2 : 区切り文字単位の部分文字列の取得 *【引数】 * char *strSrc; 参照元の文字列 * char *strDest; 取り出した部分文字列の格納先 * char *strSep; 区切り文字の文字群 * int nPos; 取り出し位置(区切り文字毎のカラム位置) * int nSize; strDestのバッファサイズ *【戻り値】 * int =0 : 部分文字列なし * =1 : 部分文字列あり * =-1: 引数エラー */ int strtok2( char *strSrc, char *strDest, char *strSep, int nPos, int nSize ) { int ians = 0; /* 戻り値 */ int icnt; /* ループ変数 */ unsigned long usize; /* 文字列のサイズ */ char *p1; /* 文字列アクセス用のポインタ */ char *p2; /* 文字列アクセス用のポインタ */ /*== 引数のチェック==*/ if( strSrc==NULL || strDest==NULL || strSep==NULL || nPos<1 || nSize<1 ){ return -1; } /* 部分文字列を空き文字列として初期化 */ strDest[0] = '\0'; /*== 部分文字列の取り出し ==*/ p1 = strSrc; p2 = NULL; /* 指定の区切り位置までループ */ for(icnt=1; icnt<=nPos && *p1!='\0'; icnt++) { /* 区切り文字の位置取得 */ /* ※ポインタで返される。なければNULL */ p2 = strpbrk( (const char*)p1, (const char*)strSep ); if( p2 ){ /* 区切り文字あり */ if( icnt==nPos ){ /* 指定の区切り位置*/ usize = (p2 - p1) / sizeof(char); if( usize >= nSize ) usize = (nSize - 1); if( usize > 0 ){ /* 部分文字列あり */ strncpy( strDest, p1, usize ); strDest[usize] = '\0'; ians = 1; } else{ /* 部分文字列なし */ strDest[0] = '\0'; ians = 0; } } p1 = p2+1; /* 参照元の文字列ポインタを1列分進める */ } else{ /* 区切り文字なし */ if( icnt==nPos ){ /* 指定の区切り位置 */ usize = strlen( p1 ); if( usize >= nSize ) usize = (nSize - 1); if( usize > 0 ){ /* 部分文字列あり */ strncpy( strDest, p1, usize ); strDest[usize] = '\0'; ians = 1; } else{ /* 部分文字列なし */ strDest[0] = '\0'; ians = 0; } } else{ /* 指定の区切り位置でない */ strDest[0] = '\0'; ians = 0; } break; } } return ians; } ========================= ■上記サンプルの実行結果 ※以下のように、出力ファイルは、入力ファイルと同一フォーマットになります。 (まぁ、ここに貼り付けただけだとあまり意味はないですね。。。) 1)入力CSVファイルが下記だった場合 あいう,かきく,さしす たちつ,なにぬ,はひふ まみむ,やゆよ,らりる <出力CSVファイルの結果> あいう,かきく,さしす たちつ,なにぬ,はひふ まみむ,やゆよ,らりる 2)入力CSVファイルが下記だった場合 あいう,,さしす たちつ,なにぬ,はひふ まみむ,, あいう,win, <出力CSVファイルの結果> あいう,,さしす たちつ,なにぬ,はひふ まみむ,, あいう,win, 3)入力CSVファイルが下記だった場合 ,B1, ,,C2 A3,, <出力CSVファイルの結果> ,B1, ,,C2 A3,, 以上です。参考になれば幸いです。
補足
親切な回答ありがとうございました。 動かしてみました。 質問があります。 if( usize >= nSize ) usize = (nSize - 1); if( usize >= nSize ) usize = (nSize - 1); 上の2箇所警告が出ます、 警告 W8012 1116.cpp 126: 符号付き値と符号なし値の比較(関数 strtok2(char *,char *,char *,int,int) ) なぜでしょうか? 上の処理を消しても正常に動いてます。ちょっとわかりませんので教えていただけますか? もし ファイル内容が 1,あいうえお、かきく 2,, 3,,さしす で 以下の構造体に格納したい場合どこを直したらいいですか? どこでint型に変換するんですか? struct tb{ int aaa[32]; char bbb[32]; char ccc[32]; };
- shippo_ppk
- ベストアンサー率51% (28/54)
だめなところ struct tb tbl[20]; を初期化していない。 strtok の戻り値を無評価で使用している。
- Yanch
- ベストアンサー率50% (114/225)
strtok関数が使えるんじゃないかな?
関連するQ&A
- csvファイルを構造体に格納したいです
ファイル内容 ******************** あいう,,さしす たちつ,なにぬ,はひふ まみむ,, あいう,win, ******************** #include <stdio.h> #include <string.h> #define MBF 256 struct tb{ char aaa[32]; char bbb[32]; char ccc[32]; }; int main(){ struct tb tbl[20]; struct tb *tp; int ntb,itb; FILE* fi; FILE* fo; char buff[MBF]; // 入力 fi = fopen("sample.csv","r"); // 検査省略 if( fi == NULL ){ printf( "%sファイルが開けません\n" ); return -1; } ntb = 0; while ( fgets(buff,MBF,fi ) != NULL ) { strcpy(tbl[ntb].aaa,strtok(buff,",")); strcpy(tbl[ntb].bbb,strtok(NULL,",")); strcpy(tbl[ntb].ccc,strtok(NULL,",")); ntb++; } fclose( fi ); // 出力 fo = fopen("csvo.csv","w"); if( fo == NULL ){ printf( "%sファイルが開けません\n" ); return -1; } for ( itb=0;itb<ntb;itb++ ) { tp = tbl+itb; fprintf(fo,"%s%s%s",tp->aaa,tp->bbb,tp->ccc); } fclose( fo ); return 0; } csvファイルないようが以下であれば格納できるけど、すごく困ってます。 ******************** あいう,かきく,さしす たちつ,なにぬ,はひふ まみむ,やゆよ,らりる ********************
- 締切済み
- 会計ソフト
- test.csvの内容が以下のように100件位あったとして、
test.csvの内容が以下のように100件位あったとして、 d,4,ddddd bb,2,bbbbb a,1,aaa cc,3,ccccc 以下略 やりたい事としてtest.csvを2列目をキーとして昇順にソートし、新たなファイル「test1.csv」(こちらは全件表示)に書き込み、 また、test1.csvのキリのいい所だけ抜き出したcsvファイル「test2.csv」(1件目、10件目、20件目、…と以下10の倍数毎のデータだけを書き込む) を作りたいと考えて下の様なプログラムを作っていますが、ソートの段階で詰まっています。 もしよろしければこの続きを教えて頂けないでしょうか? #include <stdio.h> #define MBF 256 #define SIZE 32 #define FNAME1 "test.csv" #define FNAME2 "test1.csv" #define FNAME3 "test2.csv" struct tb{ char a[SIZE]; char b[SIZE]; char c[SIZE]; }; int main(void) { struct tb tbl[20]; struct tb *tp; int ntb,itb; FILE* fi; FILE* fo; FILE* fq; char buff[MBF]; //== 入力CSVファイルからの読み込み処理 == fi = fopen( FNAME1, "r" ); if( fi == NULL ){ printf( "ファイル\"%s\"が開けません\n", FNAME1 ); return -1; } while( fgets(buff, MBF, fi) != NULL ) { strcpy(test.a,strtok(buff,",\"")); strcpy(test.b,strtok(NULL,",\"")); strcpy(test.c,strtok(NULL,",\"")); } fclose( fi ); //== 出力CSV(test2.csv)ファイルへの書き込み処理 == fo = fopen( FNAME2, "w" ); if( fo == NULL ){ printf( "ファイル\"%s\"が開けません\n", FNAME2 ); return -1; } for ( itb=0; itb<ntb; itb++ ){ //書き込み処理? } fclose( fo ); //出力ファイルクローズ //== 出力CSV(test2.csv)ファイルへの書き込み処理 == fq = fopen( FNAME2, "w" ); if( fq == NULL ){ printf( "ファイル\"%s\"が開けません\n", FNAME3 ); return -1; } for ( itb=0; itb<ntb; itb++ ){ //書き込み処理? } fclose( fq ); //出力ファイルクローズ return 0; }
- ベストアンサー
- C・C++・C#
- test.csvの内容
test.csvの内容 "a","b","c","d" "e","f","g","h" "i","j","k","l" "m","n","o","p" "q","r","s","t" "u","v","w","x" "あ","い","う","え" "か","き","く","け" "さ","し","す","せ" "た","ち","つ","て" とし、真ん中のq,r,s,t以降の内容を表示させたく、下のようなプログラムを作成しました しかし、コンパイル後実行しようとするとエラーになってしまいます。どう直したらよいか教えて頂けますでしょうか? #include <stdio.h> #include <stdlib.h> #include <string.h> #define NAME "test.csv" #define SIZE 32 struct tb{ char a[SIZE]; char b[SIZE]; char c[SIZE]; char d[SIZE]; }; int main(void) { struct tb test; FILE *fp; char buff[SIZE]; long pos; pos=ftell(fp); fseek(fp,pos,SEEK_SET); while(fgets(buff,SIZE,fp) != NULL){ //各項目の設定 strcpy(test.a,strtok(buff,",\"")); strcpy(test.b,strtok(NULL,",\"")); strcpy(test.c,strtok(NULL,",\"")); strcpy(test.d,strtok(NULL,",\"")); printf("%s %s %s %s \n",test.a,test.b,test.c,test.d); } }
- ベストアンサー
- C・C++・C#
- CSVファイルの内容を構造体に格納したい(Unix使用)。
こんにちは。私は30代の男性です。 「名前」「身長」「体重」が記載されたCSVファイルの内容を読み取って、構造体の「name」「height」「weight」に格納するプログラムを作っています。CSVの内容は A,175,80 B,167,89 C,155,45 ・ ・ ・ Z,188,70 だと仮定します。数値が読み取れているか、下記のように「tp = strtok(file_image, ",\n" );」の前後に「printf("%s\n", file_image);」を置いてみたら、strtok前では全て表示されるのに、strtok後では「ABC」しか表示されません。これでは全てのデータを構造体に格納できないので、困っています。 1.どのようにすれば、数字も取り出せる(読み取れる)でしょうか? 2.効率よく構造体に格納するには、どのようにしたらよいでしょうか? アドバイスを頂ければ幸いです。宜しくお願いいたします。 #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <string.h> int main(int argc, char *argv[]) { FILE *fp = NULL; int rtn = 0; if ((fp = fopen(argv[1], "r")) == NULL) { printf("ファイルオープンに失敗しました。\n"); return 1; } if (argc != 2) { printf("ERROR: オプションの数に過不足があります。\n"); return 1; } rtn = change_csv(fp); return 0; } int change_csv(FILE *fp) { int i; int j; char file_image[256]; /* 読み込んだ先のメモリの領域 */ char *tp; for (i = 0; i <= 256; i++) { if (fgets(file_image, 256, fp) == NULL) { if (ferror(fp) != 0) { printf("ERROR: 読み込みに失敗しました。\n"); return 1; } } if (feof(fp) != 0) { break; } printf("%s\n", file_image); tp = strtok(file_image, ",\n" ); printf("%s\n", file_image); } fclose(fp); return 0; }
- ベストアンサー
- C・C++・C#
- 構造体について
typedef struct num{ char rv[1000]; struct number *next; struct number *prev; }Num; このような双方向のリスト構造に void aplist(Num **s,char ns[]){ Num *old, *new; if(*s==NULL){ *s = (Num *)malloc(sizeof(Num)*1); strcpy((*s)->rv,ns); (*s)->next = NULL; (*s)->ago = NULL; return;} old= (*s); while(old->next != NULL){ old = old->next;} new = (Num *)malloc(sizeof(Num)*1); strcpy(new->rv,ns); new->next = NULL; old->next = new; new->prev = old; 関数の中でこのようにnext,oldを指定していったのですが、これでよいでしょうか?また、このリストを逆方向(prevの方向)に表示していきたいのですがどのように書けばよいでしょうか?nsは1行の文字列で、各構造体に1行ずつ入れていっています。
- ベストアンサー
- C・C++・C#
- 構造体の型について
ある構造体をxxxと名づける以下のプログラムを作成しました。 ーーーーーーーーーーー #include <stdio.h> #include <string.h> main() { typedef struct { char variable[64]; char type[64]; char value[512]; } xxx; xxx aaa; strcpy(aaa.variable,"bbb"); printf("%s\n",aaa.variable); } ーーーーーーーーーーー これは動き、bbbと表示されます。 しかしながら、構造体のポインタを使用した 以下のプログラムではコンパイルはとおりますが実行時にコアダンプして落ち ます。 ーーーーーーーーーーーーーーーーーーーー #include <stdio.h> #include <string.h> main() { typedef struct { char variable[64]; char type[64]; char value[512]; } xxx; xxx* aaa; strcpy(aaa->variable,"bbb"); printf("%s\n",aaa->variable); } ーーーーーーーーーーーーーーーーーーーーーーー 両プログラムの意図はまったく同じなのに何故いけないのでしょうか。
- ベストアンサー
- C・C++・C#
- プログラム高速化について
http://oshiete1.goo.ne.jp/qa5810041.html で質問させていただいた者です。 ソースは下ので動いているのですが、北海道近辺の郵便番号を調べようとするとすぐに結果が返ってくるのですが、 沖縄県辺りですと少し時間がかかってしまいます。(わずかな差ですが…) 恐らく、上から1個ずつしらみつぶしに調べているのでそのような結果になっているのはわかるのですが… これを高速化するにはwhile文をいじる必要があると思うのですが、どのようにしたら良いでしょうか? 以下がプログラムになります。 ken_all.csvの場所:http://www.post.japanpost.jp/zipcode/dl/kogaki.html #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define NAME "ken_all.csv" #define SIZE 1024 #define setstr(x,z) {strcpy(x,strtok(z,",\""));} struct tb{ //構造体設定 char dummy[SIZE]; //全国地方公共団体コード char old_num[SIZE]; //旧郵便番号 char now_num[SIZE]; //現在の郵便番号 char kana1[SIZE]; //都道府県名(カナ) char kana2[SIZE]; //市区町村名(カナ) char kana3[SIZE]; //町域名(カナ) char kanji1[SIZE]; //都道府県名(漢字) char kanji2[SIZE]; //市区町村名(漢字) char kanji3[SIZE]; //町域名(漢字) }; int main(int argc, char *argv[]) { struct tb line; FILE *fp; char *address, buff[SIZE], string_buff[SIZE]; int flag; clock_t start,end; start = clock(); if(argc == 1){ printf("引数を指定してください\n"); } if(argc > 2){ printf("引数が多すぎます、引数は1つにしてください。\n"); return -1; } if((fp=fopen(NAME,"r"))==NULL){ printf("ファイル%sが開けません\n",NAME); return -1; } flag=0; address = argv[1]; while(fgets(buff,SIZE,fp) != NULL){ //各項目の設定 strcpy(line.dummy,strtok(buff,",\"")); strcpy(line.old_num,strtok(NULL,",\"")); strcpy(line.now_num,strtok(NULL,",\"")); strcpy(line.kana1,strtok(NULL,",\"")); strcpy(line.kana2,strtok(NULL,",\"")); strcpy(line.kana3,strtok(NULL,",\"")); strcpy(line.kanji1,strtok(NULL,",\"")); strcpy(line.kanji2,strtok(NULL,",\"")); strcpy(line.kanji3,strtok(NULL,",\"")); //文字の連結 strcpy(string_buff,line.kanji1); strcat(string_buff,line.kanji2); strcat(string_buff,line.kanji3); //住所の比較 if(strcmp(string_buff,address)==0){ printf("〒%s \n",line.now_num); flag=1; break; } //郵便番号の比較 if(strcmp(line.now_num,address)==0){ printf("%s \n",string_buff); flag=1; break; } } fclose(fp); if(flag==0 && atoi(argv[1]) == 0){ printf("「%s」に該当する郵便番号はありませんでした\n",address); } if(flag==0 && atoi(argv[1]) != 0){ printf("「%s」に該当する住所はありませんでした\n",address); } end = clock(); printf("%.30f秒かかりました\n",(double)(end-start)/CLOCKS_PER_SEC); return 0; }
- ベストアンサー
- C・C++・C#
- 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・C++・C#
- ファイルの入出力に関する質問
CSVファイルを読み込んで、処理をするプログラムを書いています。 しかし、うまくいきません。 CSVファイルは 単語1,数値データ 単語2,数値データ のようになっており、 これをsの配列に格納したいと思っています。 プログラムは以下の通りなんですが。。。 strtokはhttp://www9.plala.or.jp/sgwr-t/lib/strtok.html を参考にしました。 どなたかおしえていただけないでしょうか? #include<stdio.h> #include <string.h> int main(void) { FILE *fp; char s[1000][1000]; char tp[256]; int i=0; if((fp=fopen("in.csv","r"))==NULL){ printf("ファイルオープンできませんよ\n"); exit(1); } while(fgets(tp,256,fp)!=NULL){ tp=strtok(fp,","); puts(s[i][0]=tp); while (tp != NULL ) { tp = strtok(NULL,","); if (tp= NULL ){ puts(s[i][1]=tp); }}i++; } return(0); }
- ベストアンサー
- C・C++・C#
- 静的構造体の初期化について
struct XXX{ MYSQL *MMM; char a[32]; char b[32]; char c[32]; char d[24]; } static struct XXX YYY[] = { {NULL, "AAA", "BBB", "CCC", "DDD"}, {NULL, "AAA", "BBB", "CCC", "DDD"}, {NULL, "", "", "", ""} } main(){ for(i=0; YYY[i].a[0]; i++){ MMM = ... } 上記のように、YYY[]分MMMの値を設定します。 この設定はうまくMMMが取得できるのですが、 実際にはMMMを1つの構造体の中に10個分用意したいです。 つまり、イメージは struct XXX{ MYSQL *MMM[10]; ... } このような形です。 ただし、初期化の仕方がわからずコンパイルが通りません。 またこのようなやり方ができるのかもわかりません。 どなたかご教授ください。 他にいいやり方があるのであればそれも教えて下さい。
- ベストアンサー
- C・C++・C#
お礼
FarEyesさん: 本当にありがとうございました。 質問ちゃんと対応してくださって本当にうれしかったです。 カンマ処理するstrtok2関数についてのソースコードは理解できるまでがんばっていきたいです。 また よろしくお願いいたします。