C++ OOP(Object Oriented Programming) 문법과 개념

200원짜리개발자·2023년 6월 1일
2

C++

목록 보기
2/39
post-thumbnail
강의보고 공부한 것을 정리하는 목적으로 작성된 글이므로 틀린점이 있을 수 있음에 양해부탁드립니다. 
(피드백 환영입니다)

일단 객체지향을 공부하기 전에 객체지향이 왜 필요한지에 대해 알아야 한다고 생각한다.

그럼 객체지향은 왜 필요할까?

모든 프로그램은 데이터 + 로직으로 작동이 된다.
그러면 객체지향 없이 사용해도 뭐 상관없지 않냐? 라고 할 수 있다.
물론 객체지향 프로그래밍 없이도 구현할 수는 있다.
하지만 코드가 급격하게 늘어난다면 객체지향 프로그래밍을 하지 않았더라면
아무리 파일을 분리해서 관리한다고 하여도 관리를 하는것에 대한 한계가 있을 것이다.

한 번 절자지향적으로 간단한 코드를 적어본다면

#include <iostream>
using namespace std;

struct Knight
{
	int hp;
	int attack;
	int defence;
};

void HealPlayer(Knight& a, int value)
{
	a.hp += value;
}

int main()
{
	Knight k1;
	k1.hp = 100;
	k1.attack = 10;
	k1.defence = 3;

	HealPlayer(k1, 100);
	return 0;
}

만약 이러한 코드가 있을 때 문제점이 있을 수 있다.
일단 HealPlayer라는 함수Knight라는 타입만 받아서 함수를 실행할 수 있다는 점이다.
만약 나중에 다른 직업인 Archer, Wizard 같은 직업이 나온다면 함수를 다 따로 만들어 줘야하는 번거로움이 생긴다.
즉 함수의 사용이 한정된다는 단점이 생길 수 있다.
그리고 나중에 점점 코드가 커지다보면 함수만으로는 프로그램(게임)을 짜기 힘들어지는 순간이 올 것이다.

그럼 객체지향 프로그래밍은 뭐니?

데이터를 만들고 로직을 짜는 순서에서 탈출해 모든것을 객체단위로 생각한다고 보면 된다.(객체끼리의 상호작용이 '주'가 됨)

그럼 객체가 무엇인가? 라는 생각을 할 수 있는데
객체의 정의를 보면 메모리를 할당받아 프로그램에 사용되는 모든 데이터이다.
즉 우리가 사용했던 변수, 구조체, 함수 등이 전부 객체라고 할 수 있다.
그렇지만 객체지향(opp)에서의 객체는 대부분 클래스의 인스턴스라고 생각하면 될 것 같다.

롤을 예로들어 설명하자면
롤에나오는 미니언, 챔피언, 오브젝트 등의 세부사항과 그것들이 하는 행동까지 포함한 것이 객체라고 할 수 있다.
여기서 그럼 눈에 보이는 것만 객체인가? 라고 생각할 수 있는데
, 로비, 게임시작창 이런것들도 객체라고 볼 수 있다.

개념 정리

다시 개념들을 정리해보자면

  • 클래스
    • 객체를 만들어내기 위한 틀, 설계도
    • 변수와 메서드의 집합
  • 객체(object)

    • 개념
      • 소프트웨어 세계에 구현할 대상
      • 클래스에 선언된 모양 그대로 생성된 실체
    • 특징
      • '클래스의 인스턴스(instance)'라고 불림
        • 객체는 모든 인스턴스를 대표하는 포괄적인 의미
        • opp의 관점에서 클래스의 타입으로 선언되었을 때 '객체'라고 부름
  • 인스턴스

    • 개념
      • 설계도를 바탕으로 소프트웨어 세계에 구현된 구체적인 실체
        • 즉, 객체를 소프트웨어에 실체화 하면 그것을 '인스턴스(instance)'라고 부른다.
        • 실체화된 인스턴스는 메모리에 할당된다.
    • 특징
      • 인스턴스는 객체에 포함된다고 볼 수 있음
      • oop의 관점에서 객체가 메모리에 할당되어 실제 사용될 때 '인스턴스'라고 부른다.
      • 추상적인 개념과 구체적인 객체 사이의 관계에 초점을 맞출 경우에 사용한다.
        • '~의 인스턴스'의 형태로 사용된다.
        • 객체는 클래스의 인스턴스이다.
        • 객체 간의 링크는 클래스 간의 연관 관계의 인스턴스이다.
        • 실행 프로세스는 프로그램의 인스턴스다.
        • 즉, 인스턴스라는 용어는 반드시 클래스와 객체 사이의 관계로 한정지어서 사용할 필요는 없다.
      • 인스턴스는 어떤 원본(추상적인 개념)으로부터 '생성된 복제본'을 의미한다.

아래의 블로그의 글이 아주 잘 정리가 되어있어서 가져와서 적어보았다.
참조한 블로그

그럼 해보자!

일반적으로 객체지향 프로그래밍을 할 때는 class라는 것을 사용하기 때문에 class를 사용해보자

여기서 여담으로 C++에서는 struct와 class의 차이가 별로 없기 때문에 struct를 사용해서 할 수도 있다.
하지만 c#이나 java로 가면 class는 참조, struct는 값 방식으로 둘이 아예 다른 것이 되어버리는 것으로 알고있다.

일단 class를 사용해보자

class Knight
{

};

classstruct를 사용하는 것 처럼 사용하면 된다.
class의 정의를 알아볻자면 대충 객체를 생성하기 위한 설계도? 정도로 생각해주면 될 것 같다.
class라는 설계도객체를 만들어서 사용을 한다 라고 보면 된다.

그럼 안을 채워보자

class Knight
{
public:

public:
	// 멤버 변수
	int _hp;
	int _attack;
	int _defence;
};

class안에 들어가는 데이터를 멤버 변수라고 불러준다.
멤버변수를 작성할 때 일반 변수인지 멤버 변수인지 헷갈릴 수 있기 때문에
프로젝트마다 네이밍컨벤션을 정해서 한다. 지금은 앞에 언더바(_)를 붙여준걸 볼 수 있다.
여기까지 보면 struct를 사용할 때랑 다른점이 없다.
그리고 class만 만들었을 때는 아직까지 어떠한 메모리 영역도 자치하지 않고 Knight객체를 만들게 되면 Knight는 이러한 것들로 이루어져있다 라는 것을 알고 있어야 한다.

int main()
{
	Knight k1;
}

이런식으로 객체스택영역에 만들어줄 수 있다. (아직 동적할당은 하지 않았음으로)
그리고 객체가 하는 행동(함수)까지 넣어서 만들어 줄 수 있다.

class Knight
{
public:
	// 멤버 함수
	void Attack() { cout << "Attack" << endl; }
    void Die() { cout << "Die" << endl; }
    void HealMe(int value) 
    {
    	_hp += 10;
    }
public:
	// 멤버 변수
	int _hp;
	int _attack;
	int _defence;
};

일단 행동(함수)멤버 함수라고 부른다.
그런데 여기서 이해가 안되는 부분이 있을 수 있을 것이다.
HealMe()이 함수 부분에서 어떤식으로 hp를 받아들이는가? 이다.

한 번 하나씩 따라가보자
Knight라는 객체가 만들어졌을 때 일부분(멤버 변수)은 메모리에 할당이 될 것이고, 일부분(함수)은 메모리가 할당되는 것이아닌 코드영역에 들어가는 것이다.

즉 위 코드로 보자면 hp, attack, defence스택영역에 들어가지만 Attack(), Die(), HealMe()같은 함수들은 코드로 변경이 되어 코드영역에 들어가게 된다고 볼 수 있다.
Knight에 객체를 100개 만들어도 함수부분이 100개씩 만들어지는 것은 아니다.
Knight(class)에 함수를 만든다면 그 Knight를 대상으로 실행한다고 생각을 하면 된다.

그리고 HealMe(int value) 함수

void HealMe(Knight* Kptr, int value) 
{
	// 자기자신의 포인터를 받음
	Kprt->hp += value;
}

와 거의 유사하다고 볼 수 있다.(저 부분이 숨겨져있는 것이라고 생각하면 된다)
클래스안에 들어가있으므로 그 클래스멤버변수를 포함해가지고 객체가 만들어지기 때문에 함수 내부적으로 hp를 건드릴 수 있게 되는것이다.

그리고 멤버 함수안에서 자기자신에 대한 주소를 this라는 문법으로 사용할 수 있다.

void HealMe(int value) 
{
	// 자기자신의 포인터를 받음
	this->hp += value;
}

즉 이런식으로 this가 숨겨져있다고 보면 된다.
객체를 대상으로 함수가 실행된다라고 볼 수 있다.

객체지향에서 가장 중요한 것은 멤버 변수멤버 함수라고 볼 수 있지만
나중에 또 다양한 문법들을 배우면서 멤버 변수멤버 함수를 더욱 편리하게 사용할 수 있게 될 것이다.

생성자 (constructor)와 소멸자(destructor)

생성자클래스객체가 만들어질 때 무조건 호출되는 함수라고 볼 수 있다.
생성자는 특이하게 함수이지만 리턴타입이 없다 그리고 함수 이름을 클래스와 동일하게 만들어주면 된다.

그리고 또 소멸자라는 것이있는데 이 소멸자객체소멸이 될 때 호출되는 함수이다.
소멸자도 특이한것이 함수이지만 리턴타입이 없고인수도 없다 그리고 함수 이름은 생성자와 똑같이 클래스와 동일하게 만들어주면 된다.

입장과 퇴장이라고 생각을 하자

생성자클래스의 초기화(멤버 변수)를 돕기위해 만들어졌고,
소멸자클래스의 청소(메모리 정리)를 위해 만들어졌다고 볼 수 있다.

여기서 이런 말이 나올 수 있다.

"꼭 호출되는 함수라면서 클래스에 생성자소멸자를 안만들어도 작동이 잘되는데요?"

이렇게 생성자소멸자를 만들어주지 않는 경우에는 자동을 컴파일러기본 생성자(default constructor)와 소멸자(default destructor)를 만들어 준다.

그리고 소멸자는 하나만 만들 수 있지만 생성자같은 경우에는 여러개를 만들어줄 수 있다.

class Knight
{
public:
	// 기본 생성자
	Knight()
    {
    	_hp = 0;
        _attack = 0;
       	_defence = 0;
    }
    
    // 기타 생성자(생성자 오버로딩)
    Knight(int hp, int attack, int defence)
   	{
    	_hp = hp;
        _attack = attack;
        _defence = defence;
    }
public:
	int _hp = 0;
	int _attack;
	int _defence;
};

만든 생성자를 보통 기타 생성자라고 부른다.
그리고 만약 _hp라고 네이밍 컨벤션을 안정하고 hp라고 멤버 변수를 만들었을 때
hp = hp라고 하면 컴파일러가 구분을 못하기 때문에
this->hp = hp this포인터로 자기자신의 hp를 가르켜서 값을 집어넣을 수 있다.

기타 생성자를 만들면 knight 객체를 생성할 때 자신이 원하는 값으로 초기화를 하여 생성할 수 있다.
Knight k1(100, 10, 1);

그리고 컴파일러는 우리가 다른 생성자를 만들게 된다면 기본 생성자를 더 이상 만들어주지 않게 되기 때문에 우리가 만든 생성자형식으로 생성을 하거나 기본 생성자 형식으로 생성자를 직접 하나 더 만들어 주면 된다.

그리고 또 복사 생성자 라는 것이 있다.
복사 생성자동일 타입의 객체를 복사해서 동일한 상태의 데이터를 가지는 다른 객체를 만드는 것이다.

Knight(const Knight& other)
{
	_hp = other.hp;
    _attack = other.attack;
    _defence = other.defence;
}

이런식으로 복사 생성자를 만들 수 있다.
그럼 어떻게 사용할까?

Knight k1(100, 10, 1);

Knight k2(k1);

이렇게 사용하면 k2가 k1의 데이터를 복사해서 가져오기 때문에 완전히 동일한 값을 가지는 것을 알 수 있다.

총 정리

우리는 객체지향 프로그래밍을 할 때
클래스라는 설계도객체(인스턴스)를 만들어주고 그 객체들의 생성 이후에는 각각 독립적으로 관리해줄 수 있다.
그리고 클래스의 멤버 변수, 함수는 어떤 특정 객체종속적이다.
라는 점이 중요하다고 볼 수 있다.

그리고 객체지향의 3대 요소(oop 3대 요소)라고 해서

  • 상속성(Inheritance)
  • 은닉성(캡슐화(Encapsulation))(Data Hiding)
  • 다형성(Polymorphism)

등이 있는데 이것들은 다음에 시간이 된다면 블로그에 정리를 해보겠다.

profile
고3, 프론트엔드

0개의 댓글