read

스물두번째 열혈강의


base

지금까지 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;
}

앞으로의 프로젝트에 있어서는 미리 파일을 분할하고 진행해 나가는 습관을 기르자.


연산자 오버로딩


base

앞선 프로젝트 코딩이 워낙 많긴 했으나, 실질적인 오늘의 블로깅은 여기부터다.

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 + pos2pos1.operator+(pos2)는 100% 똑같은 문장이다. pos1 + pos2pos1.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 스타일의 코드구현이 가능하기 때문에 전역에 대한 개념이 존재하지만, 특별한 경우(이 후 설명이 될 것이다)를 제외하면 멤버함수를 기반으로 연산자를 오버로딩 하는 게 낫다.

이후에 오버로딩 불가능한 함수부터는 다음 포스팅에서 진행하겠다.

Blog Logo

구찌


Published

Image

구찌의 나도한번 해블로그

구찌의 개발 블로그 입니다.

Back to Overview