객체 지향 프로그래밍 OOP 개념
OOP : Objected Oriented Programming
복소수를 표현하기 위해 double형 변수 2개를 사용
날짜를 표현하기 위해 int형 변수 3개 를 사용
사람을 표현하기 위해 이름, 나이 등의 변수들을 사용
아래와 같이 복소수 계산을 위해 input 6개를 넣어주는 함수를 만드는데 복잡하고 번거롭다.
객체 지향 프로그래밍 관점에서 Complex, Date, Person 와 같은 객체를 만들어 표현하고 싶다.
// 복소수 2개를 더하고 싶다.
void add( double ar, double ai, double br, double bi, // 계산에 필요한 변수 4개 (in parameter)
double* sr, double* si ) // 결과 값을 담을 변수 2개 (out parameter)
{
*sr = ar + br;
*si = ai + bi;
}
int main()
{
double xr = 1, xi = 1; // 1 + 1i
double yr = 2, yi = 2; // 2 + 2i
double sr, si;
add( xr, xi, yr, yi, &sr, &si );
// sr, si 결과가 있다.
}
필요한 데이타 타입을 설계한다.
// Complex라는 데이타 타입을 설계해 좀 더 편하게 코딩이 가능해진다.
struct Complex
{
double real;
double image;
};
Complex add(const Complex& c1, const Complex& c2)
{
Complex temp;
temp.real = c1.real + c2.real;
temp.image = c1.image + c2.image;
return temp;
}
int main()
{
Complex c1 = { 1, 1}; // 1 + 1i
Complex c2 = { 2, 2}; // 2 + 2i
Complex ret = add(c1, c2);
}
현실세계에 존재 하는 사물은 상태와 동작이 있다.
타입을 설계할 때, 상태는 변수로 동작은 함수로 표현한다.
C에서의 구조체는 데이타만 포함할 수 있다.
C++에서의 구조체는 데이타와 함수를 포함할 수 있다.
객체 지향 프로그래밍 예제
stack을 예제로 객체 지향 프로그래밍을 살펴보면,
아래와 같이 stack을 만들고 push와 pop을 사용하고 싶다.
int main()
{
push(10);
push(20);
push(30);
int n1 = pop(); // 30
int n2 = pop(); // 20
}
0단계 : 간단한 구현.
구현을 해보면 push와 pop을 만들어 볼 수 있다.
그런데 여기서 2개의 stack이 필요하다면?
#include <iostream>
int buf[10];
int idx = 0;
void push(int value)
{
buf[idx++] = value;
}
int pop()
{
return buf[--idx];
}
int main()
{
push(10);
push(20);
push(30);
std::cout << pop() << std::endl; // 30
std::cout << pop() << std::endl; // 20
}
1단계 : 너무 불편한데...
2개의 stack이 필요하다면? 변수를 2개를 설정해 만들어 볼 수 있다.
그런데... 뭔가 좀 복잡해진다. 연관된 데이타를 묶어서 Stack 타입을 만들고 싶어진다.!!!
#include <iostream>
void push(int* buf, int* idx, int value)
{
buf[++(*idx)] = value;
}
int pop(int* buf, int* idx)
{
return buf[(*idx)--];
}
int main()
{
int buf1[10];
int idx1 = 0;
int buf2[10]; // 2번째 버퍼
int idx2 = 0; // 2번째 인덱스
push(buf1, &idx1, 10);
push(buf1, &idx1, 20);
push(buf2, &idx2, 30);
std::cout << pop(buf1, &idx1) << std::endl; // 30
std::cout << pop(buf1, &idx1) << std::endl; // 20
}
2단계 : 뭔가 좀 한 참 모자라...
구조체를 활용해서 Stack 을 설계해보는데,,,
조금 간편해 졌긴 하지만, 아직은 조금 불편하다.
첫번째 인자로 Stack* s 를 건네줘야 한다. push, pop 이외에도 직접 idx, buf에 접근이 가능하다.
#include <iostream>
struct Stack
{
int buf[10];
int idx;
};
void push(Stack* s, int value)
{
s->buf[s->idx++] = value;
}
int pop(Stack* s)
{
return s->buf[--(s->idx)];
}
int main()
{
Stack s1;
Stack s2;
s1.idx = 0;
s2.idx = 0;
push(&s1, 10);
push(&s1, 20);
s1.idx = 0;
push(&s1, 30);
std::cout << pop(&s1) << std::endl; // 30
std::cout << pop(&s1) << std::endl; // 20
}
3단계 : 아직 조금 모자라~~
구조체 안에 함수를 포함한다.
멤버 함수와 멤버 데이타
멤버 함수에서는 멤버 데이터에 접근할 수 있다.
단점 : idx, buf를 누구나 접근이 가능하다.
#include <iostream>
struct Stack
{
int buf[10];
int idx;
void push(int value)
{
buf[idx++] = value;
}
int pop()
{
return buf[--idx];
}
};
int main()
{
Stack s1;
Stack s2;
s1.idx = 0;
s2.idx = 0;
s1.push(10);
s1.push(20);
s1.idx = 0; // 외부에서 idx 접근이 가능... (문제 발생 가능성!)
std::cout << s1.pop() << std::endl; // 30
std::cout << s1.pop() << std::endl; // 20
}
4단계 : 가장 이상적인 OOP의 예제!!!
class로 정의하게 되면, private와 public으로 함수를 구분하여 idx, buf 접근을 막고
init, push, pop에 접근할 수 있도록 한다. 캡슐화라고 말한다.
#include <iostream>
class Stack
{
private: // 접근지정자 생략시 class는 접근지정자가 private가 된다.
// 접근지정자 생략시 struct는 접근지정자가 public이 된다.
int buf[10];
int idx;
public:
void init() { idx = 0;}
void push(int value)
{
buf[idx++] = value;
}
int pop()
{
return buf[--idx];
}
};
int main()
{
Stack s1;
Stack s2;
s1.init();
s2.init();
s1.push(10);
s1.push(20);
std::cout << s1.pop() << std::endl; // 30
std::cout << s1.pop() << std::endl; // 20
}
생성자
생성자를 통해 자동으로 초기화를 해준다. 리턴 타입이 없다.
#include <iostream>
class Stack
{
private:
int buf[10];
int idx;
public:
Stack() { idx = 0; } // 생성자 : 자동으로 초기화, 리턴 타입이 없음
void push( int a) { buf[idx++] = a; }
int pop() { return buf[--idx]; }
};
int main()
{
Stack s1;
//s1.init();
s1.push(10);
std::cout << s1.pop() << std::endl;
}
생성자와 파괴자 + 메모리 할당 및 해제
생성자에 input을 받아 버퍼 크기를 바꿀 수 있도록 한다.
생성자에 size를 받아서 new int[size] 메모리 동적 할당을 해준다.
파괴자를 통해 buf를 delete[]를 통해 메모리 해제를 해준다.
#include <iostream>
class Stack
{
private:
int* buf;
int idx;
public:
Stack(int size = 10)
{
idx = 0;
buf = new int[size];
}
~Stack() { delete[] buf;}
void push( int a) { buf[idx++] = a; }
int pop() { return buf[--idx]; }
};
int main()
{
Stack s1(30);
s1.push(10);
std::cout << s1.pop() << std::endl;
}
클래스 파일 분할
실무에서는 대부분 파일이 분할되어 있기에 기본적으로 이 구조를 많이 보게 된다.
/* ============= stack.h ============== */
class Stack
{
public:
Stack(int size = 10);
~Stack();
void push( int a);
int pop();
private: // 코딩 관례상 private을 아래쪽에 둔다. 굳이 알 필요가 없어서.
int* buf;
int idx;
};
/* ============= stack.cpp ============== */
#include "stack.h"
Stack::Stack(int size)
{
idx = 0;
buf = new int[size];
}
Stack::~Stack() { delete[] buf;}
void Stack::push( int a) { buf[idx++] = a; }
int Stack::pop() { return buf[--idx]; }
// ====================== main ==========================
#include <iostream>
#include "stack.h"
int main()
{
Stack s1(30);
s1.push(10);
std::cout << s1.pop() << std::endl;
}
Template Class (템플릿 클래스)
buf 타입을 int만이 아닌 다양한 type을 처리해주는 코딩을 하고 싶다.
적절하게 template 코드를 넣어준다.
#include <iostream>
template<typename T> // template 사용하는 클래스로 만들기!
class Stack
{
private:
T* buf; // 버퍼를 template로 !
int idx;
public:
Stack(int size = 10)
{
idx = 0;
buf = new T[size]; // 메모리 생성시 T타입
}
~Stack() { delete[] buf;}
void push( T a) { buf[idx++] = a; } // input도 T로 넣어준다.
T pop() { return buf[--idx]; } // 반환값 T
};
int main()
{
Stack<int> s1(30); // input 타입이 필요
Stack<double> s2(30);
s1.push(10);
std::cout << s1.pop() << std::endl;
}
'C++' 카테고리의 다른 글
[C++] 객체지향 : 접근지정자 private, public, friend (0) | 2021.08.18 |
---|---|
[C++] STL : stack, vector (0) | 2021.08.17 |
[C++] 동적메모리 할당 (new, delete), nullptr (0) | 2021.08.15 |
[C++] Explicit Casting (명시적 캐스팅) (0) | 2021.08.14 |
[C++] const reference, return by reference (0) | 2021.08.14 |
댓글