객체지향 프로그래밍의 개념
-
프로그램에서 필요한 데이터 타입을 먼저 설계
-
C언어의 구조체를 사용하면 새로운 데이터 타입을 정의 가능
-
C 구조체 : 데이터만 포함 할 수 있음
-
C++ 구조체 : 데이터, 함수 포함 가능
C Style(구조체 없이 구현)
void Add(int xr, int xi, int yr, int yi, //in parameters
int* sr, int* si) // out parameters
{
*sr = xr + yr;
*si = xi + yi;
}
int main()
{
int ar = 1, ai = 1;
int br = 2, bi = 2;
int sr, si;
Add(ar, ai, br, bi, &sr, &si);
}
C Style(구조체를 사용한 구현)
Complex Add(const Complex& c1, const Complex& c2)
{
Complex temp;
temp.re = c1.re + c2.re;
temp.im = c1.im + c2.im;
return temp;
}
int main()
{
Complex c1 = { 1, 1 };
Complex c2 = { 2, 2 };
Complex t = Add(c1, c2);
}
Stack으로 배우는 OOP
Step1. 구조체 없이 전역으로 구현한 Stack
-
쉽게 구현하였으나 Stack을 다중으로 사용하기에는 적절하지 않음
int buf[10];
int idx = 0;
void push(int n) { buf[idx++] = n; }
int pop() { return buf[--idx]; }
int main()
{
push(10);
push(20);
push(30);
std::cout << pop() << std::endl;
}
Step2. 구조체를 사용한 Stack
-
Stack 구조체를 사용하지 않았을때 보단 간결하지만 Stack 상태 조작 함수가 외부에 있음
struct Stack
{
int buf[10];
int idx;
};
void push(Stack* s, int n)
{
s->buf[(s->idx)++] = n;
}
int pop(Stack* s)
{
return s->buf[--(s->idx)];
}
int main()
{
Stack s1, s2;
s1.idx = 0;
s2.idx = 0;
push(&s1, 10);
push(&s1, 20);
push(&s1, 30);
std::cout << pop(&s1) << std::endl;
}
Step3. C++ 구조체를 사용한 Stack
-
구조체에 데이터와 함수를 함께 구현
- idx가 잘못된 값으로 초기화 위험성 있음
struct Stack
{
int buf[10]; // 멤버 데이터
int idx;
void push(int n) // 멤버 함수
{
buf[idx++] = n;
}
int pop() // 멤버 함수
{
return buf[--idx];
}
};
int main()
{
Stack s1, s2;
s1.idx = 0;
s2.idx = 0;
s1.push(10);
s1.push(20);
s1.push(30); // 컴파일러에 의해서 내부적으로는 push(&s1, 20) 형태로 사용됨
std::cout << s1.pop() << std::endl;
}
Step4. 정보 은닉(Information Hiding)
- 접근 지정자
- private : 멤버 함수에서만 접근 할 수 있음
- public : 외부 함수에서 접근 가능
- 정보 은닉(information hiding)
- 멤버 데이터를 외부에서 직접 접근할 수 없게 하고, 멤버 함수를 통해서만 접근 가능하도록 함
- 외부에 잘못된 사용으로 부터 객체가 불안해 지는것을 방지
- Stack을 사용하는 사람은 push, pop 함수만 알면 되지 Stack 내부 데이터를 몰라도 됨
- struct vs class
- struct : 접근 지정자 생략시 기본 접근자 public
- class : 접근 지정자 생략시 기본 접근자 private
class Stack
{
// class 접근 기본값은 private
int buf[10];
int idx;
public: // 외부 접근을 위한 public 접근자
void init() { idx = 0; }
void push(int n)
{
buf[idx++] = n;
}
int pop()
{
return buf[--idx];
}
};
int main()
{
Stack s1;
s1.init();
s1.push(10);
s1.push(20);
s1.push(30);
std::cout << s1.pop() << std::endl;
}
Step5. 생성자(Constructor)
-
클래스이름과 동일한 이름을 가지는 함수
- 객체를 만들면 자동으로 생성자가 호출됨
- 리턴 타입을 표지하지 않음
- 인자 사용 여부는 선택적
class Stack
{
int buf[10];
int idx;
public:
// 클래스 이름과 동일한 함수 : 생성자
Stack() { idx = 0; }
void push(int n) { buf[idx++] = n; }
int pop() { return buf[--idx]; }
};
int main()
{
Stack s1;
s1.push(10);
s1.push(20);
s1.push(30);
std::cout << s1.pop() << std::endl;
}
Step6. 소멸자(Destructor)
- 스택의 버퍼크기를 변경 할 수 있도록 제공을 위해선 동적 메모리 할당이 필요한데 객체가 소멸되는 시점에 메모리 해지를 위해서 소멸자 이용
-
~클래스이름() 형태의 함수
- 객체가 파괴될 때 호출됨
class Stack
{
int* buf;
int idx;
public:
// 생성자 : 객체가 생성될때 자동으로 호출
Stack(int size = 10)
{
buf = new int[size]; // new 배열 타입으로 동적 메모리 할당
idx = 0;
}
// 소멸자 : 객체가 파괴될때 자동으로 호출
~Stack()
{
delete[] buf; // new 배열 타입으로 할당한 메모리를 delete[]로 해지
}
void push(int n) { buf[idx++] = n; }
int pop() { return buf[--idx]; }
};
int main()
{
Stack s1(20);
s1.push(30);
std::cout << s1.pop() << std::endl;
}
Step7. 선언과 구현의 분리
-
선언과 구현의 분리
-
클래스 선언 안에는 함수의 선언만 포함
-
함수의 구현은 클래스 외부에서 구현
-
-
선언 파일과 구현 파일의 분리
-
클래스 선언부는 헤더 파일로 생성(.h)
-
클래스 구현부는 소스 파일로 생성(.cpp)
-
// stack.h
class Stack
{
private:
int* buf;
int idx;
public:
Stack(int size = 10);
~Stack();
void push(int n);
int pop();
};
// stack.cpp
#include "Stack.h"
Stack::Stack(int size = 10)
{
buf = new int[size];
idx = 0;
}
Stack::~Stack()
{
delete[] buf;
}
void Stack::push(int n)
{
buf[idx++] = n;
}
int Stack::pop()
{
return buf[--idx];
}
// main.cpp
#include <iostream>
#include "Stack.h"
int main()
{
Stack s1(20);
s1.push(30);
std::cout << s1.pop() << std::endl;
}
Step8. 코딩 관례
-
대부분의 오픈소스들은 프로젝트들은 사용자 친화적으로 public 함수를 상단, private 함수, 변수는 하단에 배치하는 경향을 보임
// Stack.h
class Stack
{
public: // 주 접근 함수를 상단
Stack(int size = 10);
~Stack();
void push(int n);
int pop();
private: // 내부 접근 함수 및 변수는 하단
int* buf;
int idx;
};
Step9. class template
-
Stack은 int 뿐 아니라 다른 타입버전도 필요하므로 템플릿을 이용하여 구현
- 내부적으로는 컴파일 타임에 사용자 코드를 기반으로 각각의 데이터 타입별 Stack 클래스를 별도로 생성함
- 클래스 템플릿에서 멤버 함수 구현시 주의사항
- 클래스 외부, 내부에 구현부를 분리하여 넣어도 되지만, 별도의 소스파일로 분리하면 안됨
- 함수 선언과 구현 모두 헤더파일에 존재 하여야함
template<typename T>
class Stack
{
T* buf;
int idx;
public:
Stack(int size = 10) {
buf = new T[size];
}
~Stack() {
delete[] buf;
}
void push(T n) { buf[idx++] = n; }
T pop() { return buf[--idx]; }
};
// 구현부 분리 시
template<typename T>
Stack<T>::Stack(){}
template<typename T>
T Stack<T>::pop(){}
int main()
{
Stack<int> s1(20);
s1.push(30);
Stack<double> s2(20);
std::cout << s1.pop() << std::endl;
}
Step10. STL stack
-
STL
- C++ 표준 라이브러리
- 다양한 자료구조와 알고리즘 함수 제공
- stack
- 내부 버퍼 크기는 자동으로 관리됨
- 템플릿으로 구현 됨
- 제거용 함수와 리턴용 함수가 분리 되어 있음
- pop : 제거만 하고 리턴 안됨
- top : 리턴만 하고 제거되지 않음
int main()
{
std::stack<int> s;
s.push(10);
s.push(20);
s.push(30);
int n1 = s.top(); // return 30(리턴만 하고 제거 X)
int n2 = s.top(); // return 30(리턴만 하고 제거 X)
s.pop(); // 제거만 하고 리턴 X
int n3 = s.top(); // return 20
s.pop(); // 제거만 하고 리턴 X
int n4 = s.top(); // return 10
}
'프로그래밍 언어 > C++' 카테고리의 다른 글
C++ 복사 생성자 (0) | 2019.05.06 |
---|---|
C++ 접근 지정자, 생성자, 소멸자 (0) | 2019.05.06 |
C++ Explicit Casting (0) | 2019.05.06 |
C++ reference 변수 (0) | 2019.05.06 |
C++ new (0) | 2019.05.06 |