ページ 11

配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 21:00
by bonbo
配列を用いた構造体をfwriteでバイナリファイルに書き込み、それを読み込もうと思い、以下のようなプログラムをつくったのですが、うまく動作してくれません(読み込めていないのか書き込めていないのかわからないが、すべて0になっている)。どうすればいいのでしょうか。

コード:

#include <iostream>
using namespace std;
const int testmax=10;
typedef struct {
	int a1,a2;
}test_t;
test_t test[testmax];
inline void Save(test_t test[]){
	FILE *fp ;
	fp = fopen( "test.dat" , "wb" ) ;
	if( fp == NULL )
		return ;
	fwrite(test,sizeof(test) , 1 , fp ) ;
	// ファイルを閉じる
	fclose( fp ) ;
}
inline void Load(test_t test[]){
	FILE *fp ;
	fp = fopen("test.dat","rb") ;
	if(fp==NULL){
	}
	else
	{
		//データの読み込み
		fread(test, sizeof(test) , 1 , fp ) ;
		// ファイルを閉じる
		fclose( fp ) ;
	}
}
int main(void){
	for(int i=0;i<testmax;i++){
		test[i].a1=i;
		test[i].a2=10-i;
	}
	Save(test);
	for(int i=0;i<testmax;i++){
		test[i].a1=0;
		test[i].a2=0;
	}
	Load(test);
	for(int i=0;i<testmax;i++)
		cout<<test[i].a2<<"\n";
	cin>>test[0].a2;
	return 0;
}

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 21:07
by box
bonbo さんが書きました:

コード:

	fwrite(test,sizeof(test) , 1 , fp ) ;
		fread(test, sizeof(test) , 1 , fp ) ;
fwrite() と fread() の第3引数が1である理由を教えてください。

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 21:23
by box
第3引数がどうのこうの言う前に、コンパイルが通らないっぽいですね。

# 当方だけかな?きっとそうなんでしょう。

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 21:24
by みけCAT
Save関数とLoad関数の中のtestはtest_t型の変数へのポインタであり、
サイズは(Ideoneでは)4バイトになります。

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 22:56
by ISLe
fread/fwriteで読み書きしているのは、関数の引数であるtest_tへのポインタ型の一時変数testです。
なので外部宣言されたtest配列には影響しません。


(訂正)
#思いっきり勘違い。<俺
Load/Save関数内のtestは、関数の引数であるtest_tへのポインタ型の一時変数testが対象です。
なのでsizeofではtest_tへのポインタ型のサイズが返ります。
というのは、みけCATさんが書いているとおり。

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 23:00
by bonbo
>boxさん
ありがとうございます。
>fwrite() と fread() の第3引数が1である理由を教えてください。
ミスです…というかよくわかっていませんでした…
この場合、a1の要素10こ、とa2の要素10こで20とすべきだ。ということでしょうか…間違っていたらません…
>みけCATさん
ありがとうございます。
>Save関数とLoad関数の中のtestはtest_t型の変数へのポインタであり、
サイズは(Ideoneでは)4バイトになります。
つまり関数内ではバイト数が変わるので、そのままではsizeof()の中には入れられないということでしょうか…

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 23:09
by bonbo
一応第三引数を増やしたら書き込めるようにはなりました。いろいろ実験してみたところfwriteの仕組みがわかったので解決にしておきます。みなさんありがとうございました。また何かありましたらよろしくお願いします。

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 23:13
by bonbo
忘れてました…

コード:

#include <iostream>
using namespace std;
const int testmax=10;
typedef struct {
	int a1,a2;
}test_t;
test_t test[testmax];
inline void Save(test_t test[]){
	FILE *fp ;
	fp = fopen( "test.dat" , "wb" ) ;
	if( fp == NULL )
		return ;
	fwrite(test,sizeof(test) , 20 , fp ) ;
	// ファイルを閉じる
	fclose( fp ) ;
}
inline void Load(test_t test[]){
	FILE *fp ;
	// オープンできなかったらファイルが無いとみなし
	//消す
	fp = fopen("test.dat","rb") ;
	if(fp==NULL){
	}
	else
	{
		//データの読み込み
		fread(test, sizeof(test) , 20 , fp ) ;
		// ファイルを閉じる
		fclose( fp ) ;
	}
}
int main(void){
	for(int i=0;i<testmax;i++){
		test[i].a1=i;
		test[i].a2=10-i;
	}
	Save(test);
	for(int i=0;i<testmax;i++){
		test[i].a1=0;
		test[i].a2=0;
	}
	Load(test);
	for(int i=0;i<testmax;i++)
		cout<<test[i].a2<<"\n";
	for(int i=0;i<testmax;i++)
		cout<<test[i].a1<<"\n";
	cin>>test[0].a2;
	return 0;
}

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 23:16
by へにっくす
bonbo さんが書きました:つまり関数内ではバイト数が変わるので、そのままではsizeof()の中には入れられないということでしょうか…
グローバルの変数名と関数の引数の名前が同じだから分かりにくいのです。
グローバル変数名 test を g_test とかにしてみましょう。
また配列を関数の引数として渡すなら、個数も渡すべきでしょう。

コード:

// 略
test_t g_test[testmax]; // 変数名はなるべく同じ名前にしない!!
inline void Save(test_t test[], int kosu)
{
	// 略
	
	// 変数名でなく型名をsizeofする
	fwrite(test,sizeof(test_t), kosu, fp );
	// 略
}

inline void Load(test_t test[], int kosu)
{
	// 略
	
	// 変数名でなく型名をsizeofする
	fread(test,sizeof(test_t), kosu, fp );
	// 略
}

// main関数内では
// Save(g_test, testmax);
// Load(g_test, testmax);
オフトピック
bonbo さんが書きました:いろいろ実験してみたところfwriteの仕組みがわかったので解決にしておきます
fread/fwriteの第3引数を20にしてて、sizeofに渡すのも変数のままなので、本当に理解しているの?とこちらでは思うのですが。

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 23:19
by box
bonbo さんが書きました:

コード:

	fwrite(test,sizeof(test) , 20 , fp ) ;
		fread(test, sizeof(test) , 20 , fp ) ;
第3引数を20にした理由は何ですか?

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 23:35
by bonbo
>へにっくすさん
ありがとうございます。
>変数名はなるべく同じ名前にしない!!
>変数名でなく型名をsizeofする
>また配列を関数の引数として渡すなら、個数も渡すべきでしょう。
なるほど…
全然理解してないですね…親切なアドバイスありがとうございます…
>boxさん
>第3引数を20にした理由は何ですか?
この場合、a1の要素10こ、とa2の要素10こで20とすべきだ。と思ったからです。間違っていたら指摘していただけると嬉しいです

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 23:44
by box
bonbo さんが書きました: この場合、a1の要素10こ、とa2の要素10こで20とすべきだ。と思ったからです。間違っていたら指摘していただけると嬉しいです
構造体のメンバーが増えたら、その都度30にしたり40にしたりするんですか?
何だか効率的ではないような気がします。
test_t 型(メンバーの構成は知ったことではない)が testmax 個あると考える方が、
より自然ではないのかな、という気がします。配列test[] の定義がそういう風になってますよね。
その配列をまるっと読み書きするのですから、test[] の先頭アドレスから、
test_t 型の大きさの要素を testmax 個読み書きする、というコードの方が、
改変が少なくてすむような気がします。

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 23:51
by bonbo
boxさん
>test_t 型(メンバーの内容は変わりうる)が testmax 個あると考える方が、
より自然ではないのかな、という気がします。
つまり…メンバーの数をmenbernumとでもおいて
20のところをtestmax*menbernumとすればいいということでしょうか。
そのほうがきれいですね。
アドバイスありがとうございます。

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月19日(日) 23:55
by box
bonbo さんが書きました: つまり…メンバーの数をmenbernumとでもおいて
20のところをtestmax*menbernumとすればいいということでしょうか。
membernum という変数を導入する必要は、ありません。
sizeof(test_t) という、構造体の大きさが testmax 個ある、ということです。
仮に、構造体の中に、double型とかchar型とかのメンバーを追加したとしても、
「え~とint型が2個でdouble型が何個でchar型が何バイトだから…」などという計算を自分でしなくても、
構造体そのもの大きさはいつだって sizeof(test_t) で求まる、というのがポイントです。

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月20日(月) 00:09
by bonbo
>boxさん
>sizeof(test_t) という、構造体の大きさが testmax 個ある
fwrite(test,sizeof(test_t) , sizeof(test_t)*testmax, fp ) ;
でしょうか…

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月20日(月) 00:10
by bonbo
>仮に、構造体の中に、double型とかchar型とかのメンバーを追加したとしても、
「え~とint型が2個でdouble型が何個でchar型が何バイトだから…」などという計算を自分でしなくても、
構造体そのもの大きさはいつだって sizeof(test_t) で求まる、というのがポイントです。
なるほど!ありがとうございます!

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月20日(月) 06:06
by box
bonbo さんが書きました: >sizeof(test_t) という、構造体の大きさが testmax 個ある
fwrite(test,sizeof(test_t) , sizeof(test_t)*testmax, fp ) ;
でしょうか…
それだと、
sizeof(test_t) という大きさの領域が
sizeof(test_t)*testmax 個ある、
ということになってしまいそうです。

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月20日(月) 15:40
by ISLe
box さんが書きました:「え~とint型が2個でdouble型が何個でchar型が何バイトだから…」などという計算を自分でしなくても、
構造体そのもの大きさはいつだって sizeof(test_t) で求まる、というのがポイントです。
メンバのサイズの合計と構造体のサイズが等しいとは限りませんしね。

Re: 配列を用いた構造体をfwriteで書き込みたい

Posted: 2012年8月24日(金) 00:20
by かずま
Save や Load の中に 20 なんて書いてはダメです。次のようにすれば、
配列の要素数が変わっても、testmax の値の変更一か所だけで済みます。

コード:

#include <iostream>
#include <cstdio>

using namespace std;

const int testmax = 10;

struct test_t {
    int a1, a2;
};

test_t test[testmax];

inline void Save(const test_t *test, size_t size)
{
    FILE *fp = fopen("test.dat", "wb");
    if (fp == NULL) return;
    fwrite(test, sizeof(test_t), size, fp);
    fclose(fp);
}

inline void Load(test_t *test, size_t size)
{
    FILE *fp = fopen("test.dat", "rb");
    if (fp == NULL) return;
    fread(test, sizeof(test_t), size, fp);
    fclose(fp);
}

int main()
{
    for (int i = 0; i < testmax; i++) {
        test[i].a1 = i;
        test[i].a2 = 10-i;
    }
    Save(test, testmax);
    for (int i = 0; i < testmax; i++) {
        test[i].a1 = 0;
        test[i].a2 = 0;
    }
    Load(test, testmax);
    for (int i = 0; i < testmax; i++)
        cout << test[i].a2 << "\n";
    for (int i = 0; i < testmax; i++)
        cout << test[i].a1 << "\n";
    return 0;
}
さらに、C++ だったら、FILE よりも <fstream> でしょう。

コード:

#include <fstream>

using namespace std;

inline void Save(const test_t *test, size_t size)
{
    ofstream ofs("test.dat", ios::binary);
    if (ofs) ofs.write((const char *)test, sizeof(test_t) * size);
}

inline void Load(test_t *test, size_t size)
{
    ifstream ifs("test.dat", ios::binary);
    if (ifs) ifs.read((char *)test, sizeof(test_t) * size);
}