構造体メンバへの格納法

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
kopi
記事: 19
登録日時: 6年前

構造体メンバへの格納法

#1

投稿記事 by kopi » 6年前

こんにちは。またお世話になります。

コード:

typedef struct node_tag{
	int a;
	double b;
	struct node_tag *next;
}node;
例えば上のような構造体を宣言し、ファイルからデータを読み込み格納したい場合どのようにすればよいですか?

構造体で配列を宣言し、格納する方法はネットにもあったのですが、ポインタの場合はどうすればいのわからず・・・。
とりあえず下のように書いてみたのですが。

コード:

node *p;
FILE *fp;

fp = fopen("filename.txt", "r");
fscanf(fp, "%d %f", &p->a, &p->b); // pのメモリ領域は確保済みとします
ご教授お願いいたします。

環境
OS : windows 7 64bit
コンパイラ : Borland C++ Compiler 5.5.1

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 10年前
住所: 東海地方
連絡を取る:

Re: 構造体メンバへの格納法

#2

投稿記事 by softya(ソフト屋) » 6年前

ポインタの場合は、自分でバイナリのデータ構造に展開するしかありません。
要素数も保存して配列に変えて保存するといったイメージです。実際には一要素ごとにファイルに書きだすのですが。
※ 自分の作るファイルですから自由に設計してもらってかまいません。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 10年前
住所: 東海地方
連絡を取る:

Re: 構造体メンバへの格納法

#3

投稿記事 by softya(ソフト屋) » 6年前

あれ? 読み込み元はテキストファイルなのでしょうか?

【補足】
テキストファイルから読むなら、一旦構造体の変数に格納してリスト用のメモリをmallocして内容をコピー、前のリストの最後にポインタを継ぎ足す感じです(要はリスト構造の作り方)。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

kopi
記事: 19
登録日時: 6年前

Re: 構造体メンバへの格納法

#4

投稿記事 by kopi » 6年前

softya(ソフト屋) さんが書きました:あれ? 読み込み元はテキストファイルなのでしょうか?
はい、テキストファイルを想定しています。
データ数が仮に10000あったとすると、配列でその分を確保するのは困難かなーと思い、リスト構造をうまく使ってできないものかと考えたわけです。
しかし、この辺がまだはっきり理解できていないので、
データ数がこのように多い場合、どのようにして格納していけばよいのか、ご教授いただけないでしょうか。
オフトピック
実は今悩んでいるこの問題は、過去にこの掲示板で課題丸投げされたもので、
自分の練習がてら興味本位で解こうと思ってみたら、初っ端から躓いてしまったというわけです。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 10年前
住所: 東海地方
連絡を取る:

Re: 構造体メンバへの格納法

#5

投稿記事 by softya(ソフト屋) » 6年前

補足で書きましたが、ポインタによるリスト構造が理解できるならさほど難しくはないですよ。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

kopi
記事: 19
登録日時: 6年前

Re: 構造体メンバへの格納法

#6

投稿記事 by kopi » 6年前

まだまだ改善の余地は大いにあると思いますが、ファイルからの数値を読むことができました。

コード:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

#define N 10

typedef struct node_tag{
	int id[N];
	int sub_a[N];
	double sub_b[N], sub_c[N];
	struct node_tag *next;
}node;


/* エラー表示用 */
void error(char *str)
{
	printf("%s", str);
	exit(1);
}

/* メモリ確保関数 */
node *getmemory(void)
{
	node *p;

	p = (node *)malloc(sizeof(node));
	if(p == NULL)
		error("Not enough memory");

	return p;
}

/* ここは気にしないでください
node *gentree(node *p, int n)
{
	if(p == NULL)
		p = getmemory();
		strcpy(p->id, n)
	else if()
		p->left = gentree(p->left, n)
}
*/

int main(void)
{
	node *p, *head, *old;
	FILE *fp;
	char filename[41];
	int i;
	
	printf("Enter filename : ");
	scanf("%40s", filename);

	fp = fopen(filename, "r");
	if(fp == NULL)
		error("File not find");	

	
	head = getmemory();	// ダミーノード
	old = head;
	while( p = getmemory(), fscanf(fp, "%d %d %lf %lf", p->id, p->sub_a, p->sub_b, p->sub_c) != EOF){
		old->next = p;
		old = p;	
	}
	old->next = NULL;

	fclose(fp);
	
	p = head->next;
	while(p != NULL){
		printf("%d %d %.3f %.3f\n", *p->id, *p->sub_a, *p->sub_b, *p->sub_c);
		p = p->next;
	}


	return 0;
}

コメントアウトしてある関数は気にしないでください。何をしようとしているのかばれてしまうと思いますが(笑)

結果を出力した際、一つ気になることがあります。
今現在データの行数が1000で、各行に4つの要素(int型, int型, double型, double型の順)を持つデータを用意しています。
ポインタがNULLになるまでループするはずなので、出力結果としては1行目のデータから1000行目までが一気に出力されるのかと思いきや、
300行程度しか出力されません。

その部分の結果と元のデータは同じ値なので、一応リスト構造はできていると思うのですが、ご指摘していただけないでしょうか。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 10年前
住所: 東海地方
連絡を取る:

Re: 構造体メンバへの格納法

#7

投稿記事 by softya(ソフト屋) » 6年前

まず、
int id[N];
int sub_a[N];
double sub_b[N], sub_c[N];
は配列にするのが意味不明です。
fscnafならポインタを渡せば良いので、 int id;で宣言して&演算子( &(p->id) )で良いはずですが。

それと
リダイレクトでファイル出力してみましたが入力しただけファイルに出力されているようです。
入力でエラーが有った場合は、その限りではありません。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

kopi
記事: 19
登録日時: 6年前

Re: 構造体メンバへの格納法

#8

投稿記事 by kopi » 6年前

softya(ソフト屋) さんが書きました:まず、
int id[N];
int sub_a[N];
double sub_b[N], sub_c[N];
は配列にするのが意味不明です。
fscnafならポインタを渡せば良いので、 int id;で宣言して&演算子( &(p->id) )で良いはずですが。
なるほど、私がfscanfの仕組みを理解せずに使っていたことが良く分かりました。ありがとうございます。
softya(ソフト屋) さんが書きました: それと
リダイレクトでファイル出力してみましたが入力しただけファイルに出力されているようです。
入力でエラーが有った場合は、その限りではありません。
つまり、何らかのエラーがあるため、本来入力したい分のデータ数が入力できていないということですか?
もしそうだとしたら、どの辺が原因なのでしょうか。

shirokosi
記事: 2
登録日時: 6年前

Re: 構造体メンバへの格納法

#9

投稿記事 by shirokosi » 6年前

はじめまして。
参考になりそうなコードを書いてみました。
よろしければお使いください。

環境  
 OS : Windows 7 64bit
 コンパイラ名 : VC++ 2012

コード:

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//#include <conio.h>

// 次のnodeへのポインタを持つ構造体
struct node_t {
	int id;
	int sub_a;
	double sub_b, sub_c;
	node_t *next; // 次のnode_tへのポインタ
};

// nodeの先頭と一番後ろを保持する構造体
// nodeの管理のために必要になる構造体
struct node_manager_t {
	node_t *head; // 先頭
	node_t *tail; // 末尾
};

node_manager_t* create_node_manager();
void delete_node_manager(node_manager_t* nmt);
int  count_node_manager(node_manager_t* nmt);
bool input_node(node_manager_t* nmt, node_t *node);
void nmt_print(node_manager_t* nmt);

// node 管理用構造体用にメモリを確保
node_manager_t* create_node_manager()
{
	node_manager_t *tmp;
	tmp = (node_manager_t*) malloc(sizeof(node_manager_t));
	if( tmp == NULL )
		return tmp;
	tmp->head = NULL;
	tmp->tail = NULL;
	return tmp;
}

// mallocで確保したものは開放する。
void delete_node_manager(node_manager_t* nmt)
{
	if( nmt == NULL ) return; // 入力値検査

	// まずnode_tを消す
	for( node_t *p = nmt->head;
		 p!=NULL; )
	{
		node_t *temp = p; // いつ消すか・・・?
		p = p->next; // 次のデータへ移動
		free( temp );// ここで消す
	}
	
	// node_manager_tを消す
	free( nmt );
}

// ノードの個数を数えます
int count_node_manager(node_manager_t* nmt)
{
	if( nmt == NULL ) return 0;// 入力値検査

	int counter=0;
	for( node_t *p = nmt->head;
		 p!=NULL; 
		 p=p->next )
	{
		counter++;
	}

	return counter;
}

// ノードの一番後ろにデータを突っ込みます
bool input_node(node_manager_t* nmt, node_t *node)
{
	if( nmt == NULL ) return false; // 入力値検査
	if( node == NULL ) return false; // 入力値検査

	node_t *new_node = (node_t*)malloc(sizeof(node_t));
	if( new_node == NULL )
		return false;

	// 単純にコピー
	new_node->id = node->id;
	new_node->sub_a = node->sub_a;
	new_node->sub_b = node->sub_b;
	new_node->sub_c = node->sub_c;
	new_node->next = NULL;

	// 初回入力
	if( nmt->head == NULL && 
		nmt->tail == NULL) 
	{
		nmt->head = new_node;
		nmt->tail = new_node;
	} 
	else if( nmt->head && nmt->tail )
	{
		nmt->tail->next = new_node;
		nmt->tail = new_node;
	}
	else // この条件にはならないはず。けど一応書く
	{
		free( new_node );
		return false;
	}

	return true;
}

// データを表示してみる
void nmt_print(node_manager_t* nmt)
{
	if( nmt == NULL ) return;
	int node_num = count_node_manager(nmt);
	printf("ノードの数:%d\n", node_num);
	printf("表示開始!\n");
	for( node_t *p = nmt->head;
		 p!=NULL; 
		 p=p->next )
	{
		printf("%d %d %.3f %.3f\n", p->id, p->sub_a, p->sub_b, p->sub_c );
		// _getch();
	}

	printf("表示おわり!\n");
}

/* エラー表示用 */
void error(char *str)
{
    printf("%s", str);
    exit(1);
}


node_manager_t *node_manager;

// テスト用のデータを作ります
void data_gen()
{
	FILE *fp;
	fp = fopen("test_data.txt", "w");
	
	for(int i=0; i<1000; i++) 
	{
		fprintf(fp, "%d %d %.3f %.3f\n", i, rand()%2000, (rand()%10000)*0.001, (rand()%10000)*0.001);
	}
	fclose(fp);
}

int main()
{
	//data_gen();
	node_manager = create_node_manager();
	if( node_manager == NULL )
		return EXIT_FAILURE;

	FILE *fp;
	char filename[41];
    
    printf("Enter filename : ");
    scanf("%40s", filename);

	fp = fopen(filename, "r");
    if(fp == NULL)
        error("File not find"); 

	while(1) {
		node_t tmp;
		int i = fscanf( fp, "%d%d%lf%lf", &(tmp.id), &(tmp.sub_a), &(tmp.sub_b), &(tmp.sub_c) );
		if( i < 4 ) break; // 入力数がおかしかったらとめる
		if( input_node(node_manager, &tmp) == false ) break; // 読み込んだものを入力
	}

	fclose(fp);

	nmt_print( node_manager );

	delete_node_manager( node_manager );

	//_getch();
}
 

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 10年前
住所: 東海地方
連絡を取る:

Re: 構造体メンバへの格納法

#10

投稿記事 by softya(ソフト屋) » 6年前

kopi さんが書きました: つまり、何らかのエラーがあるため、本来入力したい分のデータ数が入力できていないということですか?
もしそうだとしたら、どの辺が原因なのでしょうか。
fscanfは入力エラーの場合でも戻り値でEOFを返しますので、EOFで終了と判定するのは本来間違いです。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

kopi
記事: 19
登録日時: 6年前

Re: 構造体メンバへの格納法

#11

投稿記事 by kopi » 6年前

shirokosi さんが書きました: はじめまして。
参考になりそうなコードを書いてみました。
よろしければお使いください。
ありがとうございます。ぜひ参考にさせていただきます。
softya(ソフト屋) さんが書きました: fscanfは入力エラーの場合でも戻り値でEOFを返しますので、EOFで終了と判定するのは本来間違いです。
エラーでもファイルの終端でもEOFを返すのなら、ファイル終端とエラーを区別できればいいのでは、と考え

コード:

 while( p = getmemory(), fscanf(fp, "%d %d %lf %lf", p->id, p->sub_a, p->sub_b, p->sub_c) != EOF){
        old->next = p;
        old = p;    
    }
と書いていたところを、下のように書いてみたのですが

コード:

while(!feof(fp)){
		p = getmemory();	
		fscanf( fp, "%d %d %lf %lf", &(p->id), &(p->sub_a), &(p->sub_b), &(p->sub_c) );
		old->next = p;
		old = p;
	}
結果は相変わらず700行~1000行の間の300行程度しか読み込めないまま。
例えば、1行目から100行目まで読み込んでいるのであれば、101行目のデータがおかしいのかなと、なんとなく想像がつくのですが
スタートが700行目からになっている原因がよく分かりません。それまでのデータはどこへ・・・。

データであるtxtファイルを添付しておきますので、ご確認していただければ幸いです。
添付ファイル
num.txt
データファイル
(19.39 KiB) ダウンロード数: 28 回

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 10年前
住所: 東海地方
連絡を取る:

Re: 構造体メンバへの格納法

#12

投稿記事 by softya(ソフト屋) » 6年前

それはコンソールに最後の300行しか表示されていないのでは?
ファイルにリダイレクトされましたか?
xxx.exe > out.txt
ってやつです。
VC++だとプロジェクトのプロパティでデバッグの引数に「> out.txt」と書いて下さい。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

kopi
記事: 19
登録日時: 6年前

Re: 構造体メンバへの格納法

#13

投稿記事 by kopi » 6年前

softya(ソフト屋) さんが書きました:それはコンソールに最後の300行しか表示されていないのでは?
ファイルにリダイレクトされましたか?
xxx.exe > out.txt
ってやつです。
VC++だとプロジェクトのプロパティでデバッグの引数に「> out.txt」と書いて下さい。
ファイルに出力して試してみたところ、無事、全データを出力していることを確認できました。
コンソール上で表示する行数には制限等があるんですかね。。。

とりあえずは問題が解決いたしましたので、一旦は解決とさせていただきます。

まだまだ先はあるので、つまずき次第また質問させていただきます。
質問に答えていただきありがとうございました。

特に丁寧に説明していただいたsoftya(ソフト屋)さん、ありがとうございます。

閉鎖

“C言語何でも質問掲示板” へ戻る