본문 바로가기
프로그래밍/C++

C++ ] 상속/가상함수를 이용한 동물호텔

by eteo 2022. 6. 24.

 

main.h

#include <iostream>
#include "Hotel.h"
#include <windows.h>

using namespace std;

int main(void)
{
	Hotel hotelHandler;
	
	while(true)
	{
		int select;

		system("cls");
		hotelHandler.DispMenu();
		cout << "  선택 : [_]\b\b";
		cin >> select;			

		switch (select)
		{
		case CHECKIN:
			hotelHandler.CheckIn();
			break;
		case CHECKOUT:
			hotelHandler.CheckOut();
			break;
		case VIEW:
			hotelHandler.ViewList();
			break;
		case EXIT:
			cout << endl<<"  "; return 0;
		default:
			cin.clear();
			cin.ignore(1000, '\n');
			cout << endl << "  잘못 누르셨습니다. 다시 선택해주세요" << endl;
			Sleep(500);
		}
	}
	return 0;
}

 

루프 안에선 select 변수를 만들고 메뉴를 보여준뒤 사용자에게 입력을 받아서 switch case 로 각각에 메뉴로 들어간다. default 로는 문자 또는 문자열이 들어온 경우를 대비해서 입력버퍼를 비워주었다.

 

 

 


hotel.h

#pragma once
#include "Animal.h"

#define MAXROOM 8

typedef enum
{
	CHECKIN=1,
	CHECKOUT,
	VIEW,
	EXIT
}_Select;

class Hotel
{
private:
	Animal* guest[MAXROOM];			// Animal 포인터 배열
	int currentGuest;			// 현재 투숙동물 수
	
public:
	Hotel()
		:currentGuest(0)			// 투숙동물은 생성시 0으로 초기화
	{		
		for(int i=0; i< MAXROOM; i++)
		{
			guest[i] = NULL;		// NULL 포인터 대입
		}
	};
	
	void DispMenu() const;			// 메뉴 표시
	void CheckIn();					// 체크인
	void CheckOut();				// 체크아웃
	void ViewList() const;			// 방 조회
	int ViewVacantList() const;		// 빈방 보여주기
	
	~Hotel()
	{}
};

 

메뉴를 enum으로 선언해 두었다.

호텔 클래스에는 길이가 #define MAXROOM 인 Animal 포인터 배열과 현재 투숙동물 수를 나타내는 currentGuest가 있다.

 

currentGuest는 멤버이니셜라이저로 생성시 0으로 초기화하고 포인터 배열에는 다 NULL 값을 대입한다.

 

처음엔 방이 빈방인지 아닌지 확인하는 플래그를 두었는데 guest[i]가 NULL값을 가지고 있는지 아닌지 체크하면 빈방인지 아닌지를 알 수 있으니까 필요없어서 삭제했다.

 

 

 

 

Animal.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <cstring>

using namespace std;

typedef enum
{
	DOG =1,
	CAT
}_AnimalType;

class Animal
{
public:

private:
	_AnimalType type;
	char* name;

public:
	Animal(char *myname, _AnimalType mytype)
		:type(mytype)
	{		
		name = new char[strlen(myname) + 1];strcpy(name, myname);
	}
	
	// 가상함수
	virtual void SayBye() const = 0;
	virtual void SayHi() const = 0;

	char* GetName() const
	{
		return name;
	}

	_AnimalType GetType() const		// type getter
	{
		return type;
	}

	void Setname(char* myname)		// name setter
	{
		delete[]name;
		name = new char[strlen(myname) + 1];
		strcpy(name, myname);
	}

	char* AtypeToStr() const	// 동물 타입 enum 을 Str으로 반환
	{
		char* d = (char*)"강아지";
		char* c = (char*)"고양이";
		char* e = (char*)"알수없음";

		if (this->type == DOG) return d;
		else if (this->type == CAT) return c;
		else return e;
	}

	virtual ~Animal()				// 가상소멸자
	{
		delete[]name;
	}
};

class Cat : public Animal
{
public:
	Cat(char* myname, _AnimalType mytype)
		: Animal(myname, mytype)
	{}

	void SayBye() const { cout << "냥냥 이따봐"; }
	void SayHi() const { cout << "냥냥 안녕"; }

};

class Dog : public Animal
{
public:
	Dog(char* myname, _AnimalType mytype)
		: Animal(myname, mytype)
	{}

	void SayBye() const { cout << "멍멍 이따봐"; }
	void SayHi() const { cout << "멍멍 안녕"; }

};

 

Animal은 enum인 type과 chat* 형 name을 멤버변수로 갖는다.

 

SayBye 와 SayHi 함수는 나중에 Cat 과 Dog 가 상속할 거라서 virtual 로 선언했다. 이 외에는 Name에 대한 Getter Setter, type에 대한 Getter를 만들어 두었다.

 

그리고 부모 포인터를 사용해서 delete[] 할꺼기 때문에 소멸자에 virtual을 붙여 가상소멸자로 만들었다.

 

Cat 과 Dog는 Animal을 public으로 상속하고, 생성자를 넣고 SayBye와 SayHi 함수를 재정의 해주었다.

 

 

 

 

 

 

Hotel.cpp

#include "Hotel.h"
#include "Animal.h"
#include <iostream>
#include <windows.h>
#include <cstdio>

using namespace std;

void Hotel::DispMenu() const
{
	cout << "========== 메뉴 ==========" << endl;
	cout << "|                        |" << endl;
	cout << "| 1. 체크인              |" << endl;
	cout << "|                        |" << endl;
	cout << "| 2. 체크아웃            |" << endl;
	cout << "|                        |" << endl;
	cout << "| 3. 방조회              |" << endl;
	cout << "|                        |" << endl;
	cout << "| 4. 나가기              |" << endl;
	cout << "|                        |" << endl;
	cout << "=========================" << endl;
}

int Hotel::ViewVacantList() const
{
	bool choose=true;
	int select = 0;
	while(choose)
	{
		system("cls");
		cout << "==================== 체크인 ====================" << endl << endl;

		cout << "숙박을 원하시는 방을 선택해주세요 (현재 상태가 'X'인 방만 체크인 가능합니다)" << endl << endl;
		printf("-------------------------------------------------\n");
		printf("|1번방|2번방|3번방|4번방|5번방|6번방|7번방|8번방|\n");
		printf("-------------------------------------------------\n");

		for(int i=0; i<MAXROOM;i++)
		{
			if(guest[i]==NULL) printf("|  %c  ", 'X');	// 포인터 배열의 해당 인덱스가 NULL값이면 X
			else printf("|  %c  ", 'O');				// 아니면 O
		}
		printf("|\n");
		
		printf("-------------------------------------------------\n\n");
		printf(" 선택 [_]\b\b");
		cin >> select;
		if(select < 1 || select > MAXROOM)
		{
			cin.clear();								// 잘못 입력시 입력 버퍼 비움
			cin.ignore(1000, '\n');
			cout << endl << "잘못 입력하셨습니다. 다시 선택해주세요..." << endl;
			Sleep(500);
			continue;
		}else
		{
			if(guest[select - 1] !=NULL )				// 해당 방에 동물이 있는 경우
			{
				cout << endl << "해당 방에 투숙하실 수 없습니다. 다시 선택해주세요..." << endl;
				Sleep(500);
			}else
			{
				choose = false;
				break;
			}
		}
	}

	return select-1;									// 제대로 입력한 경우 입력값-1 리턴
}

 

체크인 단계에서 빈방을 보여주는 함수이다. Animal 포인터배열인 guest[i]가 NULL인지 MAXROOM까지 반복하며 NULL이면 화면에 X 표시 아니면 O 표시를 해서 사용자가 빈방을 알 수 있게끔 한다.

 

사용자가 범위를 넘어가는 값을 입력하거나 이미 차있는 방을 선택하는 경우에 예외처리도 했다. 제대로 선택한 경우에만 break로 빠져나와 select-1 (인덱스값)을 리턴한다.

 

 

void Hotel::CheckIn()
{
	
	if(currentGuest < MAXROOM)										// 현재 방이 꽉 찬 상태가 아니라면
	{
		int roomIndex = ViewVacantList();							// 사용자가 선택한 방 인덱스 반환값 저장
		int select = 0;

		Animal* tempPtr = 0;

		cout << endl << "동물의 종류를 입력해주세요." << endl << endl;
		cout << "  1. 강아지" << endl << endl;
		cout << "  2. 고양이" << endl << endl;
		cout << "  [_]\b\b";
		cin >> select;

		if(select!=1 && select !=2)							// 잘못입력 시 입력 버퍼 비움
		{
			cin.clear();
			cin.ignore(1000, '\n');
			cout << endl << "  잘못 입력하셨습니다. 메뉴로 돌아갑니다..." << endl;
			Sleep(500);
			return;
		}

		cout << endl << "동물의 이름을 입력해주세요 : " << endl << endl;
		cout << "  _______________________\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";

		char* tempName = new char[100];
		cin >> tempName;

		if (select == DOG) tempPtr = new Dog(tempName, DOG);					// 임시포인터에 Dog 또는 Cat 동적할당
		else if (select == CAT) tempPtr = new Cat(tempName, CAT);			
	

		delete[]tempName;									// 동물 이름 입력받기 위해 만들었던 임시 포인터 해제

		if(tempPtr!=NULL)									// 동적할당에 실패하지 않았다면
		{
			this->guest[roomIndex] = tempPtr;				// 게스트 배열의 사용자가 선택한 인덱스에 포인터 대입
			
			cout << endl << "매니저 \"체크인이 완료되었습니다\"" << endl << endl;
			cout << "투숙동물 \"";
			guest[roomIndex]->SayBye();
			cout << "\"" << endl << endl;

			currentGuest++;									// 현재 투숙객 수 증가시킴

			system("pause");
		}

	}else
	{
		cout << "현재 빈 방이 없어 체크인이 불가능 합니다" << endl << endl;
		system("pause");
	}
}

 

currentGeust가 MAXROOM 보다 적은 경우에만 입력 단계로 들어간다.

 

 

 

 

void Hotel::CheckOut()
{
	system("cls");
	cout << "============ 체크아웃 ===========" << endl << endl;

	if (currentGuest==0)
	{
		cout << "현재 투숙 동물이 없습니다." << endl << endl;
		system("pause");
		return;
	}

	cout << "체크아웃할 동물의 이름을 입력해주세요 : " << endl << endl;
	cout << "  ___________________\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b";

	char* tempName = new char[100];
	cin >> tempName;	

	int i;
	int chk = 0;
	for(i=0; i < MAXROOM ; i++)
	{
		if(guest[i]!=NULL)									// 현재 동물이 있는방만 strcmp로 이름 비교
		{
			if (strcmp(guest[i]->GetName(), tempName) == 0)
			{
				chk++;										// 일치하는 검색어가 있으면 chk++
				break;
			}
		}

	}

	delete[]tempName;									// 동물 이름 입력받기 위해 만들었던 임시 포인터 해제

	if(chk==1)
	{
		cout << endl << "매니저 \"" << guest[i]->GetName() << " 을/를 체크아웃 합니다\"" << endl << endl;
		cout << "투숙동물 \"";
		guest[i]->SayHi();
		cout << "\"" << endl << endl;

		delete guest[i];								// 해당 포인터 동적할당 해제함
		guest[i] = NULL;								// NULL 포인터 대입

		currentGuest--;									// 현재 투숙동물 수 줄임

		system("pause");

	}else if(chk==0){									// 검색 결과가 없을 시
		cout << endl << "일치하는 이름의 동물이 없습니다." << endl << endl;
		system("pause");
	}
	
}

 

현재 투숙동물이 0인 경우 바로 return 하고 char* tempName = new char[100]; 으로 사용자에게 키워드를 입력받아 MAXROOM 까지 반복하면서 현재 동물이 있는 방만 if (strcmp(guest[i]->GetName(), tempName) == 0) 으로 키워드랑 게스트 이름을 비교한다. NULL포인터를 써서 GetName()함수를 사용하려고 하면 런타임 에러가 날 것이다.

 

만약 일치하는 이름이 있으면 chk++하고 break로 빠져나온다. 동일한 이름의 동물이 둘 있는 경우는 고려하지 않았다. 현재 구조로는 break로 나오고 바로 i값을 써서 해제하는 방식이기 때문에 동일한 이름이 둘 있는 경우 앞쪽 인덱스가 해제될 것이다.

 

임시 포인터인 tempName 해제하고 guest[i] 도 해제하고 NULL을 대입해준다. currentGuest도 --한다.

 

처음엔 삭제한 인덱스가 마지막 인덱스거나 혹은 데이터가 0개인 경우를 제외하고 memmove 함수로 배열 인덱스를 for문 돌려 한칸씩 땡겼었는데 생각해보니 동물호텔에서 앞칸이 체크아웃했다고 뒷칸에 있는 애들을 앞으로 땡기는건 좀 이상한 것 같아서 삭제했다.

예전에 했던 도서관리 프로젝트에선 효율적으로 데이터 관리하기 위해 한 인덱스가 삭제됐을 때 한칸씩 땡기는게 맞는데 이것처럼 물리적 공간이 존재한다고 가정하면 그대로 놔두는 것이 맞다.

 

void Hotel::ViewList() const
{
	system("cls");
	printf("======== 현재 투숙동물 현황 [ %d / %d ] ========\n\n", currentGuest, MAXROOM);
	printf("   방 번호     동물 종류      동물 이름\n");
	printf("----------------------------------------------\n");
	for(int i=0 ; i < MAXROOM; i++)						// 현재 투숙객 수만큼 반복하며 목록 프린트
	{
		if(guest[i]==NULL)
		{
			printf("    [%d]         X               X  \n", i + 1);
		}else
		{
			printf("    [%d]         %s          %s  \n", i + 1, guest[i]->AtypeToStr(), guest[i]->GetName());
		}
		
	}
	printf("----------------------------------------------\n\n");
	cout << "  ";
	system("pause");
}

 

NULL인지 아닌지 검사해서 NULL이면 X표시 아니면 종류와 이름 표시하는데 동물 enum 타입을 char* 형태로 반환하는 AtypeTostr() 함수를 만들어놔 사용하였다.