クラスを使ったメニュー処理

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

クラスを使ったメニュー処理

#1

投稿記事 by おにぎり » 3年前

コード:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>



enum 
{
	MODE_FINISH,     // 終了
	MODE_INPUT,        // 入力
	MODE_OUYPUT,       // 出力
	MODE_SAVE,         // セーブ
	MODE_LORD,         // ロード
	MODE_MAX
};

//=========================================
//
//モンスタークラス 入力処理 出力処理
//
//=========================================
class Monster
{
	int hp;
public:
	void inputData();
	void outputData();

};

void Monster::inputData()
{
	printf("hp ->");
	scanf("%d",&hp);
}

void Monster::outputData()
{
	printf("hpは%d", hp);
}

//=========================================
//
//データクラス 入力処理 出力処理
//
//=========================================
class Data 
{
	int num;			// 入力モンスター数
	Monster *monst;		// Monsterクラスのポインタ
	void input();		// 入力処理
	void output();		// 出力処理

	
public:

	void selectMenu(void);		//メニュー処理関数

	Data()	// コンストラクタ
	{
		printf("コンストラクタを実行します\n");
	}
	~Data()	// デストラクタ
	{
		printf("デストラクタを実行します\n");
	}
};

void Data::input(void)
{
	printf("何体のデータを入力しますか?");
	scanf("%d", &num);

	Monster* monst = new Monster[num];

	for (int i = 0; i < num; i++)
	{
		printf("<%d>体目のデータ>\n", i + 1);
		monst[i].inputData();
	}


	delete[] monst;

	rewind(stdin);

	getchar();

}

void Data::output(void)
{
	for (int i = 0; i < num; i++)
	{
		printf("<%d>体目のデータ>\n", i + 1);
		monst[i].outputData();
	}
}

//===================================
//
//  メニュー処理
//
//===================================
void Data::selectMenu(void)
{
	bool loop = true;
	while (loop)
	{
		printf("======================================\n");
		printf("%d:データ入力\n", MODE_INPUT);
		printf("%d:データの表示\n", MODE_OUYPUT);
		printf("%d:データをセーブ\n", MODE_SAVE);
		printf("%d:データをロード\n", MODE_LORD);
		printf("%d:プログラムを終了\n", MODE_FINISH);
		printf("======================================\n");

		int mode = 0;
		while (mode < MODE_FINISH || mode >= MODE_MAX)
		{
			scanf("%d", &mode);
		}

		switch (mode)
		{

		case MODE_INPUT:  // 入力処理
			input();
			break;

		case MODE_OUYPUT:  // 出力処理
				//output();
			break;

		case MODE_SAVE:    // セーブ
				//save();
			break;

		case MODE_LORD:    // ロード
				//lord();
			break;

		case MODE_FINISH: // 終了
			loop = false;
			break;

		}
	}

}

//============================
//
//メイン処理
//
//============================
int main(void)
{

	Data data;

	//モード選択
	data.selectMenu();

	//入力待ち
	rewind(stdin);
	getchar();


	return 0;
}

クラスを使ったメニュ-処理を作っているんですが、メニュー選択で1を押してもデータ入力「input関数」に移行せずプログラムが終了してしまいます。

移行の処理がうまくいってないみたいなんですが、分からないので教えていただきたいです。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: クラスを使ったメニュー処理

#2

投稿記事 by みけCAT » 3年前

C++のenumの値は明示的に指定しないと0から始まるので、
mode = 0のときwhile文の条件の mode < MODE_FINISH || mode >= MODE_MAX
すなわち mode < 0 || mode >= 5 は偽になり、入力を受け付けずにそのままswitch文へ行き、
case MODE_FINISH:を実行して終了することになります。
modeを0ではなく-1やMODE_MAXなどwhile文の条件を真にする値で初期化すると改善するでしょう。
もしくは、while文のかわりにdo-while文を使うようにしてもいいでしょう。

ちなみに、このコードではData::output()を実行しないので問題にはならないですが、
Data::input()では確保した配列を開放してしまい、他にData::monstに有効なポインタを代入する処理は無いので、
Data::output()をData::numが正の状態で実行するのは危険です。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

おにぎり

Re: クラスを使ったメニュー処理

#3

投稿記事 by おにぎり » 3年前

コード:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>



enum
{
	MODE_FINISH,     // 終了
	MODE_INPUT,        // 入力
	MODE_OUYPUT,       // 出力
	MODE_SAVE,         // セーブ
	MODE_LORD,         // ロード
	MODE_MAX
};

//=========================================
//
//モンスタークラス 入力処理 出力処理
//
//=========================================
class Monster
{ 
	int hp;		// HP
	int atk;	// 攻撃力
	int def;	// 防御力

public:
	void inputData(void);
	void outputData(void);

	Monster(void)
	{
		printf("コンストラクタを実行します\n");
	}
	~Monster(void)
	{
		printf("デストラクタを実行します\n");
	}

};

void Monster::inputData(void)
{
	printf("hp ->");
	scanf("%d", &hp);

	printf("atk ->");
	scanf("%d", &atk);

	printf("def ->");
	scanf("%d", &def);
}

void Monster::outputData(void)
{
	printf("hpは%d \n", hp);

	printf("atkは%d \n", atk);

	printf("defは%d\n", def);
}

//=========================================
//
//データクラス 入力処理 出力処理
//
//=========================================
class Data
{
	int num;			// 入力モンスター数
	Monster *monst;		// Monsterクラスのポインタ
	void input(void);		// 入力処理
	void output(void);		// 出力処理


public:

	void selectMenu(void);		//メニュー処理関数

	Data()	// コンストラクタ
	{
		printf("コンストラクタを実行します\n");
	}
	~Data()	// デストラクタ
	{
		printf("デストラクタを実行します\n");
	}
};

void Data::input(void)
{
	printf("何体のデータを入力しますか?");
	scanf("%d", &num);

	Monster* monst = new Monster[num];

	for (int i = 0; i < num; i++)
	{
		printf("<%d>体目のデータ>\n", i + 1);
		monst[i].inputData();
	}


	delete[] monst;

	rewind(stdin);

	getchar();

}

void Data::output(void)
{

	Monster* monst = new Monster[num];
	for (int i = 0; i < num; i++)
	{
		printf("<%d>体目のデータ>\n", i + 1);
		monst[i].outputData();
	}

	delete[] monst;

	getchar();
}

//===================================
//
//  メニュー処理
//
//===================================
void Data::selectMenu(void)
{
	bool loop = true;
	while (loop)
	{
		printf("======================================\n");
		printf("%d:データ入力\n", MODE_INPUT);
		printf("%d:データの表示\n", MODE_OUYPUT);
		printf("%d:データをセーブ\n", MODE_SAVE);
		printf("%d:データをロード\n", MODE_LORD);
		printf("%d:プログラムを終了\n", MODE_FINISH);
		printf("======================================\n");

		int mode = -1;
		while (mode < MODE_FINISH || mode >= MODE_MAX)
		{
			scanf("%d", &mode);
		}

		switch (mode)
		{

		case MODE_INPUT:  // 入力処理
			input();
			break;

		case MODE_OUYPUT:  // 出力処理
				output();
			break;

		case MODE_SAVE:    // セーブ
				//save();
			break;

		case MODE_LORD:    // ロード
				//lord();
			break;

		case MODE_FINISH: // 終了
			loop = false;
			break;

		}
	}

}

//============================
//
//メイン処理
//
//============================
int main(void)
{

	Data data;

	//モード選択
	data.selectMenu();

	//入力待ち
	rewind(stdin);
	getchar();


	return 0;
}


input()までは移行と入力が出来たんですが、output()が入力した数値ではなく、-842150451という数値が表示されてしまいます。

なぜこのようになるのか、分からないのでどこを変えたらいいのか教えて頂きたいです。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: クラスを使ったメニュー処理

#4

投稿記事 by みけCAT » 3年前

-842150451は2の補数として16進数にすると0xCDCDCDCDとなります。
これはVisual C++のデバッグモードでは「newなどで確保されたが、まだ初期化されていない領域」を表します。
実際、コードを見ると、Data::outputではnewで確保した配列のデータを明示的に初期化せずに出力していますね。
この配列の要素であるクラスMonsterのメンバはint型であり、コンストラクタやメンバの宣言に初期化はありません。

今回のnewでは、初期化用のデータが指定されていないので、確保された領域はdefault-initializeされます。
(N3337 5.3.4 Newの15)
「Monsterの配列」のdefault-initializeは、それぞれの要素Monsterのdefault-initializeです。
Monsterはクラスなので、default-initializeではデフォルトコンストラクタが呼び出されます。
(N3337 8.5 Initializersの6)
デフォルトコンストラクタとは、引数を与えずに呼び出せるコンストラクタのことです。
(N3337 12.2 Constructorsの5)
Monsterクラスのデフォルトコンストラクタは、
同じクラスの他のコンストラクタを利用しない (non-delegating constructor) です。
また、Monsterクラスの各メンバは以下の条件を満たします。
・各mem-initializer-list (コンストラクタの引数の後に : 変数名(値)をつけるやつ) に無い
・抽象クラスの仮想基底クラスではない
・brace-or-equal-initializerを持たない (メンバの宣言に直接初期化が書かれていない)
・unionではない
従って、各メンバはdefault-initializeされます。
(N3337 12.6.2 Initializing bases and membersの8)
各メンバはintであり、クラスでも配列でもないので、初期化は行われません。
(N3337 8.5 Initializersの6)
従って、初期化用のデータを指定しないnewによって、Monsterクラスのメンバは初期化されません。
そのため、コンパイラによって埋められた-842150451がそのまま出力されたと考えられます。

改善するには、以下のようにするといいでしょう。

・Data::input 関数内の delete[] monst; を削除する (入力されたデータをそのまま保持しておく)
・Data::output 関数内の Monster* monst = new Monster[num]; を削除する
 (新たに確保した領域ではなく、入力されたデータがある領域を使用する)

さらに、これだけだとData::outputを複数回連続して呼び出すと開放された領域を使用することになって危険なので、
Data::output 関数内の delete[] monst; も削除しておいたほうが安全でしょう。

参考:
Working Draft, Standard for Programming Language C++ (N3337)
Decimal to Hexadecimal Converter
c++ - When and why will a compiler initialise memory to 0xCD, 0xDD, etc. on malloc/free/new/delete? - Stack Overflow
What is a non-delegating constructor in c++ - Stack Overflow
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

返信

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