I'm FanJae.

[C++] OOP(Object Oriented Programming) I 본문

C++/Basic

[C++] OOP(Object Oriented Programming) I

FanJae 2024. 8. 12. 13:53

※ 본 포스트는 코드누리 C++ Basic 강의 내용을 보고 정리한 포스트입니다.

 

1. OOP(Object Oriented Programming)에 대한 필요성 

이 개념은 C++에서 가장 중요한 개념인 '객체 지향 프로그래밍'에 대한 이야기를 해보고자 한다.

OOP의 기본적인 개념과, 접근 지정자(private,public), 캡슐화(Encapsulation) 에 대해서도 얘기하고자 한다. 

 

먼저, 사각형을 그리고(draw), 넓이를 구하는 함수에 대해 생각해보자.

#include <iostream>

int getRectArea(int x1, int y1, int x2, int y2)
{
    return (x2-x1) * (y2-y1);
}
 
void drawRect(int x1, int y1, int x2, int y2)
{
   std::cout << "Draw Rect" << std::endl;
}
 
int main()
{
   int n = getRectArea(1, 1, 10, 10);
    
   drawRect(1, 1, 10, 10);
}

- 일반적으로 2개의 점이 있으면 하나의 사각형의 결정이 가능해서 위와 같이 표현 가능하다.

- 이 코드가 약간 복잡해 보이는 이유는 사각형을 다루는데, C/C++에는 사각형이란 타입이 없다.

- 따라서, 사각형 한 개를 int 변수 4개로 표현하여 다소 복잡해 보인다.

 

※ 사각형이라는 타입이 있으면 된다. -> C 언어에도 struct 문법을 사용하여 구현 가능하다.

 

OOP의 핵심개념 1. 필요한 타입을 먼저 설계하자.

struct Rect
{
    int left;
    int top;
    int right;
    int bottom;
};

// int getRectArea(Rect r)
int getRectArea(const Rect& r)
{
    return (r.right - r.left) * (r.bottom - r.top);
}

// void drawRect(Rect r)
void drawRect(const Rect& Rect r)
{
    std::cout << "Draw Rect" << std::endl;
}

int main()
{
    Rect rc = {1, 1, 10, 10};
    
    int n = getRectArea(rc);
    
    drawRect(rc);
}

 

※ 이와 같이 구현하면 사각형 타입을 구현하면 훨씬 더 코드가 간결해 질 수 있다.

추가로, 인자를 넘겨 줄때 참조자를 통해 넘겨주면, 이 값을 할당해주기 위한 복사본을 만들지 않기 때문에 훨씬 더 효율적이다.

 

이런 점에서 OOP의 첫번쨰 핵심은 "필요한 타입을 먼저 설계하자."이다.

사실 이 개념은 C 언어에서도 설계할 수 있기는 하다. 하지만 이후 개념 부터는 C 언어에서 설계하기 다소 어렵다.

 

OOP의 핵심개념 '2. 상태를 나타내는 데이터와 상태를 조작하는 함수를 묶어서 타입을 설계한다.'

- 데이터와 함수가 분리되어 있으면, 데이터를 항상 함수의 인자로 전달해야 한다.

- 사각형 관련 모든 함수는 인자로 Rect를 받아야 한다.

즉, 이러한 형태가 불편하니까. 구조체 안에 함수를 넣자는 것이다.

 

※ C 언어는 함수를 구조체 안에 넣는 행위가 불가능하다. (물론, 함수 포인터쓰면 가능하겠지만.. 이는 논외로 하자.)

이에 반해 C ++에서는 구조체 안에 함수를 넣는 기능을 제공한다.

 

struct Rect
{
    int left;
    int top;
    int right;
    int bottom;
    
    int getArea()
    {
        return (r.right - r.left) * (r.bottom - r.top);
    }
    
    void draw()
    {
        std::cout << "Draw Rect" << std::endl;
    }    
};

int main()
{
    Rect rc = {1, 1, 10, 10};
    int n = rc.getArea();
    
    draw();
}

- 이와 같이 호출 할 수 있다. 코드를 작성할 때도 상대적으로 간결해지고, 사용법도 쉬워진다.

- 구조체 안에 함수가 들어가기 때문에 함수 이름도 간결하게 작성할 수 있다.


2. Object Oriented Programming의 기본 개념

※ 정리하면, 데이터와 함수를 묶어서 타입을 설계하면, 사용법이 쉬워지고, 다양한 객체지향 문법을 통해서 보다 안전하고, 사용하기 쉬운 타입 설계를 할 수 있다는 것이 기본 개념이다.

 


3. Object란 

"In computer science, an object can be variable, a data structure, a function, or a method. As regions of memory, they contain value and are referenced by identifiers."

- 즉, 컴퓨터 과학에서 객체는 변수, 자료 구조, 함수, 메서드일 수 이다고 하면서 메모리에 존재하고, 이름으로 접근할 수 있는 모든 것을 의미한다.

 

- 하지만, 실제로 우리가 int n과 같은 primitive type은 객체라고 부르지 않고, 보통 변수라고 한다.

- 보통 사용자가 정의한 Data type인 User define type를 객체라고 칭한다.

 

struct Rect
{
    int left;
    int top;
    int right;
    int bottom;
    
    int getArea() { return (r.right - r.left) * (r.bottom - r.top); }
    
    void draw()  { std::cout << "Draw Rect" << std::endl; }    
};

int main()
{
    Rect rc1 = {1, 1, 5, 5};
    Rect rc2 = {3, 3, 8, 8};
    Rect rc3 = {3, 3, 8, 8};
    
    std::cout << sizeof(rc1) << std::endl;
    
    rc1.draw();
    rc2.draw();
}

 

- 이들이 실제로 메모리에 할당되는 것을 보면, Struct에는 getArea()와 draw()라는 멤버 함수가 있다. 하지만 객체가 만들어질때마다 이를 생성하는 것은 아니다.

 

 

※ 즉,  멤버 함수는 객체의 개수에 상관없이 코드 메모리에 한 개만 만들어진다.

 

이를 보면 Rc1과 Rc2가 같은 멤버함수를 공유하고 있는데, 인자가 없는것처럼 보인다.

rc1.draw();
rc2.draw();

즉, 하나밖에 없는 draw에서 이를 어떻게 구분하는가의 대한 문제이다. 

정확히는 this 포인터라는 것을 통해 접근할 수 있다. 이는 추후에 별도 포스트로 다뤄보고자 한다.


4. 접근지정자와 class

#include <string>
struct Person
{
    std::string name;
    int age;
};

int main()
{
    Person p;
    p.age = -10;
}

- 멤버 데이터를 외부에서 직접 접근하면 객체가 잘못된 상태가 접근될 수 있다.

- 따라서 멤버 데이터는 외부의 잘못된 사용으로 부터 보호하는것이 안전하다.

 

접근 지정자에는 크게 3가지가 있다. private,public,protected가 있는데,

오늘은 private,public에 대해서만 이야기 하고자한다. protected는 추후, 상속 포스트에서 따로 다뤄보고자 한다.

 

4-1. private

#include <string>
struct Person
{
private:
    std::string name;
    int age;
    
    void setAge(int value)
    {
    	age = value;
    }
};

int main()
{
    Person p;
//    p.age = -10; // error
    p.setAge(-10);
}

- 이렇게 하면 Person 객체의 값을 수정할 때 직접적으로 수정은 불가능해지고, setAge()를 통해서만 수정 가능하다.

- 하지만, setAge()도 지금 private에 해당하기 때문에 외부 접근이 불가능한 상태이다.

- 이에 따라서, setAge()는 public을 사용한다.

- 즉, Private인 경우, 멤버 함수에서만 접근이 가능하며, 멤버가 아닌 함수에서 접근 시 에러가 발생한다.

 

4-2. public

#include <string>
struct Person
{
private:
    std::string name;
    int age;
    
    void setAge(int value)
    {
    	age = value;
    }
public:
    void setAge(int value)
    {
        if ( value >= 1 && value <= 150)
             age = value;
    }
};

int main()
{
    Person p;
//    p.age = -10; // error
    p.setAge(-10);
}

- 이렇게 함수를 통해 접근 시키면 잘못된 값이 접근하는 것에 대한 방어 코드의 작성이 가능하다.

- 즉, 잘못된 상태가 되는 것을 막는 것은 가능하다. 이에 따라서 코드가 조금 더 안전해 질 수 있다.

- 모든(멤버와 멤버가 아닌) 함수 에서 접근이 가능하다.


5. 캡슐화(Encapsulation)

캡슐화의 개념

- 객체 지향 프로그래밍(OOP)에서 아주 중요한 개념이다.

- 캡슐화는 객체의 내부 상태(데이터)를 외부로부터 숨기고, 접근 지정자를 통해 보호하며, 객체의 내부 데이터는 직접적으로 외부에 접근할 수 없게 만든다.

- 즉, 멤버 데이터는 private영역이나 protected에 놓고 외부의 잘못된 사용으로 부터 보하한다.

- 멤버 데이터의 변경은 잘 정의된 규칙을 가진 멤버 함수를 통해서만 변경 가능하게 하는것이 목적이다.

 

5-1. struct vs class

struct는 접근 지정자 생략시 기본값이 public이다.

class는 접근 지정자 생략시 기본값이 private이다.

 

※ 객체 지향적 철학에서는 class가 오히려 더 적합하다고 볼 수 있다.

 

다른 언어는 이것 외에도 많은 차이가 있으나 C++의 경우 이정도의 차이만 띄고 있다.

접근 지정자의 사용은 언어마다 약간의 차이가 존재한다. 아래는 C#의 예시이다.

class Person
{
    private string name;
    private int age;
    
    public void setAge(int value)
    {
        if ( value >= 1 && value < 150 )
             age = 150;
    }         
}

 

'C++ > Basic' 카테고리의 다른 글

[C++] Vector I  (0) 2024.08.13
[C++] Constructor / Destructor  (0) 2024.08.13
[C++] Explicit casting  (0) 2024.08.11
[C++] nullptr  (0) 2024.08.11
[C++] Reference  (0) 2024.08.09
Comments