04-1 정보은닉과 const
chapter 03에서 제시한 과일장수 시뮬레이션 예제에서 정의한 두 클래스의 멤버변수는 private으로 선언이 되어있다.
그러나 다음 조건을 유지할 수 있는 장치는 없다.
사과의 구매를 목적으로 0보다 작은 수를 전달할 수 없다
위 제약사항을 항상 만족시킬 수 있도록 예제를 변경하고, 예제의 안전성을 높일 수 있도록 일부 함수를 const로 선언해보자.
chapter 03에서 제시한 과일장수 시뮬레이션 예제
#include <iostream>
using namespace std;
//사과장수
class FruitSeller
{
private:
int APPLE_PRICE; //사과가격 (상수)
int numOfApples; //재고
int myMoney; //사과장수의 지갑
public:
// 초기화
void InitMembers(int price, int num, int money)
{
APPLE_PRICE = price;
numOfApples = num;
myMoney = money;
}
// 사과 판매
int SaleApples(int money)
{
int num = money / APPLE_PRICE; //팔린 갯수
numOfApples -= num;
myMoney += money; //사과장수의 지갑
return num;
}
// 수익과 재고 출력
void ShowSalesResult() {
cout << "남은 사과 : " << numOfApples << endl;
cout << "판매 수익: " << myMoney<< endl <<endl;
}
};
// 손님
class FruitBuyer {
//프라이빗
int myMoney; // 손님지갑
int numOfApples; //산 사과갯수
public:
void InitMembers(int money)
{
myMoney = money;
numOfApples = 0;
}
// 구매기능
// 구매대상, 구매금액이 전달
void BuyApples(FruitSeller &seller, int money) {
numOfApples += seller.SaleApples(money); //사과 구매하려고 함수 호출
myMoney -= money;
}
void ShowBuyResult()
{
cout << "현재 잔액 : " << myMoney << endl;
cout << "사과 개수 : " << numOfApples << endl << endl;
}
};
int main(void)
{
FruitSeller seller;
seller.InitMembers(1000, 20, 0);
FruitBuyer buyer;
buyer.InitMembers(5000);
buyer.BuyApples(seller, 2000); //사과 구매
cout << "과일판매자 현황" << endl;
seller.ShowSalesResult();
cout << "과일구매자 현황" << endl;
buyer.ShowBuyResult();
return 0;
}
해설
#include <iostream>
using namespace std;
//사과장수
class FruitSeller
{
private:
int APPLE_PRICE; //사과가격 (상수)
int numOfApples; //재고
int myMoney; //사과장수의 지갑
public:
// 초기화
void InitMembers(int price, int num, int money)
{
APPLE_PRICE = price;
numOfApples = num;
myMoney = money;
}
// 사과 판매
int SaleApples(int money)
{
if (money < 0) {
cout << "잘못된 정보가 전달되어 구매를 취소합니다" << endl;
return 0;
}
int num = money / APPLE_PRICE; //팔린 갯수
numOfApples -= num;
myMoney += money; //사과장수의 지갑
return num;
}
// 수익과 재고 출력
// const : 이 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다
void ShowSalesResult() const ////추가
{
cout << "남은 사과 : " << numOfApples << endl;
cout << "판매 수익: " << myMoney << endl << endl;
}
};
// 손님
class FruitBuyer {
//프라이빗
int myMoney; // 손님지갑
int numOfApples; //산 사과갯수
public:
void InitMembers(int money)
{
myMoney = money;
numOfApples = 0;
}
// 구매. 구매대상, 구매금액이 전달
void BuyApples(FruitSeller& seller, int money) {
// unsigned int로 하면 없어도 됨..
if (money < 0) {
cout << "잘못된 정보가 전달되어 구매를 취소합니다" << endl;
return;
}
numOfApples += seller.SaleApples(money); //사과 구매하려고 함수 호출
myMoney -= money;
}
void ShowBuyResult() const ////추가
{
cout << "현재 잔액 : " << myMoney << endl;
cout << "사과 개수 : " << numOfApples << endl << endl;
}
};
int main(void)
{
FruitSeller seller;
seller.InitMembers(1000, 20, 0);
FruitBuyer buyer;
buyer.InitMembers(5000);
buyer.BuyApples(seller, 2000); //사과 구매
cout << "과일판매자 현황" << endl;
seller.ShowSalesResult();
cout << "과일구매자 현황" << endl;
buyer.ShowBuyResult();
return 0;
}
04-2 다양한 클래스의 정의
다음의 Point 클래스를 기반으로 활용하여 원을 의미하는 Circle 클래스를 정의하자
class Point
{
private:
int xpos, ypos;
public:
void Init(int x, int y)
{
xpos=x;
ypos=y;
}
void ShowPointInfo() const
{
cout<<"["<<xpos<<", "<<ypos<<"]"<<endl;
}
};
Circle 객체에는 좌표상의 위치 정보(원의 중심좌표)와 반지름의 길이 정보를 저장, 출력할 수 있어야 한다. 그리고 정의한 Circle 클래스를 기반으로 Ring 클래스도 정의하자. 링은 두 개의 원으로 표현 가능하므로(바깥쪽, 안쪽 원), 두 개의 Circle 객체를 기반으로 정의가 가능하다. 참고로 안쪽 원과 바깥쪽 원의 중심좌표가 동일하다면 두께가 일정한 원을 표현하는 셈이 되며, 중심좌표가 동일하지 않다면 두께가 일정하지 않은 링을 표현하는 셈이 된다. 이렇게 해서 클래스의 정의가 완료되었다면, 다음 main 함수를 기반으로 실행을 시키자.
int main(void)
{
Ring ring;
ring.Init(1, 1, 4, 2, 2, 9);
ring.ShowRingInfo();
return 0;
}
Init의 함수호출을 통해서 전달된 1,1,4는 안쪽 원의 정보에 해당하며 (x좌표, y좌표, 반지름) 이어서 전달된 2,2,9는 바깥쪽 원의 정보에 해당한다. 그리고 실행결과는 다음과 유사해야 한다.
Inner Circle Info...
radius: 4
[1, 1]
Outter Circle Info...
radius: 9
[2, 2]
참고로 1개의 클래스를 정의하더라도, 항상 캡슐화를 고민하기 바란다. 이 문제의 답안도 캡슐화의 고민여부에 따라 차이를 보일 수 있다.
해설
#include <iostream>
using namespace std;
class Point
{
private:
int xpos, ypos;
public:
void Init(int x, int y)
{
xpos = x;
ypos = y;
}
void ShowPointInfo() const
{
cout << "[" << xpos << ", " << ypos << "]" << endl;
}
};
// 원의 중심좌표와 반지름 길이를 저장, 출력
class Circle {
int rad; //반지름
Point center; //원의 중심좌표
public:
void Init(int x, int y, int r) {
rad = r;
center.Init(x, y);
}
void ShowCircleInfo() const {
cout << "radius : " << rad << endl;
center.ShowPointInfo();
}
};
class Ring
{
Circle inCircle;
Circle outCircle;
public:
// 안쪽원 x좌표,y좌표,반지름
// 바깥원 x좌표,y좌표,반지름
void Init(int xX,int xY, int xR, int yX, int yY, int yR)
{
inCircle.Init(xX, xY, xR);
outCircle.Init(yX, yY, yR);
}
/*void ShowRingInfo() {*/
void ShowRingInfo() const {
cout << "Inner Circle Info" << endl;
inCircle.ShowCircleInfo();
cout << "Outter Circle Info" << endl;
outCircle.ShowCircleInfo();
}
};
int main(void)
{
Ring ring;
ring.Init(1, 1, 4, 2, 2, 9);
ring.ShowRingInfo();
return 0;
}
04-3 C++ 기반의 데이터 입출력
1. 04-2는 생성자를 안 배웠기 때문에 별도의 초기화 함수를 정의, 호출하여 Point, Circle, Ring 클래스의 객체를 초기화하였다. 이 때 구현한 답에 대해서 모든 클래스에 생성자를 정의해보자.
해설
#include <iostream>
using namespace std;
class Point
{
private:
int xpos, ypos;
public:
Point(int x, int y) : xpos(x), ypos(y) { }
/*void Init(int x, int y)
{
xpos = x;
ypos = y;
}*/
void ShowPointInfo() const
{
cout << "[" << xpos << ", " << ypos << "]" << endl;
}
};
// 원의 중심좌표와 반지름 길이를 저장, 출력
class Circle {
int rad; //반지름
Point center; //원의 중심좌표
public:
Circle(int x, int y, int r) : rad(r), center(x, y) { }
/*void Init(int x, int y, int r) {
rad = r;
center.Init(x, y);
}*/
void ShowCircleInfo() const {
cout << "radius : " << rad << endl;
center.ShowPointInfo();
}
};
class Ring
{
Circle inCircle;
Circle outCircle;
public:
// 안쪽원 x좌표,y좌표,반지름
// 바깥원 x좌표,y좌표,반지름
//void Init(int xX,int xY, int xR, int yX, int yY, int yR)
//{
// inCircle.Init(xX, xY, xR);
// outCircle.Init(yX, yY, yR);
//}
Ring(int xX, int xY, int xR, int yX, int yY, int yR) :inCircle(xX, xY, xR), outCircle(yX, yY, yR){}
/*void ShowRingInfo() {*/
void ShowRingInfo() const {
cout << "Inner Circle Info" << endl;
inCircle.ShowCircleInfo();
cout << "Outter Circle Info" << endl;
outCircle.ShowCircleInfo();
}
};
int main(void)
{
/*Ring ring;
ring.Init(1, 1, 4, 2, 2, 9);*/
Ring ring(1, 1, 4, 2, 2, 9);
ring.ShowRingInfo();
return 0;
}
2. 명함을 의미하는 NameCard 클래스를 정의해보자. 이 클래스에는 다음의 정보가 저장되어야 한다.
성명, 회사이름, 전화번호, 직급
단, 직급 정보를 제외한 나머지는 문자열의 형태로 저장을 하되, 길이에 딱 맞는 메모리 공간을 할당 받는 형태로 정의하자(동적 할당을 하라는 뜻). 그리고 직급 정보는 int형 멤버변수를 선언해서 저장을 하되, 아래의 enum 선언을 활용해야 한다.
enum {CLERK, SENIOR, ASSIST, MANAGER};
위의 enum 선언에서 정의된 상수는 순서대로 사원, 주임, 대리, 과장을 뜻한다. 그럼 다음 main 함수와 실행의 예를 참조하여, 이 문제에서 원하는 형태대로 NameCard 클래스를 완성해보자.
int main(void)
{
NameCard manClerk("Lee", "ABCEng", "010-1111-2222", COMP_POS::CLERK);
NameCard manSENIOR("Hong", "OrangeEng", "010-3333-4444", COMP_POS::SENIOR);
NameCard manAssist("Kim", "SoGoodComp", "010-5555-6666", COMP_POS::ASSIST);
manClerk.ShowNameCardInfo();
manSENIOR.ShowNameCardInfo();
manAssist.ShowNameCardInfo();
return 0;
}
이름 : Lee
회사 : ABCEng
전화번호 : 010-1111-2222
직급 : 사원
이름 : Hong
회사 : OrangeEng
전화번호 : 010-3333-4444
직급 : 주임
이름 : Kim
회사 : SoGoodComp
전화번호 : 010-5555-6666
직급 : 대리
03의 예제 RacingCarEnum.cpp를 참고하면 수월하다. (더보기 클릭)
// 03장의 RacingCarEnum.cpp
#include <iostream>
using namespace std;
// 이름 공간안에 구조체 Car에서 사용하는 상수를 모아놓음
namespace CAR_CONST {
// 열거형 enum 상수 선언
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
};
}
struct Car
{
// 소유자ID, 연료량, 현재속도
char gamerID[CAR_CONST::ID_LEN];
int fuelGauge;
int curSpeed;
void ShowCarState() {
cout << "소유자ID : " << gamerID << endl;
cout << "연료량 : " << fuelGauge << "%" << endl;
cout << "현재속도 : " << curSpeed << "km/s" << endl<< endl;
}
void Accel() {
if (fuelGauge <= 0) {
return;
}
else {
fuelGauge -= CAR_CONST::FUEL_STEP;
}
if (curSpeed + CAR_CONST::ACC_STEP >= CAR_CONST::MAX_SPD) {
curSpeed = CAR_CONST::MAX_SPD;
return;
}
curSpeed += CAR_CONST::ACC_STEP;
}
void Break() {
if (curSpeed < CAR_CONST::BRK_STEP) {
curSpeed = 0;
return;
}
curSpeed -= CAR_CONST::BRK_STEP;
}
};
int main(void)
{
// 구조체변수의 선언 및 초기화 진행
Car run99 = { "run99", 100, 0 };
run99.Accel();
run99.Accel();
run99.ShowCarState();
run99.Break();
run99.ShowCarState();
Car sped77= { "sped77", 100, 0 };
sped77.Accel();
sped77.Break();
sped77.ShowCarState();
return 0;
}
해설
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
//#include <string.h>
#include <cstring>
using namespace std;
// 직급정보
namespace COMP_POS
{
// 익명이라도 충돌하지 않도록
enum { CLERK, SENIOR, ASSIST, MANAGER };
// 익명 열거형 : 열거형 이름, 변수가 없는 것
void ShowPositionInfo(int pos)
{
switch (pos)
{
case CLERK:
cout << "사원" << endl;
break;
case SENIOR:
cout << "주임" << endl;
break;
case ASSIST:
cout << "대리" << endl;
break;
case MANAGER:
cout << "과장" << endl;
}
}
}
// 명함
class NameCard
{
private:
char* name; // 성명
char* company; // 회사
char* phone; // 전번
int position; //직급
public:
NameCard(const char* _name, const char* _company, const char* _phone, int pos)
: position(pos)
{
// 동적할당해 문자열의 형태로 저장
name = new char[strlen(_name)+1]; //new는 생성된 것에 대해 포인터를 반환한다.
company = new char[strlen(_company)+1];
phone = new char[strlen(_phone)+1];
strcpy(name, _name);
strcpy(company, _company);
strcpy(phone, _phone);
}
void ShowNameCardInfo() const {
cout << "이름 : " << name << endl;
cout << "회사 : " << company << endl;
cout << "전화번호 : " << phone << endl;
cout << "직급 : "; COMP_POS::ShowPositionInfo(position);
cout << endl;
}
~NameCard() {
delete []name;
delete []company;
delete []phone;
}
};
int main(void)
{
char lee[4] = {"Lee"};
NameCard manClerk(lee, "ABCEng", "010-1111-2222", COMP_POS::CLERK);
NameCard manSENIOR("Hong", "OrangeEng", "010-3333-4444", COMP_POS::SENIOR);
NameCard manAssist("Kim", "SoGoodComp", "010-5555-6666", COMP_POS::ASSIST);
manClerk.ShowNameCardInfo();
manSENIOR.ShowNameCardInfo();
manAssist.ShowNameCardInfo();
return 0;
}
'C, C++ > 열혈 C++ 프로그래밍' 카테고리의 다른 글
04-4. 클래스와 배열 그리고 this 포인터 (책) (0) | 2021.09.25 |
---|---|
04-03. 생성자와 소멸자(책) (0) | 2021.09.24 |
3. 클래스의 기본 (0) | 2021.09.15 |
02. C언어 기반의 C++ 연습문제 (0) | 2021.09.14 |
02-3. 참조자(Reference)의 이해 (0) | 2021.09.13 |