文字列が空であるかどうかの判定がうまくできません。

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
かなたん

文字列が空であるかどうかの判定がうまくできません。

#1

投稿記事 by かなたん » 14年前

はじめまして。
私は去年1年授業でC言語を習った大学生です。
これは今宿題で作っている「n人の持っているゲームを管理するプログラム」の一部です。
名前を入力してもらって、リストにその名前がなければ新規登録して、名前があればその番号を示すような部分を作ろうと思っています。
↓自分で作ってみたプログラム(一部抜粋) *繰り返して使うことを全く意識していません。 よって、当然プログラムを終えてまた起動すると、結局毎回同じ動作をすることになります。

コード:

int main(void){
	int i,k=-1,n=100; //iは、配列を作成したりリストの番号を調べるための変数。 kは、同じ名前があるかどうか判定したりするための変数。 nはn人のn。
	char **list,people[20];
	list=(char **)malloc(n*sizeof(char *));
	list[0]=(char *)malloc(n*20*sizeof(char));
	for(i=0;i<n;i++){
		list[i]=list[0]+i*20;
	}
	//配列のサイズがnの値によって変わるため、メモリを動的に確保した。
	i=0;
	printf("あなたの名前は?\n");
	scanf("%s",people); //ここで聞いた名前をリストに登録したい。
	if(strcmp(list[0],"")==0){ //1番目が空きなら、そこに新規登録する。
		strcpy(list[0],people);
	}
	else{ //そうじゃなかったら同じ名前が登録されているか探しに行く。
		while(strcmp(list[i],"")!=0){ //登録されているデータがある間
			if(strcmp(list[i],people)==0){ //同じ名前があればその番号を記録する。
				k=i;
				break; //もう探す必要はないので抜ける。
			}
		}
		if(k==-1){ //登録されているデータの中になければ、
			strcpy(list[i],people); //最後に新規登録する。
		}
	}
	free(list); //メモリの開放。
	return 0;
}
これを実行すると、名前を入れた後なかなか次(↑の通りだと終了)へ行きません。
そこで、16/17/19/20にブレイクポイントを設置し、デバッグをかけてみました。
すると、名前を入力した後普通に16で止まるのですが、その後17を飛ばして19の次の20へ行ってしまいます。
当然その下のwhileの部分も、ずっと判定内のため無限ループになってしまいます。
自動変数をみてみると、list[0]やlistは0x006c1290"ヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘヘ・・・・"と文字化けしたときようなものが入っています。
特になにか代入したわけではないのであのようなものが入っているのはわかるのですが、だからでしょうか?
16でリストの1番目(配列で言うと0番目)になにも入っていなければ―が、思ったように判断されません。
私はなにも入っていないを「""」で表しているのですが、本当はどのようにあらわすのがよいでしょうか?

使用環境
OS:Windows7 Home Premium
ソフト:Microsoft Visual Studio 2008 ツール:C/C++コンパイラツール

box
記事: 2002
登録日時: 14年前

Re: 文字列が空であるかどうかの判定がうまくできません。

#2

投稿記事 by box » 14年前

ザッと見ただけですけど…。
かなたん さんが書きました:

コード:

	for(i=0;i<n;i++){
		list[i]=list[0]+i*20;
	}
このループで何をしようとしているのか、わかりません。list[1]以降はmalloc()しなくてもいいんでしょうか。
かなたん さんが書きました:

コード:

	else{ //そうじゃなかったら同じ名前が登録されているか探しに行く。
		while(strcmp(list[i],"")!=0){ //登録されているデータがある間
			if(strcmp(list[i],people)==0){ //同じ名前があればその番号を記録する。
				k=i;
				break; //もう探す必要はないので抜ける。
			}
		}
while文でループを回すのはいいとして、 i の値を変化させなくていいんでしょうか。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

アバター
a5ua
記事: 199
登録日時: 14年前

Re: 文字列が空であるかどうかの判定がうまくできません。

#3

投稿記事 by a5ua » 14年前

初期化されていない部分には、不定値が入っているので、空文字と比較するのは適切ではありません。

名前を入力する時点で、0 ~ i - 1番目まで登録されているので、
i未満の部分を全て調べる必要があります。

コード:

/* 名前入力 */

for (k = 0; k < i; k++) {
	/* 同じ名前が見つかったらループを抜ける */
}

/* kに同じ名前が見つかった場所が入る */
/* 同じ名前が見つからなかった場合、kはいくつになっているか考えてみましょう */


box
記事: 2002
登録日時: 14年前

Re: 文字列が空であるかどうかの判定がうまくできません。

#4

投稿記事 by box » 14年前

かなたん さんが書きました:

コード:

	for(i=0;i<n;i++){
		list[i]=list[0]+i*20;
	}
やっと意図がわかりました。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

box
記事: 2002
登録日時: 14年前

Re: 文字列が空であるかどうかの判定がうまくできません。

#5

投稿記事 by box » 14年前

もしかすると、malloc()のかわりにcalloc()を使うとよいかもしれません。
確か、calloc()では動的確保した領域に 0x00 を埋め込むことを保証しているはずなので。
間違っていたらすみません。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

non
記事: 1097
登録日時: 14年前

Re: 文字列が空であるかどうかの判定がうまくできません。

#6

投稿記事 by non » 14年前

callocでもよいけど、mallocなら
memset(list[0], 0, n*20*sizeof(char));
をしましょう。
freeはmallocの回数やりましょう。
non

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#7

投稿記事 by かなたん » 14年前

boxさん・a5uaさん・nonさん。 回答ありがとうございました。
box さんが書きました:
かなたん さんが書きました:

コード:

	for(i=0;i<n;i++){
		list[i]=list[0]+i*20;
	}
このループで何をしようとしているのか、わかりません。list[1]以降はmalloc()しなくてもいいんでしょうか。
そこは、教科書の 田中敏幸著「C言語によるプログラミングの基礎」の動的メモリ割りつけの項の2次元配列の動的確保のサンプルプログラムを参考に作ったものです。
box さんが書きました:
かなたん さんが書きました:

コード:

	else{ //そうじゃなかったら同じ名前が登録されているか探しに行く。
		while(strcmp(list[i],"")!=0){ //登録されているデータがある間
			if(strcmp(list[i],people)==0){ //同じ名前があればその番号を記録する。
				k=i;
				break; //もう探す必要はないので抜ける。
			}
		}
while文でループを回すのはいいとして、 i の値を変化させなくていいんでしょうか。
あ・・・やってしまった・・・;
はい。 i変化させないとだめですね。 じゃないといつまでも変わらなくなりますもんね;
a5ua さんが書きました:初期化されていない部分には、不定値が入っているので、空文字と比較するのは適切ではありません。
名前を入力する時点で、0 ~ i - 1番目まで登録されているので、
i未満の部分を全て調べる必要があります。
やはりそうですか・・・
空だったら―は通用せず、すべて調べなくてはならないと。
a5ua さんが書きました:

コード:

/* 名前入力 */
 
for (k = 0; k < i; k++) {
    /* 同じ名前が見つかったらループを抜ける */
}
 
/* kに同じ名前が見つかった場所が入る */
/* 同じ名前が見つからなかった場合、kはいくつになっているか考えてみましょう */
だからこのようなやり方を進めてくださったのですね。
↑の場合は・・・見つからなかった場合kはiですよね?
でも、見つからなかった場合の新規登録はどのようにするのがいいでしょうか?
最後を探そうにも真の空は存在しないから、「""」と==にならないし・・・
box さんが書きました:もしかすると、malloc()のかわりにcalloc()を使うとよいかもしれません。
確か、calloc()では動的確保した領域に 0x00 を埋め込むことを保証しているはずなので。
間違っていたらすみません。
おぉ。
callocだと「""」と==になりました。
参考サイト 「calloc」 http://www9.plala.or.jp/sgwr-t/lib/calloc.html
non さんが書きました:callocでもよいけど、mallocなら
memset(list[0], 0, n*20*sizeof(char));
をしましょう。
ほぉ。
作った後に空を代入させるんですね?
参考サイト 「memeset」 http://www9.plala.or.jp/sgwr-t/lib/memset.html
私は空の箱を用意してもらえればそれでいいので、callocが空の箱を作ってもらえるのでいいと思いました。
non さんが書きました:freeはmallocの回数やりましょう。
教科書でも参考にしたサイトでもfreeはmallocを何度しようと1回(配列にポインタ変数の数だけ?)しかしていないのですが、それではいけないのでしょうか?

これでプログラムを作ることができました。
3次元配列の部分は、こちらを利用させてもらいました。
「ポインタのポインタのポインタの利用と3次元配列の利用 - あきらけいすけの雑記帳」 http://d.hatena.ne.jp/arakik10/20070614/p1
ありがとうございました。

コード:

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

void enter(int k,char ***namedata,char ***harddata); //ゲームを登録するための関数。 登録したい人とリストの情報が渡される。
void elase(int k,char ***namedata,char ***harddata); //ゲームを削除するための関数。 削除したい人とリストの情報が渡される。

int main(void){
	int n=100,i,k;//nはn人のn。 iは、配列を作成したりリストの番号を調べたり、行う動作を調べるための変数。 kは、同じ名前があるかどうか判定したりするための変数。
	char **list,people[20],***namedata,***harddata; //**listは名前を記録するための配列用ポインタ。 peopleは名前を仮保存するための変数。 ***namedeta,***harddataはゲームやその本体の名前を記録するための配列用ポインタ。
	list=(char **)calloc(100,sizeof(char *)); //boxさん・nonさんが教えてくれたcallocのおかげで、きちんと空の箱が作れました。
	list[0]=(char *)calloc(100*20,sizeof(char));
	for(i=0;i<n;i++){
		list[i]=list[0]+i*20;
	}
	namedata=(char ***)calloc(n,sizeof(char **));
	harddata=(char ***)calloc(n,sizeof(char **));
	for(i=0;i<n;i++){
		namedata[i]=(char **)calloc(100,sizeof(char *));
		harddata[i]=(char **)calloc(100,sizeof(char *));
	}
	for(i=0;i<n;i++){
		for(k=0;k<100;k++){
		namedata[i][k]=(char *)calloc(100,sizeof(char));
		harddata[i][k]=(char *)calloc(20,sizeof(char));
		}
	}
	i=0;
	k=-1;
	printf("あなたの名前は?\n");
	scanf("%s",people); //ここで聞いた名前をリストに登録したい。
	if(strcmp(list[0],"")==0){ //1番目が空きなら、そこに新規登録する。
		strcpy(list[0],people);
		k=0;
	}
	else{ //そうじゃなかったら同じ名前が登録されているか探しに行く。
		while(strcmp(list[i],"")!=0){ //登録されているデータがある間
			if(strcmp(list[i],people)==0){ //同じ名前があればその番号を記録する。
				k=i;
				break; //もう探す必要はないので抜ける。
			}
		}
		if(k==-1){ //登録されているデータの中になければ、
			strcpy(list[i],people); //最後に新規登録する。
		}
	}
	i=-1;
	while(i!=0){
		printf("やりたいのはなにかな?\n1.ゲームの追加 2.ゲームの削除 0. 終了\n");
		scanf("%d",&i);
		if(i==1){
			enter(k,namedata,harddata); //登録したい人とリストの情報を渡してゲームを登録するための関数を実行。
		}
		else if(i==2){
			elase(k,namedata,harddata); //削除したい人とリストの情報を渡してゲームを削除するための関数を実行。
		}
	}
	free(list); //メモリの開放。
	free(namedata);
	free(harddata);
	return 0;
}
void enter(int k,char ***namedata,char ***harddata){ //ゲームを登録するための関数  登録したい人とリストの情報が渡される。
	char name[100],hard[20]; //name,hardはゲームとその本体の名前を仮保存するための変数。
	int i=0; //iは空きデータを探したりそこに記録したりするための変数。
	printf("新しく増えたゲームは?\n");
	scanf("%s",name);
	printf("そのゲームは何で遊ぶのかな?\n");
	scanf("%s",hard); //ここで聞いたゲーム情報をリストに登録したい。
	while(strcmp(namedata[k][i],"")!=0){ //リストの空きが発見されるまで繰り返す。
		i=i+1;
	}
	strcpy(namedata[k][i],name);
	strcpy(harddata[k][i],hard); //ゲーム情報を登録する。
}
void elase(int k,char ***namedata,char ***harddata){ //ゲームを削除するための関数 削除したい人とリストの情報が渡される。
	char name[100],hard[20]; //name,hardはゲームとその本体の名前を仮保存するための変数。
	int i=0,t=0; //iは空きデータを探したりそこに記録したりするための変数。 tは〃ゲームがあるかどうか判断するための変数。
	printf("捨てたゲームは?\n");
	scanf("%s",name);
	printf("そのゲームは何で遊ぶのかな?\n");
	scanf("%s",hard); //ここで聞いたゲーム情報をリストから削除したい。
	while(strcmp(namedata[k][i],"")!=0){ //登録されているデータがある間
		if(strcmp(name,namedata[k][i])==0){ //入力してもらったゲーム名とリストのゲーム名が一致したら、
			if(strcmp(hard,harddata[k][i])==0){ //入力してもらったゲーム名と本体名が一致するか調べて、
				strcpy(namedata[k][i],"");
				strcpy(harddata[k][i],""); //一致したらそのデータを削除。
				t=1; //同じゲームがあったと言う。
				break; //もう探す必要がないので抜ける。
			}
		}
		i=i+1;
	}
	if(t==0){ //〃ゲームが見つからなかったら。
		printf("そのゲームは持ってないよ?\n");
	}
}

アバター
a5ua
記事: 199
登録日時: 14年前

Re: 文字列が空であるかどうかの判定がうまくできません。

#8

投稿記事 by a5ua » 14年前

かなたん さんが書きました:
a5ua さんが書きました:

コード:

/* 名前入力 */
 
for (k = 0; k < i; k++) {
    /* 同じ名前が見つかったらループを抜ける */
}
 
/* kに同じ名前が見つかった場所が入る */
/* 同じ名前が見つからなかった場合、kはいくつになっているか考えてみましょう */
だからこのようなやり方を進めてくださったのですね。
↑の場合は・・・見つからなかった場合kはiですよね?
でも、見つからなかった場合の新規登録はどのようにするのがいいでしょうか?
最後を探そうにも真の空は存在しないから、「""」と==にならないし・・・
以下のプログラムで説明しますと、
iが現在登録されているデータの総数になるので、i番目(data)には意味のある文字列は入っていません。
なので、見つからなかった場合は、i番目に新規登録すればよいのです。(登録したら、データ数を更新するのをお忘れなく)

コード:

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

int main(void)
{
	int k;
	char name[256];

	int i = 0;			// 現在のデータの個数
	char data[3][256];	// 全データ(とりあえず3個まで)

	// データ登録
	while (i < 3) {
		printf("あなたの名前は?");
		scanf("%s", name);

		// 重複した名前を探す
		for (k = 0; k < i; ++k) {
			if (strcmp(name, data[k]) == 0) {
				break;
			}
		}
		if (k == i) {
			// 新規登録
			strcpy(data[i], name);
			++i;
		} else {
			printf("その名前は登録済みです\n");
		}
	}

	// 3個データが登録されたら、データを表示してみる
	printf("データ表示\n");
	for (k = 0; k < 3; ++k) {
		printf("%s\n", data[k]);
	}

	return 0;
}

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#9

投稿記事 by かなたん » 14年前

a5uaさん回答ありがとうございます。
a5ua さんが書きました:以下のプログラムで説明しますと、
iが現在登録されているデータの総数になるので、i番目(data)には意味のある文字列は入っていません。
なので、見つからなかった場合は、i番目に新規登録すればよいのです。(登録したら、データ数を更新するのをお忘れなく)

コード:

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

int main(void)
{
	int k;
	char name[256];

	int i = 0;			// 現在のデータの個数
	char data[3][256];	// 全データ(とりあえず3個まで)

	// データ登録
	while (i < 3) {
		printf("あなたの名前は?");
		scanf("%s", name);

		// 重複した名前を探す
		for (k = 0; k < i; ++k) {
			if (strcmp(name, data[k]) == 0) {
				break;
			}
		}
		if (k == i) {
			// 新規登録
			strcpy(data[i], name);
			++i;
		} else {
			printf("その名前は登録済みです\n");
		}
	}

	// 3個データが登録されたら、データを表示してみる
	printf("データ表示\n");
	for (k = 0; k < 3; ++k) {
		printf("%s\n", data[k]);
	}

	return 0;
}
確かに、特になにも登録していないデータの中に繰り返して登録させるときはこんな感じでいいと思います。
ですが、途中まできちんとしたデータがある場合。
事前にいくつデータがあるかわかっていればそれをiに代入しておけばいいのかもしれませんが、いくつあるのかが分からない場合はiはどうやって探したらいいんですか?
やっぱり、作るときに空の箱を作るか、作った直後に空にするかしておくしかないですよね?

アバター
a5ua
記事: 199
登録日時: 14年前

Re: 文字列が空であるかどうかの判定がうまくできません。

#10

投稿記事 by a5ua » 14年前

>途中まできちんとしたデータがある場合
この“きちんとしたデータ”も、最初は何も登録していないデータから作られると思います
ファイルから読み込むにしろ、プログラム中に直接書くにしろ、そのデータ数はわかるのではないでしょうか

※かなたんのプログラムを否定しているわけではないので、誤解のないように
データとしての空文字が無効ならば、それをデータの終端として扱うのは、問題ありません。

non
記事: 1097
登録日時: 14年前

Re: 文字列が空であるかどうかの判定がうまくできません。

#11

投稿記事 by non » 14年前

かなたん さんが書きました:
non さんが書きました:freeはmallocの回数やりましょう。
教科書でも参考にしたサイトでもfreeはmallocを何度しようと1回(配列にポインタ変数の数だけ?)しかしていないのですが、それではいけないのでしょうか?

[/code]
私の認識では、必要ですが、参考にされたのはどちらのサイトでしょうか?
non

ISLe
記事: 2650
登録日時: 14年前
連絡を取る:

Re: 文字列が空であるかどうかの判定がうまくできません。

#12

投稿記事 by ISLe » 14年前

確保した分を超えて登録しようとしたときについて考慮しなくても良いのでしょうか。

あと、
ポインタのポインタの~は引数でそれだけ渡されても要素数が分からないし単純な多次元配列の代わりにする意味がないと思います。
実際にバグを増やす原因になってますし。
要素数が分かっているなら配列型を使うのが良いのではないでしょうか。

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#13

投稿記事 by かなたん » 14年前

a5ua さんが書きました:>途中まできちんとしたデータがある場合
この“きちんとしたデータ”も、最初は何も登録していないデータから作られると思います
ファイルから読み込むにしろ、プログラム中に直接書くにしろ、そのデータ数はわかるのではないでしょうか
確かに初めっから自分で0番目はaで1番目はbで―って代入しているのなら、いくつだか自分でわかっていると思います。
ですが、あの登録や削除・・・の場合はいくつ入っていくつ消えたかどこかでカウントしておけばいいんですね。
で、ファイルのデータ読み込みの場合だとしたら・・・ファイル内のデータの数をカウントさせることとかってできるんですか?
ファイルへの入出力の方法は知っているのですが、データがいくつあるかとか、どこにどのデータがあるかを調べるすべを知らないもので・・・
でも、Googleで調べてみたらありました。
EOFというのを使って、EOFが来るまで読んでいって個数を調べてみればいいんですね?
参考サイト 「C言語(データ数の読み取り) | OKWave」 http://okwave.jp/qa/q1701244.html
そうかぁ。 これらのような手がありますね。
そしたら、真の空の箱でなくってもデータの最後に新しいデータの登録ができますね。
a5ua さんが書きました:※かなたんのプログラムを否定しているわけではないので、誤解のないように
データとしての空文字が無効ならば、それをデータの終端として扱うのは、問題ありません。
はい。 わかりました。
いろいろとありがとうございました。

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#14

投稿記事 by かなたん » 14年前

nonさん・ISLeさん回答ありがとうございます。
non さんが書きました:
かなたん さんが書きました:
non さんが書きました:freeはmallocの回数やりましょう。
教科書でも参考にしたサイトでもfreeはmallocを何度しようと1回(配列にポインタ変数の数だけ?)しかしていないのですが、それではいけないのでしょうか?

[/code]
私の認識では、必要ですが、参考にされたのはどちらのサイトでしょうか?
サイトというのは、その返信でも書いたポインタのポインタのポインタと3次元配列の利用について書かれている記事のことです。
「ポインタのポインタのポインタと3次元配列の利用 - あきらけいすけの雑記帳」 http://d.hatena.ne.jp/arakik10/20070614/p1
3次元配列をポインタのポインタのポインタでmallocを3回以上使って作られていますが、freeは最後の1回しかされていません。
教科書というのは、田中敏幸著「C言語によるプログラミングの基礎」です。
その本の動的メモリ割りつけの項の2次元配列の動的確保のサンプルプログラムによると、2次元配列をポインタのポインタでmallocを2回以上使って作られていますが、↑と同様にfreeは最後の1回しかされていません。
また、関数free()の説明文にはこう書いてあります。
関数free()は、引数に与えられるポインタが示す領域を解放し、その後に割りつけに再び使用できるようにする関数である。
―中略―
malloc()を使って領域を確保したときは、プログラムの最後で必ず開放するようにしよう。
ということは、
どんなポインタ変数に何回mallocで領域を確保しようと、先頭のアドレスを解放してやれば後もすべて解放される。
ということではないでしょうか?
ISLe さんが書きました:確保した分を超えて登録しようとしたときについて考慮しなくても良いのでしょうか。
登録できる個数として100用意したのは、100くらいあれば足りるかなぁっておもったからです。
もし100以上持っている人の場合は、まったく想定してませんでした・・・
また、登録する人やゲーム、本体の名前用の要素数も、だいたいこれくらいあれば足りるだろうと私が勝手に思った数です。
ですが、今よくよく考えるとゲームボーイアドバンスなんて入力されると全角11文字(半角22文字分)ですから、明らかに足りないですね・・・
そういうことも考えておかないといけなかったですよね・・・
ISLe さんが書きました:あと、
ポインタのポインタの~は引数でそれだけ渡されても要素数が分からないし単純な多次元配列の代わりにする意味がないと思います。
実際にバグを増やす原因になってますし。
要素数が分かっているなら配列型を使うのが良いのではないでしょうか。
配列をポインタのポインタのポインタで作ろうと思ったのは、先生が「n人の人が―」と言っていたことから、配列のサイズをmallocを使って動的に用意しようと思ったからです。
また、nを100限定にして

コード:

char namedata[100][100][100],harddata[100][100][20];
のようにきちっと決めてしまおうとも思ったのですが、実行するとstack overflowが出てしまって・・・
そこで調べてみると、原因は配列のサイズが大きいため使用できるメモリ量を上回ってしまっているようで。
やはりmalloc等で動的にメモリを確保するほうがいいみたいで、mallocで作ることにしたんです。
でも、バグが増える原因になってしまうのですか・・・
だとしたら、配列のサイズが大きくてstack overflowになってしまうような場合は、どのようにするのがいいんですか?
使用できるメモリ量を増やせばいいんですか? それとも渡す変数を変更するなり追加するなりするのがいいんですか?

アバター
bitter_fox
記事: 607
登録日時: 14年前
住所: 大阪府

Re: 文字列が空であるかどうかの判定がうまくできません。

#15

投稿記事 by bitter_fox » 14年前

かなたん さんが書きました:
教科書でも参考にしたサイトでもfreeはmallocを何度しようと1回(配列にポインタ変数の数だけ?)しかしていないのですが、それではいけないのでしょうか?
プログラムが終了すると多くのOSはプログラムが確保したリソースを解放するように制御しているので問題にはなりません。
ですが、実行が継続しているプログラムで幾度も解放のミス(メモリリーク)が起きると、非常にまずいことになります。
かなたん さんが書きました: http://d.hatena.ne.jp/arakik10/20070614/p1
3次元配列をポインタのポインタのポインタでmallocを3回以上使って作られていますが、freeは最後の1回しかされていません。
教科書というのは、田中敏幸著「C言語によるプログラミングの基礎」です。
その本の動的メモリ割りつけの項の2次元配列の動的確保のサンプルプログラムによると、2次元配列をポインタのポインタでmallocを2回以上使って作られていますが、↑と同様にfreeは最後の1回しかされていません。
ちょっとその教本のfreeの仕方は誤解を招きますね。
リンク先ではfreeすらしていないのですが、それはOSを頼っていることが感じられるので変な矛盾はないですが、教本のfreeの仕方は矛盾してしまっています。
かなたん さんが書きました: また、関数free()の説明文にはこう書いてあります。
関数free()は、引数に与えられるポインタが示す領域を解放し、その後に割りつけに再び使用できるようにする関数である。
―中略―
malloc()を使って領域を確保したときは、プログラムの最後で必ず開放するようにしよう。
ということは、
どんなポインタ変数に何回mallocで領域を確保しようと、先頭のアドレスを解放してやれば後もすべて解放される。
ということではないでしょうか?
複数回mallocした場合のアドレスが連続しているという保証は無いですし、確保した領域分しか解放されないのでそれは誤りです。

その証拠にタスクマネージャを起動して次のプログラムを実行してみてください。
プロセスタブのメモリの使用量が最初のforと二つ目のforとで違うはずです。

コード:


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

int main()
{
	int counter;
	char *p[3];

	for (counter = 0; counter < 5000; counter++)
	{
		printf("%d\r", counter);
		p[0] = (char*)malloc(10000);
		p[1] = (char*)malloc(10000);
		p[2] = (char*)malloc(10000);

		free(p[0]); // すべて解放
		free(p[1]);
		free(p[2]);
	}
	printf("Input Any Key(1st time)...");
	getchar();

	for (counter = 0; counter < 5000; counter++)
	{
		printf("%d\r", counter);
		p[0] = (char*)malloc(10000);
		p[1] = (char*)malloc(10000);
		p[2] = (char*)malloc(10000);

		free(p[0]); // 1と2は解放しない
	}
	printf("Input Any Key(2nd time)...");
	getchar();

	return 0;
}

[hr][追記]
僕の環境では次のようになりました。
すべて解放
1.png
1.png (2.24 KiB) 閲覧数: 18424 回
0だけ解放
2.png
2.png (2.79 KiB) 閲覧数: 18424 回

ISLe
記事: 2650
登録日時: 14年前
連絡を取る:

Re: 文字列が空であるかどうかの判定がうまくできません。

#16

投稿記事 by ISLe » 14年前

かなたん さんが書きました:登録できる個数として100用意したのは、100くらいあれば足りるかなぁっておもったからです。
もし100以上持っている人の場合は、まったく想定してませんでした・・・
また、登録する人やゲーム、本体の名前用の要素数も、だいたいこれくらいあれば足りるだろうと私が勝手に思った数です。
100個で足りるかどうかを問うているわけではありません。
101個目を登録しようとしたとき、プログラムが正しく動作するようになっていませんよ、ということです。
かなたん さんが書きました:やはりmalloc等で動的にメモリを確保するほうがいいみたいで、mallocで作ることにしたんです。
でも、バグが増える原因になってしまうのですか・・・
ポインタのポインタのポインタなんて使わなくて済みますよってことです。
こんな感じです。

コード:

	int n = 100;
	char (*namedata)[100][100];
	char (*harddata)[100][20];

	namedata=(char(*)[100][100])calloc(n, sizeof(char[100][100]));
	harddata=(char(*)[100][20] )calloc(n, sizeof(char[100][20] ));

	/* mallocと対応する分のfree */
	free(namedata);
	free(harddata);
バグを増やす原因というのは、一回で済むmallocをわざわざ増やしていることです。
そのせいで解放されないメモリブロックが発生しています。
malloc/freeを使うことが原因ではないです。

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#17

投稿記事 by かなたん » 14年前

昨日は忙しくて返信できませんでしたが。
bitter_foxさん・ISLeさん回答ありがとうございました。
bitter_fox さんが書きました:プログラムが終了すると多くのOSはプログラムが確保したリソースを解放するように制御しているので問題にはなりません。
ですが、実行が継続しているプログラムで幾度も解放のミス(メモリリーク)が起きると、非常にまずいことになります。
freeをしておかなくっても終了時には開放されるとは思うという話は先生から聞いた覚えがあります。
でも、開放されない可能性のために必ずしておかなければならないと。
開放のし忘れは、確かにメモリを使い続けるということなのでよくないと思います。
bitter_fox さんが書きました:ちょっとその教本のfreeの仕方は誤解を招きますね。
リンク先ではfreeすらしていないのですが、それはOSを頼っていることが感じられるので変な矛盾はないですが、教本のfreeの仕方は矛盾してしまっています。
あ・・・リンク先ではfreeを使っていませんでした。
教科書のやり方は矛盾してる・・・んですか?
bitter_fox さんが書きました:複数回mallocした場合のアドレスが連続しているという保証は無いですし、確保した領域分しか解放されないのでそれは誤りです。
そう・・・ですか・・・
bitter_fox さんが書きました:その証拠にタスクマネージャを起動して次のプログラムを実行してみてください。
プロセスタブのメモリの使用量が最初のforと二つ目のforとで違うはずです。
コピペして比べてみました。
私が参考にした値は、ガジェットのCPUメーターのランダムアクセスメモリ(RAM)の%と、タスクマネージャーの該当プロセスのメモリのKです。
(ほかにも起動していたりするので、そのプログラムだけの使用量ではないですが。)
↓値を調べたタイミングとそのときの値。
画像の添付の仕方がわからないのでSSではないですが。
[hr]1.実行する前―52% -K
2.画面上に「Input Any Key(1st time)...」と表示されているとき―52% 728K
3.画面上に「Input Any Key(2st time)...」と表示されているとき―55% 728K
4.画面上に「続行するには何かキーを押してください」と表示されているとき―51% 748K
5.終了した後―51% -K
[hr]RAMで言うと、確かにすべて開放したときより、一部しか開放しなかったときのほうが使われているように見えます。
ですが、メモリで言うと変わらない気がするのですが。
2,3の時点でfreeはすでに実行されているはずなので、使ったメモリのうち全部または一部は開放済みってことですよね?
でもbitter_foxさんのようにメモリの値が変化したりはしませんでした。
3のときにメモリの値が上昇していましたが。
私が確認したタイミングが違っていますでしょうか?
また、教科書のサンプルプログラムにおいてfreeを1回しかしていないとRAMやメモリの値はどうなるのか調べてみました。

コード:

/*  */
#include <stdio.h>
#include <stdlib.h>
int main(void){
	int **a,total=0,i,j,nrows=3,ncolumns=4;
	a=(int **)malloc(nrows*sizeof(int *));
	for(i=0;i<nrows;i++)a[i]=(int *)malloc(ncolumns*sizeof(int));
	a[0][0]=10;a[0][1]=11;a[0][2]=12;a[0][3]=13;
	a[1][0]=20;a[1][1]=21;a[1][2]=22;a[1][3]=23;
	a[2][0]=30;a[2][1]=31;a[2][2]=32;a[2][3]=33;
	for(i=0;i<nrows;i++)
		for(j=0;j<ncolumns;j++)total+=a[i][j];
	printf("result=%d\n",total);
	free(a);
	return 0;
}

コード:

/*  */
#include <stdio.h>
#include <stdlib.h>
int main(void){
	int **a,total=0,i,j,nrows=3,ncolumns=4;
	a=(int **)malloc(nrows*sizeof(int *));
	a[0]=(int *)malloc(nrows*ncolumns*sizeof(int));
	for(i=0;i<nrows;i++)a[i]=a[0]+i*ncolumns;
	a[0][0]=10;a[0][1]=11;a[0][2]=12;a[0][3]=13;
	a[1][0]=20;a[1][1]=21;a[1][2]=22;a[1][3]=23;
	a[2][0]=30;a[2][1]=31;a[2][2]=32;a[2][3]=33;
	for(i=0;i<nrows;i++)
		for(j=0;j<ncolumns;j++)total+=a[i][j];
	printf("result=%d\n",total);
	free(a);
	return 0;
}
結果はこうなりました。
[hr]6.実行する前―51% -K
7.上のを実行したあと画面上に「続行するには何かキーを押してください」と表示されているとき―51% 748K
8.上を終了した後下を実行する前―51% -K
9.下のを実行したあと画面上に「続行するには何かキーを押してください」と表示されているとき―51% 748K
10.下を終了した後―51% -K
[hr]RAMにもメモリにも特に違いは見られませんでした。
それは、連続したアドレスで作ろうと連続していないアドレスで作ろうと、freeを1回しかしていないからでしょうか?
教科書のサンプルプログラムは、bitter_foxさんのサンプルプログラムと違って配列ではないポインターですし、同じ動作を何度もさせているわけではないですけれど。
ISLe さんが書きました:
かなたん さんが書きました:登録できる個数として100用意したのは、100くらいあれば足りるかなぁっておもったからです。
もし100以上持っている人の場合は、まったく想定してませんでした・・・
また、登録する人やゲーム、本体の名前用の要素数も、だいたいこれくらいあれば足りるだろうと私が勝手に思った数です。
100個で足りるかどうかを問うているわけではありません。
101個目を登録しようとしたとき、プログラムが正しく動作するようになっていませんよ、ということです。
はい。
私は勝手に足りるかなぁと思ったので、でももし足りなくなったらを考えていませんでした。
本当は、そういうことも考えに入れておくべきですよね。

コード:

	i=0;
	k=-1;
	printf("あなたの名前は?\n");
	scanf("%s",people);
	if(strcmp(list[0],"")==0){
		strcpy(list[0],people);
		k=0;
	}
	else{
		while(strcmp(list[i],"")!=0 || i>99){
			if(strcmp(list[i],people)==0){
				k=i;
				break;
			}
		}
		if(k==-1){
			if(i!=100){
				strcpy(list[i],people);
			}
			else{
				printf("ごめんなさい。 これ以上の新規登録はできません。\n");
				exit(1);
			}
		}
	}
こんな感じですか?
ISLe さんが書きました:
かなたん さんが書きました:やはりmalloc等で動的にメモリを確保するほうがいいみたいで、mallocで作ることにしたんです。
でも、バグが増える原因になってしまうのですか・・・
ポインタのポインタのポインタなんて使わなくて済みますよってことです。
こんな感じです。

コード:

	int n = 100;
	char (*namedata)[100][100];
	char (*harddata)[100][20];

	namedata=(char(*)[100][100])calloc(n, sizeof(char[100][100]));
	harddata=(char(*)[100][20] )calloc(n, sizeof(char[100][20] ));

	/* mallocと対応する分のfree */
	free(namedata);
	free(harddata);
バグを増やす原因というのは、一回で済むmallocをわざわざ増やしていることです。
そのせいで解放されないメモリブロックが発生しています。
malloc/freeを使うことが原因ではないです。
bitter_foxさんのサンプルプログラムもそうでしたが、配列のポインタも作れるんですね。
決まっているものはすでに配列で宣言してしまって、決まっていないnの部分だけcallocすればいいんですね。
ISLeさんのサンプルプログラムのように書き変えてみても、特にエラーもなく実行することができました。
ところで、(*namedata)[100][100]や、(char(*)[100][100])calloc―のように配列要素にキャストしているのはなぜですか?
また、宣言の時配列変数名ごと()でくくっているのはなぜですか?
私はこのような使い方をしたことがないので・・・

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#18

投稿記事 by かなたん » 14年前

あ。 2箇所書き忘れてました。
教科書の1つ目のサンプルプログラムの最初にあるコメントの中身は、

コード:

/* 2次元配列の動的確保 */
2つ目のサンプルプログラムの最初にあるコメントの中身は、

コード:

/* 2次元配列の動的確保(メモリの連続的確保) */
です。

ISLe
記事: 2650
登録日時: 14年前
連絡を取る:

Re: 文字列が空であるかどうかの判定がうまくできません。

#19

投稿記事 by ISLe » 14年前

かなたん さんが書きました:本当は、そういうことも考えに入れておくべきですよね。
私的にお遊びで作るようなプログラムなら構わないかもしれませんが、バグを潰すのはプログラマの重要な仕事です。
個人的にはお遊びで作るにしても最低限の不具合は潰すべきだと思いますけど。
#鼻から悪魔が飛び出してきたら困りますし。
かなたん さんが書きました:こんな感じですか?
条件式がみごとと言っていいくらい間違ってますよ。
かなたん さんが書きました:ところで、(*namedata)[100][100]や、(char(*)[100][100])calloc―のように配列要素にキャストしているのはなぜですか?
また、宣言の時配列変数名ごと()でくくっているのはなぜですか?
私はこのような使い方をしたことがないので・・・
確保したメモリを『char[100][100]』型の要素を持つ配列としてアクセスしたいからです。
変数名(というかポインタ派生宣言子)を()で括らないと結合優先順位の問題で別の型に解釈されてしまうので、括る必要があります。
『int』型の要素を持つ配列としてアクセスしたいとき『int *』でキャストするのと同じですよ。
ふつうはtypedefを使って分かりやすくしますけど。

アバター
bitter_fox
記事: 607
登録日時: 14年前
住所: 大阪府

Re: 文字列が空であるかどうかの判定がうまくできません。

#20

投稿記事 by bitter_fox » 14年前

かなたん さんが書きました: 画像の添付の仕方がわからないのでSSではないですが。
画像を添付するにはメンバー登録していただくか外部のアップローダに上げていただいてimgタグを使用してください。
かなたん さんが書きました:[hr]1.実行する前―52% -K
2.画面上に「Input Any Key(1st time)...」と表示されているとき―52% 728K
3.画面上に「Input Any Key(2st time)...」と表示されているとき―55% 728K
4.画面上に「続行するには何かキーを押してください」と表示されているとき―51% 748K
5.終了した後―51% -K
[hr]RAMで言うと、確かにすべて開放したときより、一部しか開放しなかったときのほうが使われているように見えます。
ですが、メモリで言うと変わらない気がするのですが。
2,3の時点でfreeはすでに実行されているはずなので、使ったメモリのうち全部または一部は開放済みってことですよね?
でもbitter_foxさんのようにメモリの値が変化したりはしませんでした。
3のときにメモリの値が上昇していましたが。
えっと、「bitter_foxさんのようにメモリの値が変化したりはしませんでした」とありますが、「3の時にメモリの値が上昇していた」のではないでしょうか?
かなたん さんが書きました:また、教科書のサンプルプログラムにおいてfreeを1回しかしていないとRAMやメモリの値はどうなるのか調べてみました。
結果はこうなりました。
[hr]6.実行する前―51% -K
7.上のを実行したあと画面上に「続行するには何かキーを押してください」と表示されているとき―51% 748K
8.上を終了した後下を実行する前―51% -K
9.下のを実行したあと画面上に「続行するには何かキーを押してください」と表示されているとき―51% 748K
10.下を終了した後―51% -K
[hr]RAMにもメモリにも特に違いは見られませんでした。
それは、連続したアドレスで作ろうと連続していないアドレスで作ろうと、freeを1回しかしていないからでしょうか?
まず、「実行するには何かキーを押してください」が表示されている時点でOSによるリソースの解放が実行されてしまっているのではないでしょうか?(僕の環境(Win7, VC2008[デバッグなしで開始])ではこのメッセージが表示されている時点でプロセスが死んでしまいメモリを見ることはできませんでした。)
また、確保しているメモリの量が少なすぎるのでキロバイト表示にした際にほとんど変化が見られないように思います。

box
記事: 2002
登録日時: 14年前

Re: 文字列が空であるかどうかの判定がうまくできません。

#21

投稿記事 by box » 14年前

かなたん さんが書きました: また、宣言の時配列変数名ごと()でくくっているのはなぜですか?
()でくくらないと、意味が全然違ってしまうからです。

コード:

    char *p[10];
    char (*q)[10];
って書いたとき、
pは、char型へのポインターの『配列』です。配列の要素数は10です。
qは、char型で要素数が10である配列への『ポインター』です。
っていう、すっごく大きな違いがあります。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#22

投稿記事 by かなたん » 14年前

返事が遅れましてすいません・・・
ISLeさん・bitter_foxさん・boxさん回答ありがとうございます。
ISLe さんが書きました:
かなたん さんが書きました:本当は、そういうことも考えに入れておくべきですよね。
私的にお遊びで作るようなプログラムなら構わないかもしれませんが、バグを潰すのはプログラマの重要な仕事です。
個人的にはお遊びで作るにしても最低限の不具合は潰すべきだと思いますけど。
#鼻から悪魔が飛び出してきたら困りますし。
はい。
もちろんその通りです。
勝手な判断で起こりえるエラーを見逃してはいけないですよね。
ISLe さんが書きました:
かなたん さんが書きました:こんな感じですか?
条件式がみごとと言っていいくらい間違ってますよ。
リストの調べている箇所にデータが入っているなら、あるいはリストをすべて調べ終えるまでは
をwhileを回す条件にしようと思って考えていたのですが、そもそもあるいは以下の条件反対ですね。
それから、あのプログラムは実行テストを行っていなかったので何度でも入力&登録できるよう変更して実行テストを行ってみました。
すると、2人目の名前を入れた後無限ループに入ってしまったのか画面がうんともすんとも進まなくなってしまって。
whileの条件は複数指定してはいけないのですね?
で、while条件をすべて調べ終えるまでにし、if文でデータが入ってない個所が発見されたらbreakするように変えて実行してみたところ、うまく登録できる数の上限を超えて登録をしようとするとそれはできないという旨のメッセージを表示させることができました。

コード:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void){
	int i,k;
	char people[20],(*list)[10];
	list=(char (*)[10])calloc(10*20,sizeof(char [10]));
	while(1){
		i=0;
		k=-1;
		printf("あなたの名前は?\n");
		scanf("%s",people);
		if(strcmp(list[0],"")==0){
			strcpy(list[0],people);
			k=0;
		}
		else{
			while(i<10){
				if(strcmp(list[i],"")!=0){
					if(strcmp(list[i],people)==0){
						k=i;
						break;
					}
					i=i+1;
				}
				else{
					break;
				}
			}
			if(k==-1){
				if(i!=10){
					strcpy(list[i],people);
				}
				else{
					printf("ごめんなさい。 これ以上の新規登録はできません。\n");
					exit(1);
				}
			}
		}
	}
	free(list);
	return 0;
}
ISLe さんが書きました:
かなたん さんが書きました:ところで、(*namedata)[100][100]や、(char(*)[100][100])calloc―のように配列要素にキャストしているのはなぜですか?
また、宣言の時配列変数名ごと()でくくっているのはなぜですか?
私はこのような使い方をしたことがないので・・・
確保したメモリを『char[100][100]』型の要素を持つ配列としてアクセスしたいからです。
変数名(というかポインタ派生宣言子)を()で括らないと結合優先順位の問題で別の型に解釈されてしまうので、括る必要があります。
『int』型の要素を持つ配列としてアクセスしたいとき『int *』でキャストするのと同じですよ。
ふつうはtypedefを使って分かりやすくしますけど。
あの、ポインタ派生宣言子ってなんですか?
(それを知らない私はだめだろうか・・・)
たとえばchar *namedata;と宣言すると、namedataという名前のchar型へのポインタを宣言したことになりますよね。
このときの「*」はポインタ宣言子って名前がついてますよね。
ポインタ派生宣言子というのは、このポインタ宣言子の仲間のようなものですか?
括らないと優先順位の問題で―とありますが、bitter_foxさんのchar *p[3]とISLeさんのchar (*namedata)[100][100]は、構文的にやはり違うものなんですか?
*p[3]のほうは*pが3つ分、(*namedata)[100][100]は、100x100の配列を持つ*namedataってことですか?

typedefは、既存の型に新しい名前を付けることができる物らしいですね。
参考 「typedefの使い方」 http://www.geocities.co.jp/bleis_tift/cpp/typedef.html
これを使うとわかりやすくなる・・・具体的にどのようにするのがよいのでしょうか?
box さんが書きました:
かなたん さんが書きました: また、宣言の時配列変数名ごと()でくくっているのはなぜですか?
()でくくらないと、意味が全然違ってしまうからです。

コード:

    char *p[10];
    char (*q)[10];
って書いたとき、
pは、char型へのポインターの『配列』です。配列の要素数は10です。
qは、char型で要素数が10である配列への『ポインター』です。
っていう、すっごく大きな違いがあります。
やはりそういうことになるのですね。
アドレスが10
box さんが書きました:
かなたん さんが書きました: また、宣言の時配列変数名ごと()でくくっているのはなぜですか?
()でくくらないと、意味が全然違ってしまうからです。

コード:

    char *p[10];
    char (*q)[10];
って書いたとき、
pは、char型へのポインターの『配列』です。配列の要素数は10です。
qは、char型で要素数が10である配列への『ポインター』です。
っていう、すっごく大きな違いがあります。
やはりそういうことになるんですね。
「10人分の住所がある」ということと、「10人が住んでいる家の住所がある」ということとでは話が全然違いますもんね。
bitter_fox さんが書きました:画像を添付するにはメンバー登録していただくか外部のアップローダに上げていただいてimgタグを使用してください。
この掲示板にはメンバー登録ができたりするのですね。
外部アップローダーを利用することは考えてなかったですね。
使っているものがないわけではないので、そういうのを使うことも考えてみます。
bitter_fox さんが書きました:
かなたん さんが書きました:[hr]1.実行する前―52% -K
2.画面上に「Input Any Key(1st time)...」と表示されているとき―52% 728K
3.画面上に「Input Any Key(2st time)...」と表示されているとき―55% 728K
4.画面上に「続行するには何かキーを押してください」と表示されているとき―51% 748K
5.終了した後―51% -K
[hr]RAMで言うと、確かにすべて開放したときより、一部しか開放しなかったときのほうが使われているように見えます。
ですが、メモリで言うと変わらない気がするのですが。
2,3の時点でfreeはすでに実行されているはずなので、使ったメモリのうち全部または一部は開放済みってことですよね?
でもbitter_foxさんのようにメモリの値が変化したりはしませんでした。
3のときにメモリの値が上昇していましたが。
えっと、「bitter_foxさんのようにメモリの値が変化したりはしませんでした」とありますが、「3の時にメモリの値が上昇していた」のではないでしょうか?
私の予想では、画面上に「Input Any Key(1st time)...」と表示されているときと画面上に「Input Any Key(2st time)...」と表示されているときですでにメモリのか違法行為を終えた後のはずなので、そこでメモリにbitter_foxさんのような違いが出るのだと思っていいました。
でも、予想通りにはならなかったなぁと。
画面上に「続行するには何かキーを押してください」と表示されているときは、return 0;も終えてすべて終わった後ですよね。
でも、そんなところでメモリの値が増えていたので。
私の考えは違っていますか?
bitter_fox さんが書きました:
かなたん さんが書きました:また、教科書のサンプルプログラムにおいてfreeを1回しかしていないとRAMやメモリの値はどうなるのか調べてみました。
結果はこうなりました。
[hr]6.実行する前―51% -K
7.上のを実行したあと画面上に「続行するには何かキーを押してください」と表示されているとき―51% 748K
8.上を終了した後下を実行する前―51% -K
9.下のを実行したあと画面上に「続行するには何かキーを押してください」と表示されているとき―51% 748K
10.下を終了した後―51% -K
[hr]RAMにもメモリにも特に違いは見られませんでした。
それは、連続したアドレスで作ろうと連続していないアドレスで作ろうと、freeを1回しかしていないからでしょうか?
まず、「実行するには何かキーを押してください」が表示されている時点でOSによるリソースの解放が実行されてしまっているのではないでしょうか?(僕の環境(Win7, VC2008[デバッグなしで開始])ではこのメッセージが表示されている時点でプロセスが死んでしまいメモリを見ることはできませんでした。)
また、確保しているメモリの量が少なすぎるのでキロバイト表示にした際にほとんど変化が見られないように思います。
私の環境(Windows7,Microsoft Visual Studio 2008 [デバッグなしで開始])では、画面上に「続行するには何かキーを押してください」と表示されているときでも、プロセスのイメージ名のところに実行中のプログラムの名前が出てるので調べることができました。
それでもあのプログラムでははっきりとした違いなりが出ないですよね・・・

ISLe
記事: 2650
登録日時: 14年前
連絡を取る:

Re: 文字列が空であるかどうかの判定がうまくできません。

#23

投稿記事 by ISLe » 14年前

かなたん さんが書きました:whileの条件は複数指定してはいけないのですね?
そんなことはないですよ。
18~29行目はこういうふうにできます。

コード:

			while(i<10 && strcmp(list[i],"")!=0){
				if(strcmp(list[i],people)==0){
					k=i;
					break;
				}
				i=i+1;
			}
かなたん さんが書きました:たとえばchar *namedata;と宣言すると、namedataという名前のchar型へのポインタを宣言したことになりますよね。
このときの「*」はポインタ宣言子って名前がついてますよね。
ポインタ派生宣言子というのは、このポインタ宣言子の仲間のようなものですか?
正確には派生型含めて型を表す部分全体を型宣言子と呼んで、派生型の修飾部分だけなら派生宣言子と呼びます。
#それ以外の部分を修飾宣言子と呼びます。
ポインタ宣言子は俗称ですね。それで通じますけど。
かなたん さんが書きました:typedefは、既存の型に新しい名前を付けることができる物らしいですね。
参考 「typedefの使い方」 http://www.geocities.co.jp/bleis_tift/cpp/typedef.html
これを使うとわかりやすくなる・・・具体的にどのようにするのがよいのでしょうか?
こんな感じになります。

コード:

	typedef char namebuf[100]; /* char要素100個の配列をnamebuf型と命名 */
	typedef char hardbuf[20];  /* char要素 20個の配列をhardbuf型と命名 */
	typedef namebuf namedatabuf[100]; /* namebuf要素100個の配列をnamedatabuf型と命名 */
	typedef hardbuf harddatabuf[100]; /* hardbuf要素100個の配列をharddatabuf型と命名 */

    int n = 100;
    namedatabuf *namedata;
    harddatabuf *harddata;
 
    namedata=(namedatabuf *)calloc(n, sizeof(namedatabuf));
    harddata=(harddatabuf *)calloc(n, sizeof(harddatabuf));
 
    /* mallocと対応する分のfree */
    free(namedata);
    free(harddata);

かずま

Re: 文字列が空であるかどうかの判定がうまくできません。

#24

投稿記事 by かずま » 14年前

No.7 のプログラムをみたところ、ユーザの登録と、ユーザごとにゲームの登録
削除を行いたいようですね。
最初の質問で、はっきり C言語と書かれているので何も言わなかったのですが、
でも、やっぱり、この問題は C++ で書くととても簡単になると思えるので、
ちょっと書いてみました。
malloc も callo も free もありません。C++ の new と delete もありません。

コード:

#include <iostream>
#include <map>

using namespace std;

class GameHard : public map<string, string> {
public:
    void add(const string& game, const string& hard) {
        iterator i = find(game);
        if (i != end())
            cout << game << " は既に存在します。\n";
        else {
            (*this)[game] = hard;
            cout << game << " と " << hard << " を登録しました。\n";
        }
    }

    void remove(const string& game) {
        iterator i = find(game);
        if (i == end())
            cout << game << " は存在しません。\n";
        else {
            erase(i);
            cout << game << " を削除しました。\n";
        }
    }

    void printGameHard() {
        for (iterator i = begin(); i != end(); ++i)
            cout << "  " << i->first << ", " << i->second << endl;
    }
};

struct UserGame : public map<string, GameHard> {
    void add(string name) {
        if (find(name) != end())
            cout << name << " は既に登録済みです。\n";
        else {
            (*this)[name] = GameHard();
            cout << name << " を登録しました。\n";
        }
    }

    void remove(const string& name) {
        iterator i = find(name);
        if (i == end())
            cout << name << " は存在しません。\n";
        else {
            erase(i);
            cout << name << " を削除しました。\n";
        }
    }
    GameHard *getGameHard(const string& name) {
        iterator i = find(name);
        if (i == end()) {
            cout << name << " は存在しません。\n";
            return NULL;
        }
        cout << name << " の game と hard を参照できます。\n";
        return &(*this)[name];
    }

    void printUser() {
        for (iterator i = begin(); i != end(); ++i)
            cout << "  " << i->first << endl;
    }
};

int main()
{
    UserGame data;

    data.add("user1");
    data.add("user2");
    data.add("user3");
    data.add("user4");
    data.remove("user2");
    data.add("user4");
    data.printUser();

    GameHard *u1 = data.getGameHard("user1");
    if (u1) {
        u1->add("game1", "hard1");
        u1->add("game2", "hard1");
        u1->add("game3", "hard2");
        u1->printGameHard();
    }
    GameHard *u2 = data.getGameHard("user2");
    if (u2) {
        u2->printGameHard();
    }
    GameHard *u3 = data.getGameHard("user3");
    if (u3) {
        u3->add("game1", "hard1");
        u3->add("game4", "hard3");
        u3->add("game5", "hard3");
        u3->remove("game4");
        u3->printGameHard();
    }
    GameHard *u4 = data.getGameHard("user4");
    if (u4) {
        u4->printGameHard();
    }
}
実行結果

コード:

user1 を登録しました。
user2 を登録しました。
user3 を登録しました。
user4 を登録しました。
user2 を削除しました。
user4 は既に登録済みです。
  user1
  user3
  user4
user1 の game と hard を参照できます。
game1 と hard1 を登録しました。
game2 と hard1 を登録しました。
game3 と hard2 を登録しました。
  game1, hard1
  game2, hard1
  game3, hard2
user2 は存在しません。
user3 の game と hard を参照できます。
game1 と hard1 を登録しました。
game4 と hard3 を登録しました。
game5 と hard3 を登録しました。
game4 を削除しました。
  game1, hard1
  game5, hard3
user4 の game と hard を参照できます。

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#25

投稿記事 by かなたん » 14年前

返事が遅れてすいません・・・
ISLeさん回答ありがとうございます。
ISLe さんが書きました:
かなたん さんが書きました:whileの条件は複数指定してはいけないのですね?
そんなことはないですよ。
18~29行目はこういうふうにできます。

コード:

			while(i<10 && strcmp(list[i],"")!=0){
				if(strcmp(list[i],people)==0){
					k=i;
					break;
				}
				i=i+1;
			}
そうですよね。 できないわけないですよね。
じゃあ、あの時はまた条件間違えたんだな・・・
ISLe さんが書きました:
かなたん さんが書きました:たとえばchar *namedata;と宣言すると、namedataという名前のchar型へのポインタを宣言したことになりますよね。
このときの「*」はポインタ宣言子って名前がついてますよね。
ポインタ派生宣言子というのは、このポインタ宣言子の仲間のようなものですか?
正確には派生型含めて型を表す部分全体を型宣言子と呼んで、派生型の修飾部分だけなら派生宣言子と呼びます。
#それ以外の部分を修飾宣言子と呼びます。
ポインタ宣言子は俗称ですね。それで通じますけど。
ポインタ宣言子は俗称なんですね。
さらに宣言子には修飾宣言子もあると。
また私の知らない宣言子が・・・
ISLe さんが書きました: こんな感じになります。

コード:

	typedef char namebuf[100]; /* char要素100個の配列をnamebuf型と命名 */
	typedef char hardbuf[20];  /* char要素 20個の配列をhardbuf型と命名 */
	typedef namebuf namedatabuf[100]; /* namebuf要素100個の配列をnamedatabuf型と命名 */
	typedef hardbuf harddatabuf[100]; /* hardbuf要素100個の配列をharddatabuf型と命名 */

    int n = 100;
    namedatabuf *namedata;
    harddatabuf *harddata;
 
    namedata=(namedatabuf *)calloc(n, sizeof(namedatabuf));
    harddata=(harddatabuf *)calloc(n, sizeof(harddatabuf));
 
    /* mallocと対応する分のfree */
    free(namedata);
    free(harddata);
typedefは型だけでなく配列ごと新しい名前を付けることができるんですね。
で、namedatabufは[100][100]、harddatabufは[100][20]の配列を持つchar型の変数を宣言したのと同じになっているんですね?
これはこれで使いやすいような気もしますが、私あh[100][100]ならどっちがどっちでも同じだと思うのでいいですが、[100][20]は[20][100]と間違えてしまいそうな・・・
きちんと覚えればそんなことないでしょうけど。
それなら、harddata=(char(*)[100][20] )calloc(n, sizeof(char[100][20] ));のようにはっきりと書いてあるほうがいいかなぁと。

かずま

Re: 文字列が空であるかどうかの判定がうまくできません。

#26

投稿記事 by かずま » 14年前

C++ を C に書き換えてみました。
malloc や strdup が失敗しなかったかどうかのチェックは省略しています。
char [n][100][100] のような無駄な領域の確保はしません。

コード:

#include <stdio.h>
#include <stdlib.h>
 
typedef struct GameHard {
    char *game, *hard;
    struct GameHard *prev, *next;
} GameHard;

typedef struct UserGame {
    char *name;
    GameHard *game;
    struct UserGame *prev, *next;
} UserGame;

void addGame(GameHard *gp, const char *game, const char *hard)
{
    GameHard *p;
    for (p = gp->next; p != gp && strcmp(p->game, game); p = p->next) ;
    if (p != gp) printf("%s は既に存在します。\n", game);
    else {
        p = malloc(sizeof(GameHard));
        p->game = strdup(game),  p->hard = strdup(hard);
        p->next = gp,  p->prev = gp->prev;
        p->prev->next = gp->prev = p;
        printf("%s と %s を登録しました。\n", game, hard);
    }
}
 
void removeGame(GameHard *gp, const char *game) {
    GameHard *p;
    for (p = gp->next; p != gp && strcmp(p->game, game); p = p->next) ;
    if (p == gp) printf("%s は存在しません。\n", game);
    else {
        p->prev->next = p->next,  p->next->prev = p->prev;
        free(p);
        printf("%s を削除しました。\n", game);
    }
}
 
void removeAllGames(GameHard *gp)
{
    GameHard *p = gp->next;
    while (p != gp) {
        GameHard *np = p->next;
        free(p->game), free(p->hard),  free(p);
        p = np;
    }
    gp->prev = gp->next = gp;
}
    
void printAllGameHard(GameHard *gp) {
    GameHard *p;
    for (p = gp->next; p != gp; p = p->next)
        printf("  %s, %s\n", p->game, p->hard);
}
 
void addUser(UserGame *up, const char *name)
{
    UserGame *p;
    for (p = up->next; p != up && strcmp(p->name, name); p = p->next) ;
    if (p != up) printf("%s は既に登録済みです。\n", name);
    else {
        p = malloc(sizeof(UserGame));
        p->name = strdup(name);
        p->prev = up->prev,  p->next = up;
        p->prev->next = up->prev = p;
        p->game = malloc(sizeof(GameHard));
        p->game->game = p->game->hard = NULL;
        p->game->prev = p->game->next = p->game;
        printf("%s を登録しました。\n", name);
    }
}
 
void removeUser(UserGame *up, const char *name)
{
    UserGame *p;
    for (p = up->next; p != up && strcmp(p->name, name); p = p->next) ;
    if (p == up) printf("%s は存在しません\n", name);
    else {
        p->prev->next = p->next,  p->next->prev = p->prev;
        free(p);
        printf("%s を削除しました。\n", name);
   }
}

void removeAllUsers(UserGame *up)
{
    UserGame *p = up->next;
    while (p != up) {
        UserGame *np = p->next;
        removeAllGames(p->game);
        free(p->name), free(p->game), free(p);
        p = np;
    }
    up->prev = up->next = up;
}

GameHard *getGameHard(UserGame *up, const char *name)
{
    UserGame *p;
    for (p = up->next; p != up && strcmp(p->name, name); p = p->next) ;
    if (p == up) {
        printf("%s は存在しません\n", name);
        return NULL;
    }
    printf("%s の game と hard を参照できます。\n", name);
    return p->game;
}
 
void printAllUsers(UserGame *up)
{
    UserGame *p;
    for (p = up->next; p != up; p = p->next)
        printf("  %s\n", p->name);
}
 
int main()
{
    UserGame data;
    data.prev = data.next = &data;
 
    addUser(&data, "user1");
    addUser(&data, "user2");
    addUser(&data, "user3");
    addUser(&data, "user4");
    removeUser(&data, "user2");
    addUser(&data, "user4");
    printAllUsers(&data);
 
    GameHard *u1 = getGameHard(&data, "user1");
    if (u1) {
        addGame(u1, "game1", "hard1");
        addGame(u1, "game2", "hard1");
        addGame(u1, "game3", "hard2");
        printAllGameHard(u1);
    }
    GameHard *u2 = getGameHard(&data, "user2");
    if (u2) {
        printAllGameHard(u2);
    }
    GameHard *u3 = getGameHard(&data, "user3");
    if (u3) {
        addGame(u3, "game1", "hard1");
        addGame(u3, "game4", "hard3");
        addGame(u3, "game5", "hard3");
        removeGame(u3, "game4");
        printAllGameHard(u3);
    }
    GameHard *u4 = getGameHard(&data, "user4");
    if (u4) {
        printAllGameHard(u4);
    }

    removeAllUsers(&data);
    return 0;
}

かずま

Re: 文字列が空であるかどうかの判定がうまくできません。

#27

投稿記事 by かずま » 14年前

No.26 のプログラムに不備がありました。

removeGame() の中の free(p); の前に free(p->game), free(p->hard), を
追加してください。

removeUser() の中の free(p); の前に removeAllGames(p->game); と
free(p->name), free(p->game), を追加してください。

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#28

投稿記事 by かなたん » 14年前

返事が遅れましたが
かずまさん回答ありがとうございます。
かずま さんが書きました:No.7 のプログラムをみたところ、ユーザの登録と、ユーザごとにゲームの登録
削除を行いたいようですね。
最初の質問で、はっきり C言語と書かれているので何も言わなかったのですが、
でも、やっぱり、この問題は C++ で書くととても簡単になると思えるので、
ちょっと書いてみました。
malloc も callo も free もありません。C++ の new と delete もありません。
C++は今勉強中です。
かずまさんのプログラムを見てみたのですが、全然わからないことだらけで・・・;
とりあえずコピーして実行してみようと思ったのですが、なぜかcoutの行にエラーが・・・;
error C2679: 二項演算子 '<<' : 型 'const std::string' の右オペランドを扱う演算子が見つかりません (または変換できません)。
coutのところとかで使っているgameという変数を見てみると、const string& gameとstring型の定数としていますね。
string型を知らなかったので調べてみたところ、stringというヘッダーファイルをインクルードしておかないといけないみたいですね。
参考 「C++ (標準ライブラリ) 第1章string」 http://dixq.net/forum/posting.php?mode= ... bb698bd0ca
その修正を入れると無事に動きました。
そして、わからないところを自分で調べてみました。
でもいまいちわからないところも・・・
普通の配列ではなくmapを利用してゲーム名をキーに本体の名前を登録させたり、ユーザー名をキーにゲーム情報を登録させたりしているのですね?
で、findでiteratorを利用してiという変数にmap内にデータ調べさせたりしているのですね?
thisを使ってmap内にアクセスしているのですね?
53行目、「*」を付けているのはなぜですか?
mapは配列と違ってサイズ気にせず書き込んだりできそうでいいですね。

改めて書いてみると、私がしたかったことは名前を入れてもらって、その名前がなければ新たに登録して、その名前に対してゲームの登録なり削除なりでした。
今ではそれに加えて持っているゲームの表示や扱っているユーザーの変更も加えました。
でもユーザーの削除のこととかまでは考えてなかったです。
そんなものをC++で作ってみたのが以下のプログラムです。

stdafx.h

コード:

// stdafx.h : 標準のシステム インクルード ファイルのインクルード ファイル、または
// 参照回数が多く、かつあまり変更されない、プロジェクト専用のインクルード ファイル
// を記述します。
//

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>

class data_books{
public:
	void newbook(void); //データ領域を確保するための関数
	void enter(void); //ゲームを登録するための関数
	void erace(void); //ゲームを削除するための関数
	void list_up(void); //ゲームを一覧表示させるための関数
	void deletebook(void); //確保したデータ領域を解放する為の関数
private:
	char game_name[100]; //ゲームの名前を仮保存しておくための変数
	char hard_name[30]; //本体の名前を仮保存しておくための変数
	char game_data[130]; //ゲームの情報(ゲームの名前-本体の名前)を仮保存しておくための変数
	char (*data_book)[100]; //ゲームの情報を保存しておくための変数
};

// TODO: プログラムに必要な追加ヘッダーをここで参照してください。
ゲーム管理プログラム.cpp

コード:

// ゲーム管理プログラム.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;

void data_books::newbook(void){
	data_book=(char (*)[100])calloc(130,sizeof(char [100])); //データ保存用にchar [100][130]分の領域を確保 これにより100本分のゲームデータが保存可能
}
void data_books::enter(void){ //これはNo.7で書いている関数をこのプログラム用にちょっと書き換えたもの
	int i=0;
	cout << "これからゲームの登録をするよ。\n終わらせる時はゲーム名のところで0を入れてね。\n";
	while(1){
		cout << "新しく増えたゲームは?\n";
		cin >> game_name;
		if(strcmp(game_name,"0")==0){
			cout << "登録を終了します。\n";
			break;
		}
		cout << "そのゲームは何で遊ぶのかな?\n";
		cin >> hard_name;
		sprintf(game_data,"%s-%s",game_name,hard_name);
		while(strcmp(data_book[i],"")!=0){
			if(strcmp(game_data,databook[i])==0){
				cout << "そのゲームは既に持ってるよ?\n";
				break;
			}
			i=i+1;
		}
		strcpy(data_book[i],game_data);
		cout << game_data <<"を登録しました。\n";
	}
}
void data_books::erace(void){//これもNo.7で書いている関数をこのプログラム用にちょっと書き換えたもの
	int i=0,tf=0,k;
	cout << "これからゲームの削除をするよ。\n終わらせる時はゲーム名のところで0を入れてね。\n";
	while(1){
		if(strcmp(data_book[0],"")==0){
			cout << "あなたはゲームを持っていません。\n削除をやめます。\n";
			break;
		}
		else{
			cout <<"捨てたゲームは?\n";
			cin >> game_name;
			if(strcmp(game_name,"0")==0){
				cout << "削除を終了します。\n";
				break;
			}
			cout << "そのゲームは何で遊ぶのかな?\n";
			cin >> hard_name;
			sprintf(game_data,"%s-%s",game_name,hard_name);
			while(strcmp(data_book[i],"")!=0){
				if(strcmp(game_data,data_book[i])==0){
					strcpy(data_book[i],"");
					tf=1;
					k=i+1;
					while(strcmp(data_book[k],"")!=0){
						strcpy(data_book[(k-1)],data_book[k]);
						k=k+1;
					}
					strcpy(data_book[(k-1)],"");
					cout << game_data << "を削除しました。\n";
					break;
				}
				i=i+1;
			}
			if(tf==0){
				cout << "そのゲームは持ってないよ?\n";
			}
		}
	}
}
void data_books::list_up(void){ //data_book内にあるゲームデータを表示して、何本持っていたかも表示する。 1本も持っていなかったら持っていないという
	int i=0;
	cout << "あなたが持っているゲームを表示します。\n";
	while(strcmp(data_book[i],"")!=0){
		cout << data_book[i] << "\n";
		i=i+1;
	}
	if(i!=0){
		cout << "あなたは" << i << "本のゲームを持っています。\n";
	}
	else{
		cout << "あなたはゲームを持っていません。\n";
	}
}
void data_books::deletebook(void){
	free(data_book); //newbookで確保した領域の解放
}

int _tmain(int argc, _TCHAR* argv[])
{
	const int n=100; //n人を勝手に100人に設定。
	int i,k,new_user=0,menu=-1; //i,kはユーザーの登録等の判断で利用している変数 new_userは新規ユーザーか判断するために利用している変数 menuはユーザーが何をしたいのか判断するために利用している変数
	char **user_list; //ここにユーザーを登録する
	char user_name[20];
	data_books user[n]; //クラスに対してn(100)人分のオブジェクトを生成
	user_list=(char **)calloc(n,sizeof(char *));
	for(i=0;i<n;i++){
		user_list[i]=(char *)calloc(20,sizeof(char));
	}
	while(menu!=0){
		menu=-1;
		i=0;
		k=-1;
		new_user=0;
		cout << "あなたの名前は?\n"; //名前を入力してもらって、その名前が登録済みかどうか調べる
		cin >> user_name;
		if(strcmp(user_list[0],"")==0){ データが1つもなければ新規登録
			strcpy(user_list[0],user_name);
			k=0;
			new_user=1; //新規ユーザーであると言う
		}
		else{
			while(strcmp(user_list[i],"")!=0){ //データがあれば登録済みかどうか調べる
				if(strcmp(user_list[i],user_name)==0){
					k=i;
					break;
				}
				i=i+1;
			}
			if(k==-1){ //登録されていなければ新規登録
				strcpy(user_list[i],user_name);
				new_user=1;
				k=i;
			}
		}
		if(new_user==1){ //新規ユーザーのために新しく領域を確保
			cout << user_name << "さんは新規ユーザーなので、新しい本をつくります。\n";
			user[k].newbook();
		}
		while(menu!=0 && menu!=4){ ユーザーの変更か終了が指定されるまで繰り返す
			cout << user_name << "さんのやりたいのはなにかな?\n";
			cout << "1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示\n";
			cout << "4.ユーザーの変更 0. 終了\n";
			while(1){
				cin >> menu;
				if(menu<0 || menu>4){
					cout << "なに押したの?\n";
				}
				else{
					break;
				}
			}
			if(menu==1){
				user[k].enter(); //登録を実行
			}
			else if(menu==2){
				user[k].erace(); //削除を実行
			}
			else if(menu==3){
				user[k].list_up(); //一覧表示を実行
			}
		}
	}
	cout << "プログラムを終了します。\n";
	for(i=0;i<=k;i++){ //このプログラムではデータが残らないので、そのことを記載しておく
		cout << "残念ながら、このプログラムでは" << user_list[i] << "さんのデータは消えてしまいます。\n";
		user[i].deletebook();
	}
	free(user_list); //ユーザーリストも解散
	return 0;
}
メイン関数内でユーザーリストを管理し、クラスでユーザーごとに行うデータ領域の確保や解放、ゲームの登録・削除・一覧表示をまとめてみました。
実行結果はこんな感じです。

コード:

あなたの名前は?
test1
test1さんは新規ユーザーなので、新しい本を作ります。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
test1さんが持っているゲームの一覧を表示します。
test1さんはゲームを持っていません。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
1
これからゲームの登録をするよ。
終わらせる時はゲーム名のところで0って書いてね。
新しく増えたゲームは?
test
そのゲームは何で遊ぶのかな?
ds
test-ds
を登録しました。
新しく増えたゲームは?
stop
そのゲームは何で遊ぶのかな?
wii
stop-wii
を登録しました。
新しく増えたゲームは?
and
そのゲームは何で遊ぶのかな?
psp
and-psp
を登録しました。
新しく増えたゲームは?
any
そのゲームは何で遊ぶのかな?
ps3
any-ps3
を登録しました。
新しく増えたゲームは?
0
登録を終了します。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
test1さんが持っているゲームの一覧を表示します。
test-ds
stop-wii
and-psp
any-ps3
test1さんは4本のゲームを持っています。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
2
捨てたゲームは?
stop
そのゲームは何で遊ぶのかな?
wii
stop-wii
を削除しました。
捨てたゲームは?
0
削除を終了します。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
test1さんが持っているゲームの一覧を表示します。
test-ds
and-psp
any-ps3
test1さんは3本のゲームを持っています。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
4
あなたの名前は?
テスト2
テスト2さんは新規ユーザーなので、新しい本を作ります。
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
テスト2さんが持っているゲームの一覧を表示します。
テスト2さんはゲームを持っていません。
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
1
これからゲームの登録をするよ。
終わらせる時はゲーム名のところで0って書いてね。
新しく増えたゲームは?
テスト
そのゲームは何で遊ぶのかな?
ds
テスト-ds
を登録しました。
新しく増えたゲームは?
ストップ
そのゲームは何で遊ぶのかな?
wii
ストップ-wii
を登録しました。
新しく増えたゲームは?
アンド
そのゲームは何で遊ぶのかな?
psp
アンド-psp
を登録しました。
新しく増えたゲームは?
エニー
そのゲームは何で遊ぶのかな?
ps3
エニー-ps3
を登録しました。
新しく増えたゲームは?
0
登録を終了します。
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
テスト2さんが持っているゲームの一覧を表示します。
テスト-ds
ストップ-wii
アンド-psp
エニー-ps3
テスト2さんは4本のゲームを持っています。
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
2
捨てたゲームは?
アンド
そのゲームは何で遊ぶのかな?
psp
アンド-psp
を削除しました。
捨てたゲームは?
0
削除を終了します。
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
テスト2さんが持っているゲームの一覧を表示します。
テスト-ds
ストップ-wii
エニー-ps3
テスト2さんは3本のゲームを持っています。
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
4
あなたの名前は?
test1
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
test1さんが持っているゲームの一覧を表示します。
test-ds
and-psp
any-ps3
test1さんは3本のゲームを持っています。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
4
あなたの名前は?
テスト2
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
テスト2さんが持っているゲームの一覧を表示します。
テスト-ds
ストップ-wii
エニー-ps3
テスト2さんは3本のゲームを持っています。
プログラムを終了します。
続行するには何かキーを押してください . . .
名前の入力が英数のみでも日本語ありでもうまくいきました。

このプログラムでも101以降に対してはなにも処理していないですが・・・一応私がしたいと思っていたことはできたと思っています。

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#29

投稿記事 by かなたん » 14年前

あ・・・codeの書き方間違えました・・・;

stdafx.h

コード:

// stdafx.h : 標準のシステム インクルード ファイルのインクルード ファイル、または
// 参照回数が多く、かつあまり変更されない、プロジェクト専用のインクルード ファイル
// を記述します。
//

#pragma once

#include "targetver.h"

#include <stdio.h>
#include <tchar.h>

class data_books{
public:
	void newbook(void); //データ領域を確保するための関数
	void enter(void); //ゲームを登録するための関数
	void erace(void); //ゲームを削除するための関数
	void list_up(void); //ゲームを一覧表示させるための関数
	void deletebook(void); //確保したデータ領域を解放する為の関数
private:
	char game_name[100]; //ゲームの名前を仮保存しておくための変数
	char hard_name[30]; //本体の名前を仮保存しておくための変数
	char game_data[130]; //ゲームの情報(ゲームの名前-本体の名前)を仮保存しておくための変数
	char (*data_book)[100]; //ゲームの情報を保存しておくための変数
};

// TODO: プログラムに必要な追加ヘッダーをここで参照してください。
ゲーム管理プログラム.cpp

コード:

// ゲーム管理プログラム.cpp : コンソール アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include <iostream>
#include <stdlib.h>
#include <stdio.h>
using namespace std;

void data_books::newbook(void){
	data_book=(char (*)[100])calloc(130,sizeof(char [100])); //データ保存用にchar [100][130]分の領域を確保 これにより100本分のゲームデータが保存可能
}
void data_books::enter(void){ //これはNo.7で書いている関数をこのプログラム用にちょっと書き換えたもの
	int i=0;
	cout << "これからゲームの登録をするよ。\n終わらせる時はゲーム名のところで0を入れてね。\n";
	while(1){
		cout << "新しく増えたゲームは?\n";
		cin >> game_name;
		if(strcmp(game_name,"0")==0){
			cout << "登録を終了します。\n";
			break;
		}
		cout << "そのゲームは何で遊ぶのかな?\n";
		cin >> hard_name;
		sprintf(game_data,"%s-%s",game_name,hard_name);
		while(strcmp(data_book[i],"")!=0){
			if(strcmp(game_data,databook[i])==0){
				cout << "そのゲームは既に持ってるよ?\n";
				break;
			}
			i=i+1;
		}
		strcpy(data_book[i],game_data);
		cout << game_data <<"を登録しました。\n";
	}
}
void data_books::erace(void){//これもNo.7で書いている関数をこのプログラム用にちょっと書き換えたもの
	int i=0,tf=0,k;
	cout << "これからゲームの削除をするよ。\n終わらせる時はゲーム名のところで0を入れてね。\n";
	while(1){
		if(strcmp(data_book[0],"")==0){
			cout << "あなたはゲームを持っていません。\n削除をやめます。\n";
			break;
		}
		else{
			cout <<"捨てたゲームは?\n";
			cin >> game_name;
			if(strcmp(game_name,"0")==0){
				cout << "削除を終了します。\n";
				break;
			}
			cout << "そのゲームは何で遊ぶのかな?\n";
			cin >> hard_name;
			sprintf(game_data,"%s-%s",game_name,hard_name);
			while(strcmp(data_book[i],"")!=0){
				if(strcmp(game_data,data_book[i])==0){
					strcpy(data_book[i],"");
					tf=1;
					k=i+1;
					while(strcmp(data_book[k],"")!=0){
						strcpy(data_book[(k-1)],data_book[k]);
						k=k+1;
					}
					strcpy(data_book[(k-1)],"");
					cout << game_data << "を削除しました。\n";
					break;
				}
				i=i+1;
			}
			if(tf==0){
				cout << "そのゲームは持ってないよ?\n";
			}
		}
	}
}
void data_books::list_up(void){ //data_book内にあるゲームデータを表示して、何本持っていたかも表示する。 1本も持っていなかったら持っていないという
	int i=0;
	cout << "あなたが持っているゲームを表示します。\n";
	while(strcmp(data_book[i],"")!=0){
		cout << data_book[i] << "\n";
		i=i+1;
	}
	if(i!=0){
		cout << "あなたは" << i << "本のゲームを持っています。\n";
	}
	else{
		cout << "あなたはゲームを持っていません。\n";
	}
}
void data_books::deletebook(void){
	free(data_book); //newbookで確保した領域の解放
}

int _tmain(int argc, _TCHAR* argv[])
{
	const int n=100; //n人を勝手に100人に設定。
	int i,k,new_user=0,menu=-1; //i,kはユーザーの登録等の判断で利用している変数 new_userは新規ユーザーか判断するために利用している変数 menuはユーザーが何をしたいのか判断するために利用している変数
	char **user_list; //ここにユーザーを登録する
	char user_name[20];
	data_books user[n]; //クラスに対してn(100)人分のオブジェクトを生成
	user_list=(char **)calloc(n,sizeof(char *));
	for(i=0;i<n;i++){
		user_list[i]=(char *)calloc(20,sizeof(char));
	}
	while(menu!=0){
		menu=-1;
		i=0;
		k=-1;
		new_user=0;
		cout << "あなたの名前は?\n"; //名前を入力してもらって、その名前が登録済みかどうか調べる
		cin >> user_name;
		if(strcmp(user_list[0],"")==0){ データが1つもなければ新規登録
			strcpy(user_list[0],user_name);
			k=0;
			new_user=1; //新規ユーザーであると言う
		}
		else{
			while(strcmp(user_list[i],"")!=0){ //データがあれば登録済みかどうか調べる
				if(strcmp(user_list[i],user_name)==0){
					k=i;
					break;
				}
				i=i+1;
			}
			if(k==-1){ //登録されていなければ新規登録
				strcpy(user_list[i],user_name);
				new_user=1;
				k=i;
			}
		}
		if(new_user==1){ //新規ユーザーのために新しく領域を確保
			cout << user_name << "さんは新規ユーザーなので、新しい本をつくります。\n";
			user[k].newbook();
		}
		while(menu!=0 && menu!=4){ ユーザーの変更か終了が指定されるまで繰り返す
			cout << user_name << "さんのやりたいのはなにかな?\n";
			cout << "1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示\n";
			cout << "4.ユーザーの変更 0. 終了\n";
			while(1){
				cin >> menu;
				if(menu<0 || menu>4){
					cout << "なに押したの?\n";
				}
				else{
					break;
				}
			}
			if(menu==1){
				user[k].enter(); //登録を実行
			}
			else if(menu==2){
				user[k].erace(); //削除を実行
			}
			else if(menu==3){
				user[k].list_up(); //一覧表示を実行
			}
		}
	}
	cout << "プログラムを終了します。\n";
	for(i=0;i<=k;i++){ //このプログラムではデータが残らないので、そのことを記載しておく
		cout << "残念ながら、このプログラムでは" << user_list[i] << "さんのデータは消えてしまいます。\n";
		user[i].deletebook();
	}
	free(user_list); //ユーザーリストも解散
	return 0;
}
実行結果

コード:

あなたの名前は?
test1
test1さんは新規ユーザーなので、新しい本を作ります。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
test1さんが持っているゲームの一覧を表示します。
test1さんはゲームを持っていません。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
1
これからゲームの登録をするよ。
終わらせる時はゲーム名のところで0って書いてね。
新しく増えたゲームは?
test
そのゲームは何で遊ぶのかな?
ds
test-ds
を登録しました。
新しく増えたゲームは?
stop
そのゲームは何で遊ぶのかな?
wii
stop-wii
を登録しました。
新しく増えたゲームは?
and
そのゲームは何で遊ぶのかな?
psp
and-psp
を登録しました。
新しく増えたゲームは?
any
そのゲームは何で遊ぶのかな?
ps3
any-ps3
を登録しました。
新しく増えたゲームは?
0
登録を終了します。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
test1さんが持っているゲームの一覧を表示します。
test-ds
stop-wii
and-psp
any-ps3
test1さんは4本のゲームを持っています。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
2
捨てたゲームは?
stop
そのゲームは何で遊ぶのかな?
wii
stop-wii
を削除しました。
捨てたゲームは?
0
削除を終了します。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
test1さんが持っているゲームの一覧を表示します。
test-ds
and-psp
any-ps3
test1さんは3本のゲームを持っています。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
4
あなたの名前は?
テスト2
テスト2さんは新規ユーザーなので、新しい本を作ります。
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
テスト2さんが持っているゲームの一覧を表示します。
テスト2さんはゲームを持っていません。
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
1
これからゲームの登録をするよ。
終わらせる時はゲーム名のところで0って書いてね。
新しく増えたゲームは?
テスト
そのゲームは何で遊ぶのかな?
ds
テスト-ds
を登録しました。
新しく増えたゲームは?
ストップ
そのゲームは何で遊ぶのかな?
wii
ストップ-wii
を登録しました。
新しく増えたゲームは?
アンド
そのゲームは何で遊ぶのかな?
psp
アンド-psp
を登録しました。
新しく増えたゲームは?
エニー
そのゲームは何で遊ぶのかな?
ps3
エニー-ps3
を登録しました。
新しく増えたゲームは?
0
登録を終了します。
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
テスト2さんが持っているゲームの一覧を表示します。
テスト-ds
ストップ-wii
アンド-psp
エニー-ps3
テスト2さんは4本のゲームを持っています。
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
2
捨てたゲームは?
アンド
そのゲームは何で遊ぶのかな?
psp
アンド-psp
を削除しました。
捨てたゲームは?
0
削除を終了します。
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
テスト2さんが持っているゲームの一覧を表示します。
テスト-ds
ストップ-wii
エニー-ps3
テスト2さんは3本のゲームを持っています。
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
4
あなたの名前は?
test1
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
test1さんが持っているゲームの一覧を表示します。
test-ds
and-psp
any-ps3
test1さんは3本のゲームを持っています。
test1さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
4
あなたの名前は?
テスト2
テスト2さんのやりたいのはなにかな?
1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示
4.ユーザーの変更 0. 終了
3
テスト2さんが持っているゲームの一覧を表示します。
テスト-ds
ストップ-wii
エニー-ps3
テスト2さんは3本のゲームを持っています。
プログラムを終了します。
続行するには何かキーを押してください . . .

かずま

Re: 文字列が空であるかどうかの判定がうまくできません。

#30

投稿記事 by かずま » 14年前

かなたん さんが書きました: string型を知らなかったので調べてみたところ、stringというヘッダーファイルをインクルードしておかないといけないみたいですね。
これは失礼しました。その通りです。
gcc も bcc(Borland) も、<iostream> を include していれば <string> はなくても良いようなので、気づきませんでした。
確かに VC++ では必要ですね。
C++ を C に書き直したほうも、strcmp を使っているのに <string.h> の include を忘れています。
かなたん さんが書きました: で、findでiteratorを利用してiという変数にmap内にデータ調べさせたりしているのですね?
thisを使ってmap内にアクセスしているのですね?
そうです。this を使ったのは、add() が GameHard のメンバ関数だからです。
それと、(*this)[game] = hard; を実行すると、game が既に存在していても何も言わずに上書きしてしまうので、先に find() で調べるようにしました。
あとから思ったのですが、find() で検索したのに (*this)[game] = hard; と書くと operator[] でまた検索を実行することになるので無駄ですね。add() は次のよう書くべきでした。this は不要です。

コード:

    void add(const string& game, const string& hard) {
        if (insert(make_pair(game, hard)).second)
            cout << game << " と " << hard << " を登録しました。\n";
        else
            cout << game << " は既に存在します。\n";
    }
かなたん さんが書きました: 53行目、「*」を付けているのはなぜですか?
メンバ関数の getGameHard が、GameHard へのポインタを返すからです。
ここ関数の return も operator[] を使っていて無駄ですね。
return &(*this)[name] を return &i->second; に修正します。this は不要です。
かなたん さんが書きました: そんなものをC++で作ってみたのが以下のプログラムです。
C++ で書くのなら char game_name[100]; なんてせずに string game_name; にしたほうが楽なのに。
さらに、配列の代わりに、vector, list, map などを使うと、要素の個数を気にせずに済みます。

かずま

Re: 文字列が空であるかどうかの判定がうまくできません。

#31

投稿記事 by かずま » 14年前

かずま さんが書きました:C++ で書くのなら char game_name[100]; なんてせずに string game_name; にしたほうが楽なのに。
というとで、書いてみました。
map にはメンバ関数に find があるけど、list にはないので <algorithm> の find を使っています。

コード:

#include <iostream>
#include <sstream>
#include <string>
#include <list>
#include <map>
#include <algorithm>

using namespace std;

class data_books {
public:
    void enter();
    void erase();
    void list_up();
private:
    string game_name, hard_name, game_data;
    list<string> data_book;
};

void data_books::enter() {
    cout << "これからゲームの登録をするよ。\n"
            "終わらせる時はゲーム名のところで0を入れてね。\n";
    while (1) {
        cout << "新しく増えたゲームは?\n";
        cin >> game_name;
        if (game_name == "0") {
            cout << "登録を終了します。\n";
            break;
        }
        cout << "そのゲームは何で遊ぶのかな?\n";
        cin >> hard_name;
        game_data = game_name + "-" + hard_name;
        if (find(data_book.begin(), data_book.end(), game_data) != data_book.end())
            cout << "そのゲームは既に持ってるよ?\n";
        else {
            data_book.push_back(game_data);
            cout << game_data <<"を登録しました。\n";
        }
    }
}

void data_books::erase() {
    cout << "これからゲームの削除をするよ。\n"
            "終わらせる時はゲーム名のところで0を入れてね。\n";
    while (1) {
        if (data_book.size() == 0) {
            cout << "あなたはゲームを持っていません。\n削除をやめます。\n";
            break;
        }
        cout <<"捨てたゲームは?\n";
        cin >> game_name;
        if (game_name == "0") {
            cout << "削除を終了します。\n";
            break;
        }
        cout << "そのゲームは何で遊ぶのかな?\n";
        cin >> hard_name;
        game_data = game_name + "-" + hard_name;
        list<string>::iterator i = find(data_book.begin(), data_book.end(), game_data);
        if (i == data_book.end())
            cout << "そのゲームは持ってないよ?\n";
        else {
            data_book.erase(i);
            cout << game_data << "を削除しました。\n";
        }
    }
}

void data_books::list_up(void) {
    cout << "あなたが持っているゲームを表示します。\n";
    size_t n = data_book.size();
    if (n == 0)
        cout << "あなたはゲームを持っていません。\n";
    else {
        list<string>::iterator i;
        for (i = data_book.begin(); i != data_book.end(); ++i)
            cout << *i << "\n";
        cout << "あなたは" << n << "本のゲームを持っています。\n";
    }
}
 
int main(int argc, char *argv[])
{
    int menu = -1;
    map<string, data_books> user_list;

    while (menu != 0) {
        menu = -1;
        string user_name;
        cout << "あなたの名前は?\n";
        cin >> user_name;
        if (user_list.find(user_name) == user_list.end()) {
            cout << user_name << "さんは新規ユーザーなので、新しい本をつくります。\n";
            user_list[user_name] = data_books();
        }
        data_books *data = &user_list[user_name];
        while (menu != 0 && menu != 4) {
            cout << user_name << "さんのやりたいのはなにかな?\n"
                "1.ゲームの追加 2.ゲームの削除 3.持っているゲームの一覧表示\n"
                "4.ユーザーの変更 0. 終了\n";
            while (1) {
                string str;
                cin >> str;
                istringstream iss(str);
                iss >> menu;
                if (menu >= 0 && menu <= 4) break;
                cout << "なに押したの?\n";
            }  
            switch (menu) {
            case 1: data->enter();   break;
            case 2: data->erase();   break;
            case 3: data->list_up(); break;
            }
        }
    }
    cout << "プログラムを終了します。\n";
    map<string, data_books>::iterator i;
    for (i = user_list.begin(); i != user_list.end() ; ++i)
        cout << "残念ながら、このプログラムでは" << i->first
             << "さんのデータは消えてしまいます。\n";
    return 0;
}

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#32

投稿記事 by かなたん » 14年前

かずま さんが書きました:
かなたん さんが書きました: string型を知らなかったので調べてみたところ、stringというヘッダーファイルをインクルードしておかないといけないみたいですね。
これは失礼しました。その通りです。
gcc も bcc(Borland) も、<iostream> を include していれば <string> はなくても良いようなので、気づきませんでした。
確かに VC++ では必要ですね。
C++ を C に書き直したほうも、strcmp を使っているのに <string.h> の include を忘れています。
gcc・・・DOS窓から実行させるやり方ですっけ?
どこかで聞いた気がするけど実際にやったことはないです。
bccは聞いたことすらないです。
コンパイラだかによっては必要だったり必要でなかったりすることもあるのですね。
かずま さんが書きました:
かなたん さんが書きました: で、findでiteratorを利用してiという変数にmap内にデータ調べさせたりしているのですね?
thisを使ってmap内にアクセスしているのですね?
そうです。this を使ったのは、add() が GameHard のメンバ関数だからです。
それと、(*this)[game] = hard; を実行すると、game が既に存在していても何も言わずに上書きしてしまうので、先に find() で調べるようにしました。
あとから思ったのですが、find() で検索したのに (*this)[game] = hard; と書くと operator[] でまた検索を実行することになるので無駄ですね。add() は次のよう書くべきでした。this は不要です。

コード:

    void add(const string& game, const string& hard) {
        if (insert(make_pair(game, hard)).second)
            cout << game << " と " << hard << " を登録しました。\n";
        else
            cout << game << " は既に存在します。\n";
    }
かなたん さんが書きました: 53行目、「*」を付けているのはなぜですか?
メンバ関数の getGameHard が、GameHard へのポインタを返すからです。
ここ関数の return も operator[] を使っていて無駄ですね。
return &(*this)[name] を return &i->second; に修正します。this は不要です。
かなたん さんが書きました: そんなものをC++で作ってみたのが以下のプログラムです。
C++ で書くのなら char game_name[100]; なんてせずに string game_name; にしたほうが楽なのに。
さらに、配列の代わりに、vector, list, map などを使うと、要素の個数を気にせずに済みます。
string型は、今回調べてみてVBA(Excelのマクロ)で使ってたものと同じ(私が知っている範囲では)だなぁとは思いました。
ですが、mao等も含めて今回まで知りませんでした。
だから、そういう便利なものを利用せずあんな書き方してたのです。
学校ではクラスとかの話は習ったのですが、そういうことは習ってないので・・・

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#33

投稿記事 by かなたん » 14年前

かずま さんが書きました:
かずま さんが書きました:C++ で書くのなら char game_name[100]; なんてせずに string game_name; にしたほうが楽なのに。
というとで、書いてみました。
map にはメンバ関数に find があるけど、list にはないので <algorithm> の find を使っています。
わざわざ改変ありがとうございます。
是非今回やこれからの参考にさせてもらいます。

かずま

Re: 文字列が空であるかどうかの判定がうまくできません。

#34

投稿記事 by かずま » 14年前

かなたん さんが書きました:わざわざ改変ありがとうございます。
是非今回やこれからの参考にさせてもらいます。
適当に書いているから、不適切なところもありますよ。注意してください。例えば、

コード:

    if (user_list.find(user_name) == user_list.end()) {
        cout << user_name << "さんは新規ユーザーなので、新しい本をつくります。\n";
        user_list[user_name] = data_books();
    }
    data_books *data = &user_list[user_name];
この中の、user_list[user_name] = data_books(); は不要です。
「新規ユーザなので、新しい本をつくります」というのを受けて、つい書いてしまいましたが、
新規ユーザの場合、次の data_books *data = &user_list[user_name]; のところで、
operator[] が user_name を検索したとき、user_list の中に user_name と data_books
オブジェクトのペアを作ってしまいますから、その代入は要らないのです。
もっと言えば、user_list[user_name] = data_books(); は左辺で data_booksオブジェクトを作り、
右辺で作った data_booksオブジェクトの値を代入していますから、無駄なことをやっているわけです。
右辺の data_booksオブジェクトは、} のところ (ブロックの終わり) で解体されてしまいます。
さらに、最初の find で user_name が見つかった場合でも、data = &user_list[user_name]
の operator[] でまた検索をしてしまうという無駄なことをやってしまいます。
この部分は次のように書くべきでした。

コード:

    data_books *data;
    map<string, data_books>::iterator i = user_list.find(user_name);
    if (i == user_list.end()) {
        cout << user_name << "さんは新規ユーザーなので、新しい本をつくります。\n";
        data = &user_list[user_name];
    } else  // user_name が見つかった場合、i の指しているペアの2つ目が data_books だから
        data = &i->second;

かなたん

Re: 文字列が空であるかどうかの判定がうまくできません。

#35

投稿記事 by かなたん » 14年前

かずま さんが書きました:
かなたん さんが書きました:わざわざ改変ありがとうございます。
是非今回やこれからの参考にさせてもらいます。
適当に書いているから、不適切なところもありますよ。注意してください。例えば、

コード:

    if (user_list.find(user_name) == user_list.end()) {
        cout << user_name << "さんは新規ユーザーなので、新しい本をつくります。\n";
        user_list[user_name] = data_books();
    }
    data_books *data = &user_list[user_name];
この中の、user_list[user_name] = data_books(); は不要です。
「新規ユーザなので、新しい本をつくります」というのを受けて、つい書いてしまいましたが、
新規ユーザの場合、次の data_books *data = &user_list[user_name]; のところで、
operator[] が user_name を検索したとき、user_list の中に user_name と data_books
オブジェクトのペアを作ってしまいますから、その代入は要らないのです。
もっと言えば、user_list[user_name] = data_books(); は左辺で data_booksオブジェクトを作り、
右辺で作った data_booksオブジェクトの値を代入していますから、無駄なことをやっているわけです。
右辺の data_booksオブジェクトは、} のところ (ブロックの終わり) で解体されてしまいます。
さらに、最初の find で user_name が見つかった場合でも、data = &user_list[user_name]
の operator[] でまた検索をしてしまうという無駄なことをやってしまいます。
この部分は次のように書くべきでした。

コード:

    data_books *data;
    map<string, data_books>::iterator i = user_list.find(user_name);
    if (i == user_list.end()) {
        cout << user_name << "さんは新規ユーザーなので、新しい本をつくります。\n";
        data = &user_list[user_name];
    } else  // user_name が見つかった場合、i の指しているペアの2つ目が data_books だから
        data = &i->second;
あ。 はい。 わかりました。

閉鎖

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