스물두번째 열혈강의
지금까지 OOP 단계별 프로젝트를 이어왔는데, 이번에는 파일을 분할해 보도록 하자.
Account.h
#ifndef __ACCOUNT_H__
#define __ACCOUNT_H__
class Account
{
private:
int accID;
int balance;
char * cusName;
public:
Account(int ID, int money, char * name);
Account(const Account & ref);
int GetAccID() const;
virtual void Deposit(int money);
int Withdraw(int money) ;
void ShowAccInfo() const ;
~Account();
};
#endif
Account.cpp
#include "BankingCommonDecl.h"
#include "Account.h"
Account::Account(int ID, int money, char * name)
: accID(ID), balance(money)
{
cusName=new char[strlen(name)+1];
strcpy(cusName, name);
}
Account::Account(const Account & ref)
: accID(ref.accID), balance(ref.balance)
{
cusName=new char[strlen(ref.cusName)+1];
strcpy(cusName, ref.cusName);
}
int Account::GetAccID() const { return accID; }
void Account::Deposit(int money)
{
balance+=money;
}
int Account::Withdraw(int money)
{
if(balance<money)
return 0;
balance-=money;
return money;
}
void Account::ShowAccInfo() const
{
cout<<"계좌ID: "<<accID<<endl;
cout<<"이 름: "<<cusName<<endl;
cout<<"잔 액: "<<balance<<endl;
}
Account::~Account()
{
delete []cusName;
}
AccountHandler.h
#ifndef __ACCOUN_HANDLER_H__
#define __ACCOUN_HANDLER_H__
#include "Account.h"
class AccountHandler
{
private:
Account * accArr[100];
int accNum;
public:
AccountHandler();
void ShowMenu(void) const;
void MakeAccount(void);
void DepositMoney(void);
void WithdrawMoney(void);
void ShowAllAccInfo(void) const;
~AccountHandler();
protected:
void MakeNormalAccount(void);
void MakeCreditAccount(void);
};
#endif
AccountHandler.cpp
#include "BankingCommonDecl.h"
#include "AccountHandler.h"
#include "Account.h"
#include "NormalAccount.h"
#include "HighCreditAccount.h"
void AccountHandler::ShowMenu() const {
cout << " ===== MENU ===== " << endl;
cout << "1. 계좌개설 " << endl;
cout << "2. 입금 " << endl;
cout << "3. 출금 " << endl;
cout << "4. 계좌정보 전체 출력 " << endl;
cout << "5. 종료 " << endl;
}
void AccountHandler::MakeAccount() {
int sel;
cout << "[계좌종류선택" << endl;
cout << "1. 보통예금계좌" << endl;
cout << "2. 신용신뢰계좌" << endl;
cout << "선택 : ";
cin >> sel;
if(sel == NORMAL)
makeNormalAccount();
else
makeCreditAccount();
}
void AccountHandler::MakeNormalAccount() {
int id;
char name[NAME_LEN];
int balance;
int interRate;
cout << "[보통예금계좌 개설]" << endl;
cout << "계좌 ID : "; cin >> id;
cout << "이름 : "; cin >> name;
cout << "입금액 : "; cin >> balance;
cout << "이자율 : "; cin >> interRate;
cout << endl;
accArr[accNum++] = new NormalAccount(id, balance, name, interRate);
}
void AccountHandler::MakeCreditAccount(void)
{
int id;
char name[NAME_LEN];
int balance;
int interRate;
int creditLevel;
cout << "[신용신뢰계좌 개설]" << endl;
cout << "계좌 ID : "; cin >> id;
cout << "이름 : "; cin >> name;
cout << "입금액 : "; cin >> balance;
cout << "이자율 : "; cin >> interRate;
cout << "신용등급(1toA, 2toB, 3toC) : "; cin >> creditLevel;
cout << endl;
switch(creditLevel)
{
case 1:
accArr[accNum++]=new HighCreditAccount(id, balance, name, interRate, LEVEL_A);
break;
case 2:
accArr[accNum++]=new HighCreditAccount(id, balance, name, interRate, LEVEL_B);
break;
case 3:
accArr[accNum++]=new HighCreditAccount(id, balance, name, interRate, LEVEL_C);
}
}
void AccountHandler::DepositMoney(void)
{
int money;
int id;
cout << "[입 금]" << endl;
cout << "계좌ID : "; cin >> id;
cout << "입금액 : "; cin >> money;
for(int i = 0; i < accNum; i++){
if(accArr[i]->getAccID() == id){
accArr[i]->deposit(money);
cout << "입금완료" << endl;
return;
}
}
cout << "유효하지 않은 ID 입니다." << endl << endl;
}
void AccountHandler::WithdrawMoney(void)
{
int money;
int id;
cout << "[출 금]" << endl;
cout << "계좌ID : "; cin >> id;
cout << "출금액 : "; cin >> money;
for(int i = 0; i < accNum; i++){
if(accArr[i]->getAccID() == id){
if(accArr[i]->withDraw(money) == 0){
cout << "잔액부족" << endl << endl;
return;
}
accArr[i]->withDraw(money);
cout << "출금완료" << endl;
return;
}
}
cout << "유효하지 않은 ID 입니다." << endl << endl;
}
AccountHandler::AccountHandler() : accNum(0)
{ }
void AccountHandler::ShowAllAccInfo(void) const
{
for(int i=0; i<accNum; i++)
{
accArr[i]->ShowAccInfo();
cout<<endl;
}
}
AccountHandler::~AccountHandler()
{
for(int i=0; i<accNum; i++)
delete accArr[i];
}
NormalAccount.h
#ifndef __NORMAL_ACCOUNT_H__
#define __NORMAL_ACCOUNT_H__
#include "Account.h"
class NormalAccount : public Account
{
private:
int interRate;
public:
NormalAccount(int ID, int money, char * name, int rate)
: Account(ID, money, name), interRate(rate)
{ }
virtual void Deposit(int money)
{
Account::Deposit(money);
Account::Deposit(money*(interRate/100.0));
}
};
#endif
HignCreditAccount.h
#ifndef __HIGHCREDIT_ACCOUNT_H__
#define __HIGHCREDIT_ACCOUNT_H__
#include "NormalAccount.h"
class HighCreditAccount : public NormalAccount
{
private:
int specialRate;
public:
HighCreditAccount(int ID, int money, char * name, int rate, int special)
: NormalAccount(ID, money, name, rate), specialRate(special)
{ }
virtual void Deposit(int money)
{
NormalAccount::Deposit(money);
Account::Deposit(money*(specialRate/100.0));
}
};
#endif
BankingCommonDecl.h
#ifndef __BANKING_COMMON_H__
#define __BANKING_COMMON_H__
#include <iostream>
#include <cstring>
using namespace std;
const int NAME_LEN=20;
enum {MAKE=1, DEPOSIT, WITHDRAW, INQUIRE, EXIT};
enum {LEVEL_A=7, LEVEL_B=4, LEVEL_C=2};
enum {NORMAL=1, CREDIT=2};
#endif;
BankingSystemMain.cpp
#include "BankingCommonDecl.h"
#include "AccountHandler.h"
int main(void)
{
AccountHandler manager;
int choice;
while(1)
{
manager.ShowMenu();
cout<<"선택 : ";
cin>>choice;
cout<<endl;
switch(choice)
{
case MAKE:
manager.MakeAccount();
break;
case DEPOSIT:
manager.DepositMoney();
break;
case WITHDRAW:
manager.WithdrawMoney();
break;
case INQUIRE:
manager.ShowAllAccInfo();
break;
case EXIT:
return 0;
default:
cout<<"Illegal selection.."<<endl;
}
}
return 0;
}
앞으로의 프로젝트에 있어서는 미리 파일을 분할하고 진행해 나가는 습관을 기르자.
연산자 오버로딩
앞선 프로젝트 코딩이 워낙 많긴 했으나, 실질적인 오늘의 블로깅은 여기부터다.
C++에서는 함수뿐 아니라 연산자 도 오버로딩이 가능하다. 그런데 좀 생소하게 느껴질 수 있다. 그렇지만 기본 개념은 매우 단순하니 괜찬을 것이다. 그리고 연산자 오버로딩
은 C++을 이해하는데 매우 중요한 요소기 때문에 잘 알아두어야 한다.
함수가 오버로딩이 되면 오버로딩 횟 수만큼 기능을 제공하므로 이름은 하나지만 기능은 여러 가지가 된다. 마찬가지로 연산자의 오버로딩도 기존 연산자 기능 외에 다른 기능을 추가할 수가 있다.
operator+
라는 함수가 나온다. 먼저 예제부터 보도록 하자.
#include <iostream>
using namespace std;
class Point{
private:
int xpos, ypos;
public:
Point(int x=0, int y=0) : xpos(x), ypos(y){}
void ShowPosition() const {
cout << '[' << xpos << ", " << ypos << ']' << endl;
}
Point operator+(const Point &ref){ // operator+라는 이름의 함수
Point pos(xpos + ref.xpos, ypos + ref.ypos);
return pos;
}
};
int main() {
Point pos1(3, 4);
Point pos2(10, 20);
Point pos3 = pos1.operator+(pos2); //복사 생성자 호출
pos1.ShowPosition();
pos2.ShowPosition();
pos3.ShowPosition();
return 0;
}
Point pos3 = pos1.operator+(pos2);
이 문장에서는 pos1 객체의 operator+
함수를 호출하고 있지만, 다음과 같이 pos2 객체의 멤버 함수를 호출하는 형태로 문장을 구성해도 그 결과는 같다.
Point pos3 = pos2.operator+(pos1)
그럼 여기서 main 만 한번 바꿔보자.
#include <iostream>
using namespace std;
class Point{
private:
int xpos, ypos;
public:
Point(int x=0, int y=0) : xpos(x), ypos(y){}
void ShowPosition() const {
cout << '[' << xpos << ", " << ypos << ']' << endl;
}
Point operator+(const Point &ref){
Point pos(xpos + ref.xpos, ypos + ref.ypos);
return pos;
}
};
int main() {
Point pos1(3, 4);
Point pos2(10, 20);
Point pos3 = pos1+pos2;
pos1.ShowPosition();
pos2.ShowPosition();
pos3.ShowPosition();
return 0;
}
Point pos3 = pos1+pos2;
바뀐 부분은 다음과 같다.
확인해보면 컴파일도 가능하고, 덧셈결과도 잘 구동된다.
그럼 다음과 같이 유추된다.
pos1 + pos2가 pos1.operator+(pos2)의 다른 표현일 것이다.
pos1 + pos2가 pos1.operator+(pos2)의 다른 표현이 되기 위해 약속된 변환이 있을 것이다.
위 두가지가 유추가능 했다면 약 50%정도는 벌써 이해한 것이다.
즉 연산자 오버로딩은 C++이 우리가 쉽게 사용할 수 있게 만들어 준 약속이다.
중요한 것은 객체를 가지고 덧셈연산
을 했다는 것이다.
C++을 사용해서 객체도 기본 자료형 변수처럼 사칙연산을 가능하게 하려고 했고,(기본 C++ 철학이다.) 객체도 완벽히 기본 자료형 데이터처럼 취급할 수 있게 하려고 했다.
그래서 C++은 operator
키워드와 연산자
를 묶어서 함수의 이름을 정의하면 함수의 이름을 이용한 함수의 호출뿐 아니라 연산자를 이용한 함수의 호출도 가능하게 했다.
즉 pos1 + pos2
가 있다고 할 때, pos1.operator+(pos2)
로 바꾸어 계산 하는 것이고, 왼쪽에 있는 pos1 객체를 대상으로 operator+
함수를 호출하면서 +
연산자의 오른쪽에 있는 피연산자를 인자로 전달시킨다.
또한, +
가 아닌 -
라면, operator-
가 될 것이다.
장황하게 설명했는데, 결국 연산자 오버로딩은 일종의 약속 이다.
pos1 + pos2
와 pos1.operator+(pos2)
는 100% 똑같은 문장이다. pos1 + pos2
가 pos1.operator+(pos2)
로 해석되어 컴파일 되기 때문이다.
이는 연산자 오버로딩의 한 예 이고, 멤버함수 에 의한 방법과 전역변수 에 의한 방법이 있다.
참고로, 연산자 오버로딩 함수에도 const
는 붙일 수 있다. 아마 대부분이 붙이게 될 것이다. 왜냐하면 보통 덧셈연산이 원래 연산의 대상이 되는 피연산자의 값을 변경하는 것이 아니고, 새로운 연산의 결과를 만들어 내기 때문이다.
두가지 방법
앞서, 연산자 오버로딩엔 2가지 방법이 있다고 언급했다.
- 멤버함수에 의한 연산자 오버로딩
- 전역함수에 의한 연산자 오버로딩
앞에서 본 예제는 멤버함수
에 의한 연산자 오버로딩 이였다. 그러니 이번엔 전역함수에 대해 알아보자.
operator+(pos1, pos2)
전역함수로 오버로딩을 하면 pos1 + pos2
는 위와 같이 해석이 된다.
다시한번 차이를 보이면, pos1 + pos2
는
- 멤버함수에 의한 연산자 오버로딩 :
pos1.operator+(pos2)
- 전역함수에 의한 연산자 오버로딩 :
operator+(pos1, pos2)
와 같이 된다.
참고로, 같은 자료형을 대상으로 + 연산자를 전역함수 기반으로, 그리고 멤버함수 기반으로 동시
오버로딩 할 경우, 멤버함수가 더 우선시 된다. 그러나, 일부 컴파일러는 오류가 발생하기도 하므로 가급적 하지 말도록 하자.
그럼 전역 함수 예제를 보도록 하자.
#include <iostream>
using namespace std;
class Point{
private:
int xpos, ypos;
public:
Point(int x=0, int y=0) : xpos(x), ypos(y){}
void ShowPosition() const {
cout << '[' << xpos << ", " << ypos << ']' << endl;
}
friend Point operator+(const Point &pos1, const Point &pos2);
};
Point operator+(const Point &pos1, const Point &pos2){
Point pos(pos1.xpos + pos2.xpos, pos1.ypos + pos2.ypos);
return pos;
}
int main() {
Point pos1(3, 4);
Point pos2(10, 20);
Point pos3 = pos1+pos2;
pos1.ShowPosition();
pos2.ShowPosition();
pos3.ShowPosition();
return 0;
}
friend
가 사용된 것이 보일 것이다. 위와 같이 사용이 되었을 때,
operator+ 함수 내에서는 Point 클래스의 private 영역에 접근할 수 있다
는 생각이 들겠지만, 한가지 더 Point 클래스는 + 연산에 대해 연산자 오버로딩이 되어 있다
는 의미역시 담고 있다.
이렇게 friend
를 사용하여 할 수 있다는 점을 알 수 있었다.
그러나 객체지향에서는 전역(global)에 대한 개념이 없다. C++은 C 스타일의 코드구현이 가능하기 때문에 전역에 대한 개념이 존재하지만, 특별한 경우(이 후 설명이 될 것이다)를 제외하면 멤버함수를 기반으로 연산자를 오버로딩 하는 게 낫다.
이후에 오버로딩 불가능한 함수부터는 다음 포스팅에서 진행하겠다.