- ベストアンサー
va_listを使用したfscanf()関数のラッパー関数
【環境】WinXP/VC++6.0/Win32コンソールアプリ 初めて質問させていただきます。 テキストファイル入出力クラスを作成中で、書式指定付きの読込み関数を次のように定義しています。 CTextFileIO::Read(LPCSTR lpFormat,...) { va_list arg; va_start(arg,lpFormat); fscanf(this->hFile,lpFormat,arg); va_end(arg); } hoge.txtの内容 5 使用する場合 void main() { int i; CTextFileIO file("hoge.txt"); // ☆読込み file.Read("%d",&i); // i に 5 が読込まれることを期待している。 printf("%d",i); } 実行結果 -858993460 ご質問は、☆の箇所でどうして i に 5 が取得できないのかということです。同様の作り方で出力関数 vfprintf() のラッパー関数を試した場合は正しく動作しました。 以上よろしくお願いいたします。
- meruty
- お礼率100% (15/15)
- C・C++・C#
- 回答数6
- ありがとう数6
- みんなの回答 (6)
- 専門家の回答
質問者が選んだベストアンサー
# 誤解させました(というか問題を読み違えて誤解してた…orz。すみません。 vfscanfがないなら(あってうまくいってないと思ってた)、クラス以前の問題でした。 fscanfはvfscanfと引数が違ってva_listを認識しないので、 fscanfにそのままva_listを渡してもうまく動きません。 fscanfは「複数の引数」を期待する。 vfscanfがあるなら、「複数の引数リストをさす単一の引数」を期待する。 ようは、ポインタと、ポインタとポインタの違いみたいなものですが、これが合わないとうまく取れません。 出力のほうは、ポインタのポインタの先頭がポインタに暗黙で変換されれば出力可能なはずです。 二つ以上の引数を渡しても、正しく出力しますか?ひとつだけで試してませんか? で、手間をかけさせてしまったこともあり、ちょっと説明コードも書いてみました。(VC6はもう捨てたのでVC7.1ですが…) 単純にintの単一変数を取るだけなら、こんな感じで取れるかと思います。 void foo_scan(FILE* file, const char* const format, ...) { va_list arg; va_start(arg, format); fscanf(file,format, *reinterpret_cast<int*>(arg)); va_end(arg); } ただ、このコード、きめ打ちでintですし(こっちはまだアドレスサイズが同一でformatの指定が正しければ動くはず)、 引数の展開をまともにやってないので、二個目以降の値はうまく取れないと思います(理由は前述)。 で。もしもfscanfを使うということであれば、 va_listの内容を自前でスタックにつみなおして、 さも引数が合ったかのようにfscanfを呼ぶなどの細工が必要になるわけです。 VCは不準拠ですが最新のC99にはvfscanfがありますし、 (ライセンスが許せば)このソースなどは参考になるでしょう。 # あえてC言語風で自作する理由は何ですか? # vfscanfがないなら結構実装は面倒だと思いますし、 # Mustでないなら大体策を考えたほうがよい気もします。 # iostream風にするとか、boost::formatを使ってみるとか。
その他の回答 (5)
- MrBan
- ベストアンサー率53% (331/615)
'v'で始まる関数はva_list対応版なのでvfprintfはOKです。fprintfではNGになります。 同様に、vfscanfがあればva_listがOKで、fscanfでは現状のようなNGとなります。 fprintfで試してみると失敗するはずで、 「"fprintfに"二つ以上の引数を渡しても、正しく出力しますか?」等と書くべきでした。すみません。 なお、 > ctextfileio.cpp(90) : error C2018: 文字 '0x81' は認識できません。 これらのエラーは、全角空白を含む場合に出るものです。半角に置換してください。 そのままコピペすると掲示板での見やすさの為に全角空白でインデントされていると思います。
お礼
>> ctextfileio.cpp(90) : error C2018: 文字 '0x81' は認識できません。 >これらのエラーは、全角空白を含む場合に出るものです。 おっしゃる通りです。初歩的ミスでお恥ずかしい。。。 ありがとうございました。
- jacta
- ベストアンサー率26% (845/3158)
VC++6.0決め打ちでよいのであれば、scanf系の共通下請け関数である_input関数を直接呼び出せば簡単に解決しそうな気がします。 # もしかすると、ライブラリをスタティックリンクした場合しか使えない? 別の方法としては、可変個実引数の個数が決まっているのであれば、多重定義か省略時実引数を使って、見かけ上scanf系関数と同じように振舞うようにするのも一つの手です。 この場合、呼び出された時点で個数が分かるはずですし、実引数に渡されるのはポインタに決まっているので、すべてconst volatile void*で受けてからfscanf関数に渡してやれば、どうにかなります。 CTextFileIO::Read(LPCSTR lpFormat, const volatile void* arg) { ... fscanf(this->hFile,lpFormat, arg); ... } CTextFileIO::Read(LPCSTR lpFormat, const volatile void* arg1, const volatile void* arg2) { ... fscanf(this->hFile,lpFormat, arg1, arg2); ... } ただ、可変個実引数はC++とは相性がよくありませんから、std::istreamに置き換えるなどした方が、本当はよいと思います。 ところで、#3で紹介されているboost::formatは出力側しかできないので(つまりprintf系の代わり)、入力側(つまりscanf系の代わり)には使えなかったと思います。
お礼
ご提示いただいたコードをコンパイルすると以下のエラーが出ました。何かオプション指定が必要なのでしょうか。 ctextfileio.cpp(90) : error C2018: 文字 '0x81' は認識できません。 ctextfileio.cpp(90) : error C2018: 文字 '0x40' は認識できません。 >std::istreamに置き換えるなどした方が、本当はよいと思います。 そうですね。fscanf()にこだわった理由は、1行で異なる型の値を一気に読込めると便利だと考えたからですが、 fstream<<5000<<'Y'<<"String"<<endl; こちらのほうが可読性高い分、妥当かと思いました。 ありがとうございました。
- BLUEPIXY
- ベストアンサー率50% (3003/5914)
#2>arg と i がメモリ上の同じアドレスを指すようにできれば fscanf(hFile,lpFormat,arg); を fscanf(hFile, lpFormat, va_arg(arg,int*)); とはできるんじゃないかと思いますが、 これだと(可変引数にするには)lpFormat の% の切り出しが必要ですね^^;
お礼
BLUEPIXYさん、ありがとうございます。 >fscanf(hFile, lpFormat, va_arg(arg,int*)); で int 値が正しく読込めました。 ただ、可変引数に対応させるより std::fstream 使ったほうがスマートだと納得できましたので、fstream をそのまま使うか、継承して使おうと思います。
- MrBan
- ベストアンサー率53% (331/615)
検証して無くて申し訳ないですが、 仮にvfscanfがあったとしても多分va_listがcdeclcall前提なので、 クラスの非スタティックなメンバ関数(thiscall)だとスタックの調整でうまく動かない可能性があるはず。 スタックの位置はあってますか。 一度、クラス以外のグローバル関数でも試してみてもらえますか。 それでうまくいくようなら、自力でスタックごにょごにょしないと、 クラスのメンバ関数ではうまくいかないと思われます。
お礼
MrBanさん、ご回答ありがとうございます。 >クラス以外のグローバル関数でも試してみても >らえますか 以下のように試しましたが、結果は同じでした。 // グローバル関数として定義 void Read(FILE* hFile,LPCSTR lpFormat,...){ va_list arg; // (2) va_start(arg,lpFormat); // (3) fscanf(hFile,lpFormat,arg); va_end(arg); } // Test5.datの内容 5 // 実験 void main(){ FILE* hFile = fopen("Test5.dat","r"); int i=0; // (1) Read(hFile,"%d",&i); // (4) printf("i = %d",i); } // 結果 i = 0 >スタックの位置はあってますか 上記(1)~(4)の各行の直後で、arg と i へのポインタが指すアドレスを調べると以下のようになりました。 (1)iのアドレス 0x0012fdf4 (2)argのアドレス 0xcccccccc (3)argのアドレス 0x0012fd20 (4)iのアドレス 0x0012fdf4 arg と i がメモリ上の同じアドレスを指すようにできれば、i にファイルからの読取値が正しく取得できるかどうか試してみたいと思います。 検討違いのことを試しているようでしたら、ご指摘いただけると幸いです。
補足
自己レスです。 >arg と i がメモリ上の同じアドレスを指すようにできれば、i にファイルからの >読取値が正しく取得できるかどうか試してみたいと思います。 そもそも arg と i は別々の変数ですから同じアドレスを指すことは不可能ですよね。試したかったのは、va_listが内部で管理している可変個引数へのポインタ(どのように管理しているかは知りませんが)が、i のアドレスを指すように調整することです。 fscanf() をクラスのメンバ関数でラップした場合に動作しない原因が、スタックの問題だとすると、vfprintf() を同じようにクラスのメンバ関数でラップ(メンバ関数内でva_listを宣言・使用)した場合に動作する理由がわかりません。
- επιστημη(@episteme)
- ベストアンサー率46% (546/1184)
> fscanf(this->hFile,lpFormat,arg); 関数 vfscanf があるなら、これに取り替えれば動作するでしょうね。
お礼
epistemeさん、アドバイスありがごうございます。 残念ながら<stdio.h>に vfscanf はありませんでした。 vfprintf があるなら vfscanf もあるだろうと思ったのですが。。。 ワイド文字版の fwscanf を試しましたが、やはり値を読込むことができませんでした。 ラップせずに直接 int i; vfscanf(hFile,"%d",&i); とすれば問題なく i に値が取得できるのですが。
関連するQ&A
- fscanf関数について
-------------------------------------------------- #include<stdio.h> #include<stdlib.h> int main() { FILE*fp; int ch,dt; char ss[80]; if((fp=fopen("bbb.txt","w"))==NULL){ printf("出力ファイルをオープンできません.\n"); exit(1); } fprintf(fp,"%c",'A'); fprintf(fp,"%s\n","abcdeABCDE"); fprintf(fp,"%d\n",1234); fclose(fp); if((fp=fopen("bbb.txt","r"))==NULL){ printf("入力ファイルをオープンできません.\n"); exit(1); } ch=fgetc(fp); printf("ch=%c\n",ch); fscanf(fp,"%s",ss); printf("ss=%s\n",ss); fscanf(fp,"%d",&dt); printf("dt=%d\n",dt); fclose(fp); return 0; } -------------------------------------------------- 以上のプログラムで、プログラムの通り「bbb.txt」は、 AabcdeABCDE 1234 となっております。 そこで疑問なのですが、「ch=fgetc(fp);」は1文字読み込みなので、'A'だけと分かるのですが、「fscanf(fp,"%s",ss);」はfpからの読み込みで何故、 AabcdeABCDE 1234 の全部を読み込まず、'A'を抜かした、「abcdeABCDE」だけを読み込んでくれるのか? 後、「fscanf(fp,"%d",&dt);」は何故「AabcdeABCDE」を抜かした、「1234」だけを読み込んでくれるのかが分かりません。 「fscanf(fp,"%d",&dt);」については数値だけを読み込んでくれるのかと思い、 ch=fgetc(fp); printf("ch=%c\n",ch); fscanf(fp,"%s",ss); printf("ss=%s\n",ss); の部分を無くせば、「1234」だけを読み込んでくれるのかと思ったのですが、数値は正しく表示されません。 以上教えていただければ嬉しいです。
- ベストアンサー
- C・C++・C#
- fscanfの使い方
現在C言語の勉強をしているのですが、ファイル入力のfscanfの使い方がいまいちわかりません。 テキストファイル「TEST4K01.txt」には 「A01MATSUMOTO 090075100」が入ってるのですが、それぞれ構造体に直接振り分けて格納したい為fscan関数を使って下のソースを書いたのですがコンパイルするといつも以上終了してしまいます。大変申し訳ないのですが、誰か助言を御願いします。 #include<stdio.h> #include<stdlib.h> struct score { char clas_i; char num_i; char name[10]; int eigo_i; int sugaku_i; int kokugo_i; }; FILE *ifp; int main(void) { struct score dt; if((ifp = fopen("TEST4K01.txt", "r")) == NULL){ printf("ファイルエラー\n"); exit(1); } fscanf(ifp, "%1c%2d%10c%3d%3d%3d\n", &dt.clas_i, &dt.num_i, &dt.name, &dt.eigo_i, &dt.sugaku_i, &dt.kokugo_i); printf("%s", dt.clas_i); printf("%d", dt.num_i); printf("%s", dt.name); printf("%d", dt.eigo_i); printf("%d", dt.sugaku_i); printf("%d", dt.kokugo_i); fclose(ifp); return 0; }
- ベストアンサー
- C・C++・C#
- fscanfについて
教えてgooを参考にしましたが、fscanfを使ってテキストファイルの任意の行のデータを読み込みたいのですがうまくいきません。 テキストファイルは 0 1 1 0 0 1 0 1 1 0 0 1 0 1 0 0 0 1 0 1 1 といった感じの2進数で3行以上あります。 下に自分が作ったソースがあります。 どこがいけないのか、どういった手法があるのかを詳しく教えていただけたら幸いです。よろしくお願いします。 int gene[10]; FILE* f=fopen("Gene.txt","r"); while(fscanf(f,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", &gene[0],&gene[1],&gene[2],&gene3],&gene [4],&gene[5],&gene[6],&gene[7],&gene [8],&gene[9])!=EOF) n++; fclose(f);
- ベストアンサー
- C・C++・C#
- fscanf()
fcanf()がうまくいきません。 画面には、42640888のような数値が表示されます。 どこが間違ってるか教えてください。 (test.txtに23と書いてあるとします。) #include<stdio.h> int main(void) { FILE *fp; FILE *fp1; char str[10]; int a; fp=fopen("test.txt","r") fp1=fopen("a.txt","w") while(!feof(fp)){ fgets(str,8,fp); fputs(str,fp1); } fscanf(fp,"%d",&a); printf("%d",a); fclose(fp); fclose(fp1); return 0; }
- ベストアンサー
- C・C++・C#
- 256*256の読み込み、fscanfではどうも、、、
256*256の行列データファイルを読み込みたいのですが、 fscanf(file_i,"%lf,%lf,%lf",&d_value1,&d_value2,&d_value3); while(!feof(file_i)){ と256列分書くのは少ししんどいなぁと思いまして質問します。 なにか具体的な方法を教えていただければ幸いです。ちなみに読み込んだデータはd_data[256][256]の中に入れることができたらいいなぁと思っています。 よろしくお願いします。
- 締切済み
- C・C++・C#
- 汎用のファイル読み取り関数について
ファイルデータを読み取る関数を作成しています. Data *ReadFile (const char *name, Data *data, const char *format, ...) というプロトタイプを考えます. ここでDataはプログラマが自由に定義できる構造体です. この構造体の一例を示すと, typedef struct { int id; /* ID番号 */ char name[32]; /* 氏名 */ char sex; /* 性別 */ int age; /* 年齢 */ char addr[64]; /* 住所 */ } Data; という具合です. 読み取るファイルの書式はプログラムの種類によって 異なりますが, fopen関数やfclose関数を使用するといった 手順は全く同一です. そこで上記のような汎用的な ファイル読み取り関数を書こうと思ったのですが 読み取り部分fscanfの処理をどのように行えばよいか 分かりません. formatで指定する書式は'%'を区切りとする複数の文字列に 分け(例えばformat="%s %d"であればbuffer="%s \0%d\0"), char型のポインタ配列bpで参照できます. 可変引数の部分で 構造体のメンバを指定できるようにしたいのですが... ちなみに呼出側では Data *data; data = ReadFile ("a.txt", data, "%d %s %c %d %s", data->id, ...); というようにしたいと考えています. ソースを以下に示します. どなたかお力をお貸しくださいませ. Data *ReadFile (const char *name, Data *data, const char *format, ...){ FILE *stream; size_t data_size = 1024, buffer_size = 128, bp_size = 16; short i, j, k; char *buffer; char **bp; /* buffer pointer */ va_list ap; /* argument pointer */ (省略) va_start (ap, format); i = 0; k = 0; while (!feof (stream)) { fscanf (stream, bp[i], va_arg (ap, ????)); (省略) } va_end (ap); }
- ベストアンサー
- C・C++・C#
- fscanf ファイルから数を読み込む。
ファイルから数を読み込むと 4201696 4201696 4201696 と、sample.txtにない数が表示されます。 sample.txtの中身は、2から6の数です。 sample.txtの中身は画像に添付しました。 以下は実行したプログラムです。 #include<stdio.h> #include <assert.h> int main(void){ FILE *fp; int a,b,i; if((fp=fopen("sample.txt","a"))==NULL){ printf("fileopen error\n"); } printf("整数を入力してください。"); scanf("%d",&a); fprintf(fp,"%3d\n",a); printf("整数を入力してください。"); scanf("%d",&a); fprintf(fp,"%3d\n",a); i=0; while(i<3){ fscanf(fp, "%d",&b); assert(b>2); printf("%3d\n",b); i++; } return(0);} 2から6の間の数が表示されるよう、指摘をおねがいします。
- ベストアンサー
- 情報工学
- fscanf関数のscanf集合を使う時の不具合
こんにちは。 現在、C言語でプログラミングをしています。 最近気が付いたのですが、fgetc、fputc、fgets、fputs、fprintfといった関数は、 呼び出されるたびにファイルの現在位置を進めるのですが、 fscanf関数において、scanf集合を使った場合だけは、 何故かいつもファイルの先頭からスキャンを開始するようです。 例えば、適当なテキストファイルtest.txtを作成し、 ------------------------------------ FILE *fp; char str[80]; fp=fopen("test.txt", "r"); while(!feof(fp)){ fscanf(fp, "%[a-zA-Z]", str) puts(str); } ------------------------------------ といったコードブロックを試してみると、永遠にforループが終了せず、 最初の英単語が何回も画面に出力されます。 このコードブロックの目的は、test.txtから、英単語だけを取り出して、順番に出力していく事です。 上のコードブロックで、whileループの最後に、fseek(fp, srtlen(str), SEEK_CUR); といったコードを挿入し、ファイルの現在位置を手動で進めてみたのですが、 どうも上手く行きませんでした。 scanf集合は便利なので、fscanf関数で使っていきたいのですが、こういった不具合があるので困っています。 上のコードブロックを上手く動作させる方法だけでもいいので、何かアドバイスを頂きたいと思います。 では、よろしくお願い致します。
- ベストアンサー
- C・C++・C#
- fscanfの書式について
fscanfの書式について a[n][n]が、 a11,a12,a13---a1n, a21,----------a2n, . . an1,----------ann, という風に並んでいるファイルを読み込む場合、どのように記述すれば良いのでしょうか? 例えばFortranでは、 DO I=1,N READ(11,'(N(F8.4,A1))')(A(I,J),KAMA, I=1,N) END DO と書けば良いのですが、Cでの書き方が分りません。 元々Fortranを使いでCは初心者です。 ちなみに、 for(i=0;i<=n+1;i++){ for(j=0;j<=n+1;j++){ fscanf(fp,"%f7.6 %1s ",&a[i][j],kama); } } と書いて見たものの、駄目でした。 改行してしまうのだと思うのですが、避ける方法も、そもそも、それが出来るのかどうかも分りません。。 よろしくお願いいたします。
- ベストアンサー
- C・C++・C#
- C++でfscanf関数・fprintf関数を利用した成績処理のプログ
C++でfscanf関数・fprintf関数を利用した成績処理のプログラムを作成しています。 #include "stdafx.h" void input(void); void calc(void); void edit(void); int gakusei=10,kamoku=5; /*学生数、科目数*/ char kamokumei[5][40]; /*科目名*/ char shimei[10][40]; /*氏名*/ int tennsuu[10][5]; /*点数*/ float heikin[10]; /*平均点*/ void main() /*メイン関数*/ { input(); calc (); edit (); } void input(void) /*データ入力(infile.d.txtから読み込む)*/ { FILE *fp; int n,k; fp=fopen("infile.d.txt","r"); if(fp==NULL) { printf("infile.d.txtが開けません\n"); } printf("infile.d.txt\n\n"); fscanf(fp,"%d %d",&gakusei,&kamoku); printf("%d %d\n",gakusei,kamoku); fscanf(fp,"%s",kamokumei); printf("%s\n",kamokumei); fscanf(fp,"%s",shimei); printf("%s\n",shimei); while(fscanf(fp,"%s",tennsuu)!=EOF) { printf("%s\n",tennsuu); } printf("\n\n"); fclose(fp); } void calc(void) /*各学生の平均点を計算、平均点の優秀者(80点以上)及び不合格者(60点未満)を摘出*/ { int n,k; float sum; for(n=0; n<gakusei; n++) { sum=0; for(k=0; k<kamoku; k++) { sum+=(float)tennsuu[n][k]; } heikin[n]=sum/kamoku; } } void edit(void) /*平均点、優秀者及び不合格者の氏名を付加した成績表を出力(outfile.d.txtに書き込み)*/ { int n; FILE *seiseki; seiseki=fopen("outfile.d.txt","w"); printf("outfile.d.txt\n\n"); fprintf(seiseki,"氏名 %s 平均\n",kamokumei); printf("氏名 %s 平均\n",kamokumei); fprintf(seiseki,"%s\n",shimei); printf("%s\n",shimei); for(n=0; n<gakusei; n++) { fprintf(seiseki,"%s",tennsuu); printf("%s\n",tennsuu); } printf("\n"); fprintf(seiseki,"平均点優秀者\n"); printf("平均点優秀者\n"); fprintf(seiseki,"平均点不合格者"); printf("平均点不合格者\n"); fclose(seiseki); } 添付した画像のoutfile.d.txtのようなフォーマットで出力したいのですが、氏名がうまく出力できません。また、点数もinfile.d.txtの最後の行しか読み込んでくれません。 どのように書き換えればよいのでしょうか? よろしくお願いします。 *infile.d.txtについて 10 5 ⇒学生数と科目数 材料力学 熱力学 ・・・ ⇒科目名 石川 川上 佐藤 ・・・ ⇒学生の氏名 78 95 75 86 ・・・ ⇒各科目の点数(1行につき5科目×2人分の点数が書き込まれています。)
- ベストアンサー
- C・C++・C#
お礼
>fscanfはvfscanfと引数が違ってva_listを認識しないので、 >fscanfにそのままva_listを渡してもうまく動きません。 知りませんでした。 >二つ以上の引数を渡しても、正しく出力しますか? 二つ以上の引数を渡してみました。 // 定義 VOID CTextFileIO::Write(LPCSTR lpFormat,...) { va_list arg; va_start(arg,lpFormat); vfprintf(m_hFile,lpFormat,arg); va_end(arg); } // 実験 Write("%c %d %s",'Y',5000,"String"); // 結果 Y 5000 String 二個目以降の値も取れているようです。 ># あえてC言語風で自作する理由は何ですか? std::iostreamに不慣れなのが理由ですが、Mustでないので少し学んでfstreamで代替しようと思います。 サンプルコードありがとうございます。試してみると正しくint値が取れました。 ご丁寧にありがとうございます。