read

두번째 열혈강의


computer

이전까지 C++에서 namespace를 알아보았다. 사실 여기서는 C를 다 안다고 하고 가기 때문에, 기본서를 한번도 보지 않은 사람은 아예 이해가 되기 힘들수도 있다고 생각한다. 심지어 이 책은 벌써 설계에 대한 얘기가 나온다. 그럼 진행해 보도록 하자.


OOP


먼저, OOP 단계별 프로젝트 1단계라 하는 소제목이 나온다. 그리고 아직 익숙치 않다면 책을 한번 다 보고 진행하라고 한다. 난 내가 설계를 가장 좋아하고 잘 하고 싶어하는 사람으로써 이 챕터가 벌써 나온다는 것에 너무 만족한다. 그럼 제대로 내용을 알아 보도록 하자. C++을 공부하면서 가장 어려움을 겪는 부분이 문법의 이해가 아닌 구현이라고 한다. C언어와 다르기에 활용함에 있어 많은 시간과 노력을 요구하는데, 무엇보다도 경험이 없으면 시작이 막막하다. 그러므로 프로젝트를 하나 진행하려고 한다고 한다.

앞으로 총 7단계에 걸쳐 진행해 나가도록 한다.


프로젝트 설명


먼저 우리가 하려고 하는 것은 은행 계좌 관리 프로그램이다. 처음은 C스타일의 진행을 해보도록 하자.

구현 기능은 다음과 같다.

1. 계좌개설
2. 입금
3. 출금
4. 전체고객 잔액조회

그리고 몇가지 가정이 필요하다.

1. 통장의 계좌번호는 중복되지 않는다.(중복검사를 하지 않는다)
2. 입금  출금액은 무조건 0보다 크다. (입금  출금액 검사를 하지 않는다.)
3. 고객의 계좌정보는 계좌번호, 고객이름, 고객의 잔액 3가지만 저장, 관리한다.
4.  이상의 고객 정보 저장을 위해 배열을 사용한다.
5. 계좌번호는 정수의 형태다.

그럼 시작해 보도록 하자.


실행의 예


사실 책의 내용을 그대로 가져와서 적어 넣는 듯한 느낌이 들어, 실행의 예는 단순히 말로만 적어 보면, 메뉴를 먼저 보여주고, 거기에서 선택을 입력 받으면 상황에 따라 처리하는 형식이다. 여기서는 먼저 자신의 코드를 만들어보고 저자의 코드를 보라고 되어있다. 뭐 내 블로그다 보니 다 적어놓을 수는 없지만 궁금하다면 책을 보길 바란다. 사실 나도 C스타일이 기억이 안나서… 참 부끄럽지만 (역시 나 스스로 개발자라고 하기엔 아직 부끄러운게 너무 많다.) 조금 찾아보면서 만들게 되었다.(특히, 최근 1년간 javascript만 했다보니 객체밖에 떠오르지 않아서.. 하…) 못난 나를 꾸짖는 좋은 시간이였다.

내가 만든 코드를 올리려다 일단 무료공개해둔 코드를 적었다. 출처는 이곳을 참조하면 된다.

/*
 * Banking System Ver 0.1
 * 작성자: 윤성우
 * 내  용: OOP 단계별 프로젝트의 기본 틀 구성
 */

#include <iostream>
#include <cstring>

using namespace std;
const int NAME_LEN=20;

void ShowMenu(void);       // 메뉴출력
void MakeAccount(void);    // 계좌개설을 위한 함수
void DepositMoney(void);       //     
void WithdrawMoney(void);      //     
void ShowAllAccInfo(void);     // 잔액조회

enum {MAKE=1, DEPOSIT, WITHDRAW, INQUIRE, EXIT};

typedef struct 
{
	int accID;      // 계좌번호
	int balance;    //     
	char cusName[NAME_LEN];   // 고객이름
} Account;

Account accArr[100];   // Account 저장을 위한 배열
int accNum=0;        // 저장된 Account 

int main(void)
{
	int choice;
	
	while(1)
	{
		ShowMenu();
		cout<<"선택: "; 
		cin>>choice;
		cout<<endl;
		
		switch(choice)
		{
		case MAKE:
			MakeAccount(); 
			break;
		case DEPOSIT:
			DepositMoney(); 
			break;
		case WITHDRAW: 
			WithdrawMoney(); 
			break;
		case INQUIRE:
			ShowAllAccInfo(); 
			break;
		case EXIT:
			return 0;
		default:
			cout<<"Illegal selection.."<<endl;
		}
	}
	return 0;
}

void ShowMenu(void)
{
	cout<<"-----Menu------"<<endl;
	cout<<"1. 계좌개설"<<endl;
	cout<<"2. 입    금"<<endl;
	cout<<"3. 출    금"<<endl;
	cout<<"4. 계좌정보 전체 출력"<<endl;
	cout<<"5. 프로그램 종료"<<endl;
}

void MakeAccount(void) 
{
	int id;
	char name[NAME_LEN];
	int balance;
	
	cout<<"[계좌개설]"<<endl;
	cout<<"계좌ID: ";	cin>>id;
	cout<<"이  름: ";	cin>>name;
	cout<<"입금액: ";	cin>>balance;
	cout<<endl;
	
	accArr[accNum].accID=id;
	accArr[accNum].balance=balance;
	strcpy(accArr[accNum].cusName, name);
	accNum++;
}

void 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].accID==id)
		{
			accArr[i].balance+=money;
			cout<<"입금완료"<<endl<<endl;
			return;
		}
	}
	cout<<"유효하지 않은 ID 입니다."<<endl<<endl;
}

void 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].accID==id)
		{
			if(accArr[i].balance<money)
			{
				cout<<"잔액부족"<<endl<<endl;
				return;
			}

			accArr[i].balance-=money;
			cout<<"출금완료"<<endl<<endl;
			return;
		}
	}
	cout<<"유효하지 않은 ID 입니다."<<endl<<endl;
}

void ShowAllAccInfo(void)	
{
	for(int i=0; i<accNum; i++)
	{
		cout<<"계좌ID: "<<accArr[i].accID<<endl;
		cout<<"이  름: "<<accArr[i].cusName<<endl;
		cout<<"잔  액: "<<accArr[i].balance<<endl<<endl;
	}
}

이 코드를 작성하는 것이 1단계의 시작이다. 차차 C++ 코드로 변환시켜 나갈 것으로 예상된다.


CHAPTER 2


CHAPTER 2에 들어왔다.

CHAPTER 2를 진행하기에 앞서, 이 책에서는 C를 굉장히 중요시 여긴다. 당연한 것이다. C++이라는 것이 C + class라고 하기도 하므로 중요할 수 밖에 없다. 여기서도 이 부분에서 C 복습을 시킨다.

문제가 3개 주어진다.

1. 키워드 const 의미
2. 실행중인 프로그램의 메모리 공간
3. Call-by-value vs Call-by-reference

어찌보면 기본적이고, 가장 중요한 것들이다. 하나하나 알아보자.


const


“1. 키워드 const의 의미”에 있어서 예시들이 있는데, 다음과 같다.

1. const int num=10;
2. const int *ptr1 = &val1;
3. int* const ptr2 = &val2;
4. const int* const ptr3 = $val3;

C를 공부 제대로 안했으면 뭐가 뭔지, 아마 const가 뭔지부터 헤깔릴 수도 있으니 잘 알아두도록 하자.

먼저 const의 의미는 상수화 이다. 내가 느끼고 이해하기 쉬운 말로는 고정이라고 본다. 일단 책에서의 설명을 적어보자면

1번은 변수 num을 상수화

2번은 포인트 ptr1을 이용해서 val1의 값을 변경할 수 없음

3번은 포인터 ptr2가 상수화 됨

4번은 포인터 ptr3가 상수화 되었으며, ptr3를 이용하여 val3의 값을 변경할 수 없음

이라고 한다.

내가 만약에 설명을 한다면, 상수의 반댓말을 "변수"정도라고 생각하고 "고정"이라는 단어를 이용하려고 한다.

1번은 int형 변수 num의 값을 상수 10으로 고정

2번은 int형 포인터 ptr1이 가르치는 값을 고정, ptr1은 val1의 주소

3번은 int형 ptr2 포인터가 가리키는 값의 주소를 고정, ptr2는 val2의 주소

4번은 int형 ptr3가 가리키는 값과 주소를 둘다 고정, ptr3는 val3의 주소

정도로 정리 될 것이다.

즉, 중요한 것은 포인터의 개념도 들어가는데, int* ptr이라고 할 때 변수는 ptr이지 *ptr이 아니다. (*은 연산자이다.) 포인터를 생각할 때는, 그림을 그려도 좋고 자신이 이해하기 쉬운 쪽으로 생각하면 된다. 개인적으로는 정리를 해둔 PPT가 있어 헤깔릴 때마다 그 자료를 보다보니 자연스럽게 이해가 되었다. 간단히 설명하면, int* ptr이라 할때, ptr도 자신의 저장공간을 가지고 선언이 되는 것이고, 그 저장공간에는 포인터가 가리키는 곳의 주소가 저장된다. (즉, 포인터도 자신의 주소는 따로 있다.) 좀더 자세하게 하면 포인터 변수는 ptr, ptr이 가리키는 값은 *ptr, ptr이 기리키는 값의 주소는 ptr이라고 정리된다.

그럼 포인터 함수가 또 헤깔릴 수가 있는데, void swap(int* A, int* B)라는 함수가 있다고 하자. 우리는 main에서 어떻게 입력하냐면 swap(&num1, &num2);와 같은 식으로 함수를 호출한다. 이게 또 포인터에 대한 개념이 헤깔리면 어려워 보이는데, 포인터 변수는 A와 B이다. 즉, 포인터 변수 A에 num1의 주소를 넣어준 것이고, A는 포인터로서 가르키는 값의 주소num1의 주소가 된 것이다. 그렇다면 *A는 num1의 값이 되는게 당연하다.

이것도 이해가 힘들다면 쉽게 생각하자. int* A = &num1; 이 되는 것이고, int* B = &num2; 가 되는 것이다. 이렇게 보고나면 포인터의 기본 선언문이 되므로 쉽게 접근이 된다.

배열이 포인터라는 것도 다들 알지만 이해하기 힘들어 하는데, 배열을 한번 보도록 하자. 배열 int arr[10];을 선언 했다고 하자. 그럼 arr[0]*(arr)과 같다. 이걸 외우는 사람이 많다. arr[2] == *(arr+2) 이런식으로 말이다. 이건 외우지 않아도 된다.

배열이 포인터라면 배열을 선언할 때, arr은 제일 첫번째 배열 값의 시작 주소를 가지고 있는 것이다. 이게 무슨 뜻이냐면 값들은 메모리 어딘가 나눠져 있겠지만, 배열은 포인터로써 자신의 자료형 크기만큼 하나씩 할당하여 일렬로 값이 있는 주소를 가지고 있는다는 뜻이다. 그렇다면 재밌는 코드를 하나 보도록 하자.

#include <iostream>

using namespace std;

int main() {

	double A = 20.0;
	double *ptr = &A;

	cout << A << endl;
	cout << *ptr << endl;
	cout << A + 1 << endl;
	cout << ptr << endl;
	cout << ptr + 1 << endl;
	
	return 0;
}

C로 짰으면 더 좋았겠지만 원하는 바는 표현이 된다. 중요한 것은 ptr의 출력과 ptr + 1의 출력이다. ptr주소와 ptr주소에 1을 더한 값인데 놀랍게도 1만 더한게 아니라 8이나 더한 값이 나올 것이다. 그 이유는 ptr이 double형 포인터라 단순히 1이 더해진 것이 아니고, double형의 자료 한개 크기인 8바이트가 커진 것이다.

이제 이것을 배열에 적용 해보자. 배열arr은 방금 말했듯이 첫 배열 값의 주소다. 그렇다면 arr+1은 어떻게 될까? 해당 배열의 자료형 한개 값을 더한 크기이므로 배열 두번째 값의 주소가 되는 것이다. 따라서 *(arr+1)은 arr[1]의 값이 되는 것이다.

C에서 나오는 포인터 개념을 줄글로 간단히 정리해보았다.


Memory 와 Call-by-value, Call-by-referance


그럼 원래 하던 얘기로 돌아와서 2. 실행중인 프로그램의 메모리 공간에 대해 알아보자. 프로그램은 OS한테 메모리를 할당받는데, 이 때 크게 데이터, HEAP, STACK 영역으로 나눠진다.(이 책에서는 이렇게 나오지만 text 영역도 있다.) 이 때 어떠한 변수가 어디에 할당 되는지 알아보는 게 중요하다. 이것은 각각의 역할만 잘 알면 쉽다. text는 CPU가 실행하는 머신코드, 데이터는 전역변수, static 변수, 외부변수, 스택은 지역변수나 매개변수, 힙은 동적할당 영역 정도로 구분될 것이다.

생각보다 간단히 정리가 되었다.

그럼 마지막 3. Call-by-value, Call-by-referance를 알아보자.

이건 사실 위에 포인터를 설명하면서 어느정도 감각적으로 알 수 있을 텐데, (특히 함수) 가장 직접적으로 알 수 있는 swap을 제대로 만들어 보면,

#include <iostream>

using namespace std;

void swap_value(int A, int B);
void swap_ref(int *A, int *B);

int main() {


	int A = 10;
	int B = 20;
	
	cout << "main &A : " << &A << endl;
	cout << "main &B : " << &B << endl;
	swap_value(A, B);
	cout << "swap value 이후 A : " << A << ", swap value 이후 B : " << B << endl;
	swap_ref(&A, &B);
	cout << "swap ref 이후 A : " << A << ", swap ref 이후 B : " << B << endl;

	return 0;
}

void swap_value(int A, int B) {

	cout << "swap_value &A : " << &A << endl;
	cout << "swap_value &B : " << &B << endl;
	int tmp;
	tmp = A;
	A = B;
	B = tmp;
}

void swap_ref(int *A, int *B) {

	cout << "swap_ref &A : " << &A << endl;
	cout << "swap_ref &B : " << &B << endl;
	cout << "swap_ref *A : " << *A << endl;
	cout << "swap_ref *B : " << *B << endl;
	cout << "swap_ref A : " << A << endl;
	cout << "swap_ref B : " << B << endl;
	int tmp;
	tmp = *A;
	*A = *B;
	*B = tmp;

}

이정도로 볼 수 있다.

이후에 이 부분은 책에서 좀더 세밀하게 설명을 할 예정이라고 한다. 어느정도 감각만 가지고 있도록 하자.

아무래도 글이 길어져 다음 포스팅에서 이어서 하도록 하겠다.

Blog Logo

구찌


Published

Image

구찌의 나도한번 해블로그

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

Back to Overview