ページ 11

線形リスト構造について。(2回目)

Posted: 2012年6月17日(日) 03:44
by すぱせる
名前(半角英字によるローマ字表記のみ)、身長[cm]、体重[kg]を入力し、BMI指数の小さい順に並べた線形リストを作成した上で、BMI指数の小さい順に
BMI指数、身長、体重、名前を出力するプログラムを作成せよ。
ただし、入力データ件数は不定、名前の1文字目に"0"が入力された時データ入力を終了する。

という内容の課題を以前質問させて頂きました。
みなさんのおかげでソースを理解するに至ったのですが、いざ自分で組んでみるとエラーが出てしまいます。

コード:

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

typedef struct field{
	char name[32];
	double h,w,bmi;//h:身長、w:体重
	struct field *next;
}person;

void add(person **plist, person *newstr);
void output(person *p);
void pfree(person *p);

int main(void){

	person *list,t;
	int i;

	list=NULL;
	i=0;

	while(1){	//入力件数が不定なのでループ内のbreakで終了させる

		printf("%d人目_",i++);
		printf("名前 > ");		fgets(t.name,sizeof(t.name),stdin);

		if(t.name[0]==0){		//名前の1文字目が0なら処理終了
			break;
		}
		printf("身長[cm] > ");	scanf("%lf",&t.h);
		printf("体重[kg] > ");	scanf("%lf",&t.w);

		t.h /= 100;	//身長の単位が[cm]なので[m]に直す
		t.w /= 100;
		t.bmi = t.w/(t.h*t.h);	//BMI指数を求める
		t.next = NULL;

		add(&list,&t);//線形リストに追加
	}
	output(list);	//線形リストを出力
	pfree(list);	//領域を解放

	return 0;
}

void add(person **plist,person *newstr){

	person *p,*q,*r;
	
	p = *plist;
	if(p == NULL){
		q = NULL;
	}
	while(p != NULL){
		if(newstr->bmi < p->bmi){	//BMI指数が自分より大きければ処理終了
			break;
		}else{
			q = p;
			p = p->next;
		}
		r = (person*)malloc(sizeof(person));
		*r = *newstr;
		r->next = p;
		
		if(q != NULL){
			q->next = r;
		}else{
			*plist = r;
		}
	}
}

void output(person *p){

	while(1){	//入力件数が不定なので、ループ内のbreakで終了させる
		if(p==NULL){	//ポインタがNULLなら処理終了
			break;
		}else{
			printf("BMI > %lf,",p->bmi);
			printf("身長[cm] > %lf,",p->h);
			printf("体重[kg] > %lf,",p->w);
			printf("名前 > %s.\n",p->name);
			p=p->next;	//次を指すアドレスを代入
		}
	}
}

void pfee(person *p){	//領域を解放する関数
	
	//NULLポインタを見つけ次第、
	//そこから先頭に向かって順に領域を解放していく。
	if(p->next!=NULL){
		pfree(p->next);	
	}
	free(p);
}
エラー内容は
1>kadai2-1.obj : error LNK2019: 未解決の外部シンボル "void __cdecl pfree(struct field *)" (?pfree@@YAXPAUfield@@@Z) が関数 _main で参照されました。
1>C:\Users\USER\documents\visual studio 2010\Projects\kadai2-1\Debug\kadai2-1.exe : fatal error LNK1120: 外部参照 1 が未解決です。
です。
初めて見たエラーです。どのようにすれば解決できるのでしょうか?
また、ソース内でおかしな点がありましたらご指摘お願いします。

Re: 線形リスト構造について。(2回目)

Posted: 2012年6月17日(日) 04:21
by box
すぱせる さんが書きました:

コード:

void pfee(person *p){	//領域を解放する関数
		pfree(p->next);	
使いたい関数の名前は
pfee
pfree
のどちらですか?

Re: 線形リスト構造について。(2回目)

Posted: 2012年6月17日(日) 04:56
by すぱせる
box さんが書きました:
すぱせる さんが書きました:

コード:

void pfee(person *p){	//領域を解放する関数
		pfree(p->next);	
使いたい関数の名前は
pfee
pfree
のどちらですか?
ご指摘ありがとうございます。
文字が抜けていました。pfreeを使います。
それも踏まえ、ところどころ直してみました。

コード:

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

typedef struct field{
	char name[32];
	double h,w,bmi;
	struct field *next;
}person;

void add(person **plist, person *newstr);
void output(person *p);
void pfree(person *p);

int main(void){

	person *list,t;
	int i;

	list=NULL;
	i=0;

	while(1){	//入力件数が不定なのでループ内のbreakで終了させる

		printf("%d人目_\n",i++);
		printf("名前 > ");scanf("%s",t.name)

		if(t.name[0]==0){		//名前の1文字目が0なら処理終了
			break;
		}
		printf("身長[cm] > ");	scanf("%lf",&t.h);
		printf("体重[kg] > ");	scanf("%lf",&t.w);
		printf("\n");

		t.h /= 100;	//身長の単位が[cm]なので[m]に直す
		t.bmi = t.w/(t.h*t.h);	//BMI指数を求める
		t.next = NULL;

		add(&list,&t);//線形リストに追加
	}
	output(list);	//線形リストを出力
	pfree(list);	//領域を解放

	return 0;
}

void add(person **plist,person *newstr){

	person *p,*q,*r;
	
	p = *plist;
	if(p == NULL){
		q = NULL;
	}
	while(p != NULL){
		if(newstr->bmi < p->bmi){	//BMI指数が自分より大きければ処理終了
			break;
		}else{
			q = p;
			p = p->next;
		}
		r = (person*)malloc(sizeof(person));
		*r = *newstr;
		r->next = p;
		
		if(q != NULL){
			q->next = r;
		}else{
			*plist = r;
		}
	}
}

void output(person *p){

	while(1){	//入力件数が不定なので、ループ内のbreakで終了させる
		if(p==NULL){	//ポインタがNULLなら処理終了
			break;
		}else{
			printf("BMI > %.1lf,",p->bmi);
			printf("身長[cm] > %.1lf,",p->h);
			printf("体重[kg] > %.1lf,",p->w);
			printf("名前 > %s.\n",p->name);
		}
		p=p->next;	//次を指すアドレスを代入
	}
}

void pfree(person *p){	//領域を解放する関数
	
	//NULLポインタを見つけ次第、
	//そこから先頭に向かって順に領域を解放していく。
	if(p->next!=NULL){
		pfree(p->next);	
	}
	free(p);
	p = NULL;
}
ですが、このソースだと0を入力しても入力処理が終わりません…。
ifの条件式は間違っていないように思えるのですが、これはおかしいのでしょうか。

Re: 線形リスト構造について。(2回目)

Posted: 2012年6月17日(日) 06:06
by box
すぱせる さんが書きました: ifの条件式は間違っていないように思えるのですが、これはおかしいのでしょうか。
私には、どう見ても間違っているように思えます。
すぱせる さんが書きました:

コード:

		if(t.name[0]==0){		//名前の1文字目が0なら処理終了
			break;
		}
数値の0で、本当にいいんでしょうか。

Re: 線形リスト構造について。(2回目)

Posted: 2012年6月17日(日) 13:50
by すぱせる
box さんが書きました:
すぱせる さんが書きました: ifの条件式は間違っていないように思えるのですが、これはおかしいのでしょうか。
私には、どう見ても間違っているように思えます。
すぱせる さんが書きました:

コード:

		if(t.name[0]==0){		//名前の1文字目が0なら処理終了
			break;
		}
数値の0で、本当にいいんでしょうか。
char型に対応する0は'0'でした!初歩的なミスでした。ありがとうございます。
ご指摘された所を直すと、入力は上手くいっているように見えるのですが、0を入力するとコマンドプロンプトが動作停止してしまいます。
add関数にコメントを書き忘れたせいでそこだけ何をしているのか具体的にわからなくなってしまったので、現在add関数を見直しています。

Re: 線形リスト構造について。(2回目)

Posted: 2012年6月17日(日) 13:51
by すぱせる
ちなみに現在のソースはこうなっています。

コード:

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

typedef struct field{
	char name[32];
	double h,w,bmi;
	struct field *next;
}person;

void add(person **plist, person *newstr);
void output(person *p);
void pfree(person *p);

int main(void){

	person *list,t;
	int i;

	list=NULL;
	i=0;

	while(1){	//入力件数が不定なのでループ内のbreakで終了させる

		printf("%d人目_\n",++i);
		printf("名前 > ");		scanf("%s",t.name);

		if(t.name[0]=='0'){		//名前の1文字目が0なら処理終了
			break;
		}
		printf("身長[cm] > ");	scanf("%lf",&t.h);
		printf("体重[kg] > ");	scanf("%lf",&t.w);

		t.h /= 100;	//身長の単位が[cm]なので[m]に直す
		t.bmi = t.w/(t.h*t.h);	//BMI指数を求める
		t.next = NULL;	//暫定的にポインタをNULLにするとリストに追加しやすくなる

		add(&list,&t);//線形リストに追加
	}
	output(list);	//線形リストを出力
	pfree(list);	//領域を解放

	return 0;
}

void add(person **plist,person *newt){

	person *p,*q,*r;
	
	p = *plist;
	if(p == NULL){
		q = NULL;
	}
	while(p != NULL){
		if(newt->bmi < p->bmi){	//BMI指数が自分より大きければ処理終了
			break;
		}else{
			q = p;
			p = p->next;
		}
		r = (person*)malloc(sizeof(person));
		*r = *newt;
		r->next = p;
		
		if(q != NULL){
			q->next = r;
		}else{
			*plist = r;
		}
	}
}

void output(person *p){

	while(1){	//入力件数が不定なので、ループ内のbreakで終了させる
		if(p==NULL){	//ポインタがNULLなら処理終了
			break;
		}else{
			printf("BMI > %.1lf,",p->bmi);
			printf("身長[cm] > %.1lf,",p->h);
			printf("体重[kg] > %.1lf,",p->w);
			printf("名前 > %s.\n",p->name);
		}
		p=p->next;	//次を指すアドレスを代入
	}
}

void pfree(person *p){	//領域を解放する関数
	
	//NULLポインタを見つけ次第、
	//そこから先頭に向かって順に領域を解放していく。
	if(p->next!=NULL){
		pfree(p->next);	
	}
	free(p);
	p = NULL;
}

Re: 線形リスト構造について。(2回目)

Posted: 2012年6月17日(日) 14:59
by すぱせる
やはりadd関数が間違っていました!
ループの}をつけ忘れたせいで処理がおかしくなっていたようです。

現在のソースコードです。

コード:

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

typedef struct field{
	char name[32];
	double h,w,bmi;
	struct field *next;
}person;

void add(person **plist, person *newstr);
void output(person *p);
void pfree(person *p);

int main(void){

	person *list,t;			//*list:線形リストの先頭 t:最新の値を格納しておく構造体
	int i;

	list = NULL;			//この時点のrootは先頭でもあり末尾でもあるためNULLを代入
	i = 0;

	while(1){				//入力件数が不定なのでループ内のbreakで終了させる

		printf("%d人目_\n",++i);
		printf("名前 > ");		scanf("%s",t.name);

		if(t.name[0] == '0'){	//名前の1文字目が0なら処理終了
			break;
		}
		printf("身長[cm] > ");	scanf("%lf",&t.h);
		printf("体重[kg] > ");	scanf("%lf",&t.w);

		t.bmi = t.w/((t.h/100)*(t.h/100));	//BMI指数を求める。身長が[m]なので[cm]で計算
		t.next = NULL;

		add(&list,&t);			//線形リストに追加
	}
	output(list);				//線形リストを出力
	pfree(list);				//領域を解放

	return 0;
}

void add(person **plist,person *newt){

	person *p,*q,*r;
	
	p = *plist;		//*plistの場所を覚えさせる
	if(p == NULL){	
		q = NULL;	//*plistが初めからNULLなら、qを末尾にする
	}
	while(p != NULL){				//末尾でない限りループ
		if(newt->bmi < p->bmi){		//BMI指数が自分より大きければ処理終了
			break;
		}else{
			q = p;				//BMI指数が自分より小さければqにその場所を覚えさせ、
			p = p->next;		//pにpが指す次のアドレスを代入
		}
	}
	r = (person*)malloc(sizeof(person));	//割り込む場所が決まれば領域を確保
	*r = *newt;								//確保した領域rに最新の構造体を格納
	r->next = p;							//pは自分の次を指すポインタなのでr->nextにアドレスを格納
		
	if(q != NULL){
		q->next = r;		//qの次はrが来る
	}else{
		*plist = r;			//qが末尾ならrがrootになる(whileの処理は行われていない)
	}
}

void output(person *p){

	while(1){			//入力件数が不定なので、ループ内のbreakで終了させる
		if(p == NULL){	
			break;		//末尾なら処理終了
		}else{
			printf("BMI > %.1lf ",p->bmi);
			printf("身長[cm] > %.1lf ",p->h);
			printf("体重[kg] > %.1lf ",p->w);
			printf("名前 > %s.\n",p->name);
		}
		p = p->next;	//次を指すアドレスを代入
	}
}

void pfree(person *p){	//領域を解放する関数
	
		//NULLポインタを見つけ次第、
		//そこから先頭に向かって順に領域を解放していく。
	if(p->next != NULL){
		pfree(p->next);	
	}
	free(p);
	p = NULL;
}
ただ、入力が上手くいく時と、上手くいかない時があります。
何人分か入力して、次の名前に0を入れると、しっかりBMI指数の順に並ぶ場合と、
3人分の入力が終わった瞬間(名前に0を入力する前)にエラーが出て強制終了される場合の2パターンです。

エラーが起こる時は毎回3人目なのでまだadd関数がおかしいのでしょうか…?

Re: 線形リスト構造について。(2回目)

Posted: 2012年6月17日(日) 15:16
by かずま
すぱせる さんが書きました:エラーが起こる時は毎回3人目なのでまだadd関数がおかしいのでしょうか…?
add関数の中の q = NULL; は p が NULL かどうかに関係なく常に行うべきです。

Re: 線形リスト構造について。(2回目)

Posted: 2012年6月17日(日) 15:44
by すぱせる
かずま さんが書きました:
すぱせる さんが書きました:エラーが起こる時は毎回3人目なのでまだadd関数がおかしいのでしょうか…?
add関数の中の q = NULL; は p が NULL かどうかに関係なく常に行うべきです。
なるほど!3人目でエラーが起きる原因は完全にコレでした。
pがNULLであろうとなかろうと末尾は不可欠ですよね。

コード:

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

typedef struct field{
	char name[32];
	double h,w,bmi;
	struct field *next;
}person;

void add(person **plist, person *newstr);
void output(person *p);
void pfree(person *p);

int main(void){

	person *list,t;			//*list:線形リストの先頭 t:最新の値を格納しておく構造体
	int i;

	list = NULL;			//この時点のrootは先頭でもあり末尾でもあるためNULLを代入
	i = 0;

	while(1){				//入力件数が不定なのでループ内のbreakで終了させる

		printf("%d人目_\n",++i);
		printf(" 名前 > ");		scanf("%s",t.name);

		if(t.name[0] == '0'){	//名前の1文字目が0なら処理終了
			break;
		}
		printf(" 身長[cm] > ");	scanf("%lf",&t.h);
		printf(" 体重[kg] > ");	scanf("%lf",&t.w);

		t.bmi = t.w/((t.h/100)*(t.h/100));	//BMI指数を求める。身長が[m]なので[cm]で計算
		t.next = NULL;

		add(&list,&t);			//線形リストに追加
	}
	output(list);				//線形リストを出力
	pfree(list);				//領域を解放

	return 0;
}

void add(person **plist,person *newt){

	person *p,*q;	//*p:次の構造体を指すポインタ *q:前の構造体を指すポインタ
	person *r;		//*r:入力された値を格納する構造体を指すポインタ

	p = *plist;			//*plistの場所をpに覚えさせる	
	q = NULL;			//暫定的にqを末尾に設定しておく

	while(p != NULL){			//末尾でない限りループ
		if(newt->bmi < p->bmi){
			break;				//BMI指数が自分より大きければ処理終了
		}else{
			q = p;				//BMI指数が自分より小さければqにその場所を覚えさせ、
			p = p->next;		//pにpが指す次のアドレスを代入
		}
	}
	r = (person*)malloc(sizeof(person));	//割り込む場所が決まれば領域を確保
	*r = *newt;								//確保した領域rに最新の構造体を格納
	r->next = p;							//pは自分の次を指すポインタなのでr->nextにアドレスを格納
		
	if(q != NULL){
		q->next = r;		//qの次はrが来る
	}else{
		*plist = r;			//qが末尾ならrがrootになる
	}
}

void output(person *p){

	while(1){			//入力件数が不定なので、ループ内のbreakで終了させる
		if(p == NULL){	
			break;		//末尾なら処理終了
		}else{
			printf(" BMI > %.1lf ",p->bmi);
			printf(" 身長[cm] > %.1lf ",p->h);
			printf(" 体重[kg] > %.1lf ",p->w);
			printf(" 名前 > %s.\n",p->name);
		}
		p = p->next;	//次を指すアドレスを代入
	}
}

void pfree(person *p){	//領域を解放する関数
	
		//NULLポインタを見つけ次第、
		//そこから先頭に向かって順に領域を解放していく。
	if(p->next != NULL){
		pfree(p->next);	
	}
	free(p);
	p = NULL;
}
これで完成しました!
出力結果も満足のいくものでした!みなさんご協力ありがとうございます!

Re: 線形リスト構造について。(2回目)

Posted: 2012年6月17日(日) 16:33
by かずま
すぱせる さんが書きました:これで完成しました!
リストの先頭に 1個余分な要素を用意しておくと、リストの先頭への挿入を特別扱いしなくてよくなります。

コード:

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

typedef struct field {
    char name[32];
    double height, weight, bmi;
    struct field *next;
} person;

void add(person *plist, person *newstr);
void output(const person *p);
void pfree(person *p);

int main(void)
{
    person list = { 0 };  // list.next が線形リストの先頭要素を指す
    int i = 0;

    while (1) {
        person t;
        printf("%d人目_\n", ++i);
        printf(" 名前 > ");
        if (scanf("%31s", t.name) != 1 || t.name[0] == '0') break;
        printf(" 身長[cm] > ");  scanf("%lf", &t.height);
        printf(" 体重[kg] > ");  scanf("%lf", &t.weight);
        t.bmi = t.weight/((t.height/100)*(t.height/100));
        t.next = NULL;
        add(&list, &t);
    }
    output(&list);
    pfree(&list);
    return 0;
}

void add(person *p, person *newt)
{
    person *q;
    for (; p->next && newt->bmi >= p->next->bmi; p = p->next) ;
    q = (person*)malloc(sizeof(person));
    *q = *newt;
    q->next = p->next;
    p->next = q;
}

void output(const person *p)
{
    while ((p = p->next) != NULL)
        printf("BMI > %.1f 身長[cm] > %.1f 体重[kg] > %.1f 名前 > %s.\n",
            p->bmi, p->height, p->weight, p->name);
}

void pfree(person *p)
{
    person *next = p->next;
    while ((p = next) != NULL) {
        next = p->next;
        free(p);
    }
}
scanf で float は "%f"、doulbe は "%lf" ですが、
printf では float も doulbe も "%f" です。