Class
오늘은 c++에서 도입된 개념인 Class와 그를 이용한 객체지향적 프로그래밍에 대해서 공부해 보았다.
실제로 개발현장에서 프로젝트를 진행하다 보면 완성된 프로젝트는 계속 사용되는 경우가 많다고 한다. 그 결과 개발을 하는 시간보다는 유지보수를 해야 하는 시간이 더 길어지고 수정에 필요한 비용도 크게 증가한다. 그렇기에 개발단계에서부터 유지보수를 고려한 개발을 진행해야 하고 재사용성을 높이는 방향으로 구현해야 한다고 한다. 이를 위한 것이 객체지향 프로그래밍
이러한 객체지향적 프로그래밍이 가능하게하는 첫 번째 방법이 Class이다.
그렇다면 Class는 무엇일까? 예를 들어 우리가 살면서 항상 하는 '호흡'에 대해서 생각해 보도록 하자 호흡의 메커니즘은 간략히 보면
1. 공기를 들이쉰다.
2. 들어온 공기 중 산소를 폐에서 받아들인다.
3. 축적된 당과 산소를 이용해서 세포호흡을 진행 ATP에너지를 생성한다.
4. 이산화탄소와 같은 부산물을 내보낸다.
로 볼 수 있다. 세부적인 내용은 더욱 복잡하지만 위의 4단계의 과정만으로 생각해 보자. 우리가 숨을 쉴 때 저런 단계별 프로세스를 모두 의식하고 진행하는가? 그랬다면 나도 그렇고 전 인류는 숨 쉬는 행위만을 위해 모든 신경을 곤두세워야 했을 것이다. 우리는 저러한 세부 단계를 모두 의식하지 않고 단순히 숨을 들이 마시고 내쉰다는 단계만을 의식하며 그마저도 의식하지 않는다면 무의식적으로 호흡하기도 한다.(당신은 숨쉬는 것을 의식하게 됩니다.) Class를 선언하는 이유도 이와 같다. 내부적인 내용은 숨겨 따로 관리하고 사용자나 외부의 인물은 그저 작동하는 과정이나 결과만 보게 된다. 이를 프로그래밍에 빗대어 생각해 보자 호흡이라는 Class를 만들어주자. 그리고 그 Class안에 변수로서 산소와 포도당을 변수로서 선언한다. 그리고 그 변수들로 작동하는 세포호흡이라는 함수를 선언해 준다. 이때의 산소와 포도당을 멤버변수, 세포호흡을 멤버함수라고 한다. 그러고 나서 나라는 객체에게 호흡이라는 Class를 부여해 주고 멤버함수인 세포호흡을 반복하도록 호출해 주면 외부적으로 봤을 때는 호흡의 세부적인 과정은 보이지 않지만 산소라는 멤버변수가 들어갔다가 이산화 탄소라는 리턴값이 나오는 것만 알면 충분히 호흡이 가능하다.
Class의 정의
그럼 한번 Class를 정의해 보도록 하자.
class Breathing
{
//멤버함수
string Cellular_Respiration(); // 1. 세포호흡 멤버함수 클래스 외부에서 정의하기
string Cellular_Respiration() // 2. 세포호흡 멤버함수 클래스 내부에서 정의하기
{
/* 함수 동작 정의*/
return 결과값 ;
}
// 멤버변수
string Oxygen;
string Grapesugar;
};
이런 식의 코드를 작성해 주면 Breathing이라는 Class를 정의해 줄 수 있다. 멤버함수를 정의하는 방법은 위의 경우처럼 두 가지가 있는데 하나는 이름만 정해두고 세부 동작은 클래스 스코프 외부에서 정의해 주는 것(전방선언처럼) 나머지 하나는 클래스 스코프 내부에서 정의하는 방법이다.
접근 지정자
클래스는 접근지정자를 상요하여 멤버의 접근 권한을 제어할 수 있다. C++에서는 접근 지정자 다음과 같이 있다.
public, private, protected으로 각각의 역할을 알아보도록 하자
a. public : 멤버함수에 일반적으로 사용하는 접근 지정자 public멤버는 클래스 외부에서 멤버 접근 연산자로 접근 가능
b. private : class 키워드를 사용할 때 접근 지정자를 따로 명시하지 않는다면 기본적으로 private로 설정됨 일반적으로 멤버 변수에 사용되며 클래스 외부에서 접근을 시도하면 에러가 발생한다. 반대로 클래스 내부(엄밀히 말하면 후술 할 기본 클래스의 선언파트)에서의 접근은 자유롭다.
c. protected : 정의된 클래스의 내부(기본 클래스의 선언파트)와 후술할 파생 클래스의 선언파트에서 접근 가능하게 하는 지정자로서 외부나 다른 파생 클래스에서의 접근은 불가하다.
getter와 setter
private에 있는 변수를 제어할 때는 어떻게 해야 할까? 이럴 때 사용하는 대표적인 방법이 getter, setter이다. 각각 멤버변수를 바꿀 때setter를, 가져올 때getter를 사용한다. 이렇게 하면 변수를 직접 제어하지 않고 데이터를 안전하게 다룰 수 있다.
string setOxygen(char O2)
{
this->Oxygen = O2; // 밖에서 받은 변수 oxygen을 Oxygen에 대입
}
string getOxygen() { return Oxygen;} // 멤버변수 Oxygen을 값으로 반환
이런 식으로 setter를 호출하여 클래스 내의 변수를 간접적으로 수정한다. (여기서 this->는 '클래스 내의'라는 의미) 이런식으로
getter , setter을 사용하는 이유는 변수가 실수로 수정되는 경우를 방지하고 우리가 원하는 범위 내의 변숫값만 받아오기 용이하기 때문에 사용된다. 자세한 활용법은 후술 할 객체지향 프로그래밍을 설명하면서 다루도록 하자
생성자
생성자는 객체를 생성할 때마다 한 번씩 자동으로 호출되는 특별한 멤버함수이다. 생성자는 보통 필요한 멤버 변수를 초기화하거나 객체가 동작할 준비를 하기 위해 사용한다. 생성자는 반환형을 명시하지 않으며 이름과 동일한 이름을 가진 함수로 정의된다. 생성자의 종류는 여러 가지인데 예제로 하나하나 알아보자.
1. 기본생성자 : 인자가 없는 생성자
class Breathing
{
public:
string Oxygen;
string Grapesugar;
// 기본 생성자
Breathing()
{
Oxygen = "O2";
Grapesugar = "C6H12O6";
}
};
2. 매개변수가 있는 생성자
class Breathing
{
public:
string Oxygen;
string Grapesugar;
// 매개변수가 있는 생성자
Breathing(string a, string b)
{
Oxygen = a;
Grapesugar = b;
}
};
3. 기본매개변수가 있는 생성자 : 위의 두 가지 경우 모두 처리 가능
class Breathing
{
public:
string Oxygen;
string Grapesugar;
// 기본 매개변수가 있는 생성자
Breathing(string a = "O2", string b = "C6H12O6")
{
Oxygen = a;
Grapesugar = b;
}
};
int main(void)
{
Breathing b1; // 기본값 사용
Breathing b2("H2", "Death"); // 값을 지정하여 생성(산소 대신 수소가...)
return 0;
}
생성자에 필요한 매개변수가 부족하거나 잘못된 자료형의 매개변수를 입력하거나 생성자를 선언만 하고 정의하지 않거나 기본 생성자를 잘못 호출하거나 등등 당연하게도 이러한 실수들에는 에러가 발생하기에 사용에 주의해야 한다.
이러한 생성자를 이용해서 Breathing class에 생성자를 적용해 보자 호흡에는 반드시 O2와 C6H12O6이 들어가야 올바른 리턴을 반환하고 다른 인자가 들어오면 실패값을 반환해야 하기 때문에 세 종류의 생성자 중에서 기본매개변수가 있는 생성자를 사용해 보도록 하자.
#include <string>
#include <iostream>
using namespace std;
class Breathing
{
public:
string Oxygen;
string Grapesugar;
// 기본 매개변수가 있는 생성자
Breathing(string a = "O2", string b = "C6H12O6")
{
this->Oxygen = a;
this->Grapesugar = b;
}
//멤버함수 선언
string Cellular_Respiration(string Oxygen, string Grapesugar)
{
string result = "CO2"; // 사실 인자가 뭐냐에 따라 조건문을 걸어 결과를 다르게 해야하지만~
return result;
}
};
int main(void)
{
Breathing b;
cout << b.Cellular_Respiration(b.Oxygen, b.Grapesugar) << endl;
return 0;
}
클래스를 선언 후 그 클래스의 생성자를 이용해 b라는 객체를 생성해 주고 b의 멤버함수를 호출해서 결과를 출력하는 흐름이다. 자연스럽게 몸에 익도록 반복 숙달해 주도록 하자
여담으로 생성자를 선언할 때 즉 객체 생성 직후 멤버의 생성 단계에서 바로 초기화하는 방법이 있다.
class Breathing
{
public:
string Oxygen;
string Grapesugar;
// 매개변수가 있는 생성자 + 생성즉시 변수 초기화
Breathing(string a, string b) : Oxygen(a), Grapesugar(b) {}
};
이 방법이 기존에 설명한 방법보다 여러 측면에서 유리하니 앞서 설명한 방법은 이런 게 있구나 정도로 넘어가고 이 방식을 사용하는 것을 습관을 들이자.
코드를 나누어서 작성하자
앞서 클래스에 대한 내용을 설명하면서 사용자가 알 필요 없는 정보는 숨긴다고 했었다. 하지만 우리는 여태껏 하나의 파일에 선언, 구현 실행까지 모두 넣으면서 클래스를 작성했다. 내부내용은 사용자가 알 필요 없기 때문에 개발자는 선언부는 헤더파일에 구현부는 소스파일에 분리하여 작성한 뒤 필요한 곳에서 헤더를 include 하여 작성한다. 이렇게 작성하는 것이 보수성과 가독성에서 매우 유리하기에 꼭 이 방법을 사용하는걸 최우선적으로 고려하도록 하자. 그럼 일단 선언을 위한 헤더파일부터 작성해 주자.

// 헤더파일에 클래스 선언
#ifndef BREATHING_H
#define BREATHING_H
#include <string>
using namespace std;
class Breathing
{
public:
string Oxygen;
string Grapesugar;
Breathing(string a = "O2", string b = "C6H12O6")
{
this->Oxygen = a;
this->Grapesugar = b;
}
string Cellular_Respiration(string Oxygen, string Grapesugar);
};
#endif
여기서 주목해야 할 점은 ifndef와 define이다. 중복선언이 되지 않게 하기 위해 BREATHING_H가 정의되지 않았을 경우에만 정의되도록 하는 작업이 필요하다.
cpp파일에서 멤버함수의 동작을 정의
#include "breathing.h"
using namespace std;
//멤버함수 정의
string Breathing::Cellular_Respiration(string Oxygen, string Grapesugar) // 스코프한정자 :: 는 나중에 배워보자
{
string result = "CO2";
return result;
}
이런 식으로 클래스의 정의와 구현을 각각 헤더파일과 cpp에 작성해주고 프로젝트의 main.cpp에서 생성자를 호출해주면 된다.
#include "breathing.h"
#include <iostream>
using namespace std;
int main(void)
{
Breathing b;
cout << b.Cellular_Respiration(b.Oxygen, b.Grapesugar) << endl;
return 0;
}
이런식으로 작성 후 실행해 주면 정상적으로 함수의 결과가 출력된다.
객체지향 프로그래밍
자 여태까지 class에 대해서 배웠다. class는 객체지향 프로그래밍을 위한 방법 중 하나라고 앞에서 설명한 바 있다. 그러면 객체지향 프로그래밍을 하기 위해선 어떻게 해야 할까? 국내 RPG게임 중 하나이면서 내 나이대의 사람이라면 익숙할 게임인 '메이플스토리'를 예시로 한번 들어보자 메이플스토리에는 직업군이 40종이 넘어갈 정도로 많지만 간단하게 5가지 직업류 전사, 마법사, 궁수, 도적, 해적이 있다. 우리는 Job이라는 클래스를 정의해서 이러한 직업들을 관리하고자 한다. 어떤 직업이든 간에 캐릭터의 이름, 레벨, 스탯은 공통적으로 갖고 있는 요소이다. 이러한 공통적인 요소를 갖고 있는 클래스를 기본클래스(또는 부모클래스)라고 한다.
이제 세부적으로 직업을 분리해 보자 직업별로 가지각색의 개성이 존재한다. 전사는 힘 스탯이 전투력에 관여하는 바가 크고, 마법사는 지능, 도적은 행운, 궁수는 민첩, 해적은 힘 또는 민첩이 그 역할을 한다. 또 사용하는 무기가 다르며 장착할 수 있는 방어구 타입도 다르다 이러한 공통적인 요소를 포함한 채 세부적인 요소로 나누어 새롭게 정의한 기본클래스의 하위 클래스를 자식클래스(또는 자식클래스)라고 한다.
기본클래스와 파생클래스 그리고 상속
말로 설명하면 긴가민가할 것이다. 실제로 복잡한 내용이기도 하거니와 필자의 말주변이 좋지 못하여 설명을 좋게 하지 못한 바도 있을 것이다. 실제로 기본클래스와 자식클래스를 정의해 주면서 알아보도록 하자.
#ifndef RPG_H
#define RPG_H
#include <iostream>
#include <string>
using namespace std;
class RPG
{
/* 닉네임, 레벨, 스탯, HP, MP 등은 다른 객체에서도 자주 참조하기에 public 접근 지정자를 사용한다. */
public:
string nickname;
int characterlevel;
int STR;
int DEX;
int INT;
int LUK;
int HP;
int MP;
/* 생성자를 정의하면서 멤버변수 초기화 */
RPG(string name, int level) :
nickname(name), characterlevel(level), STR(4), DEX(4), INT(4), LUK(4), HP(100), MP(100)
{}
};
#endif
#pragma once
/* Rpgclass.h에 작성 */
#include "Rpgclass.h"
#include <iostream>
#include <string>
using namespace std;
/* 아직 멤버함수는 어느것도 선언하지 않았기에 잠시 비워둔다. */
이런 식으로 RPG클래스의 선언파트를 Rpgclass.h에 작성해 주고 Rpgclass.cpp에 구현파트를 작성해 준다.
이번에는 파생클래스를 정의해 주자 간단한 예시로 warrior.h를 만들어 Warrior라는 RPG의 파생 클래스를 선언해 주자
#pragma once
#include "Rpgclass.h"
using namespace std;
class Warrior : public RPG
{
public:
};
그다음 Warrior.cpp에 멤버함수의 구현을 정의해 주고 다른 직업들도 반복 작업을 해주면 된다. 여기서 핵심은 Rpgclass에 포함되어야 하기에 include "Rpgclass.h"를 꼭 사전에 작성해 주어야 RPG클래스를 제대로 받아올 수 있다. 이런 과정으로 클래스의 상속이 진행된다.
다형성
앞서 메이플스토리에서의 직업군 전사, 궁수, 마법사, 도적, 해적 저마다 사용하는 스탯이 다르고 착용하는 장비가 다르다 만약 우리가 각 직업별로 공격했을 때의 상황을 함수로 작성한다 하면 각 직업별의 공격 함수를 하나하나 다 작성해줘야 할까? 또 새로운 직업군을 추가한다면 또 공격함수를 더 추가할까? 만약 이런 식으로 진행했다면 메이플스토리의 직업군이 40여 종 각각의 공격함수를 선언하고 정의해줘야 했을 것이다. 이를 좀 더 효율적으로 처리하게 하는 게 바로 다형성
다형성이란 기본이 되는 클래스를 만들어 함수의 인터페이스를 정의하고 실제 구현은 파생 클래스에서 담당하게 하는 기법이다. 즉
기본클래스에서 기본 함수의 틀만 만들어 두고 파생클래스에서 각각 함수를 구현해주어 보다 효율적이게 작업을 할 수 있다. 이때 기본클래스에서 만들어두는 함수는 virtual 이라는 접두사를 사용하며 이를 가상함수라고 한다.
virtual 키워드를 붙이면 가상 함수 테이블이 생성되고 이 가상 함수 테이블에는 기본 클래스에서 vitual로 선언된 함수가 파생 클래스에서 재정의된 경우 그 위치가 기록되며 동적 디스패치가 가능하다.
// 기본클래스 에서 가상함수 선언
virtual int func() {};
// 파생클래스에서 가상함수 호출 및 행동 구현
int func()
{ /* 가상 함수를 파생 클래스에서 재정의 */}
오늘은 객체지향적 프로그래밍의 기초가되는 class와 그 활용에 대해서 공부해보았다. 점점 갈수록 어려워지는 c++의 세계다. 뒤쳐지지 않도록 차근차근 내용을 곱씹어 내것으로 만들어 보자.
'unreal 5기' 카테고리의 다른 글
| 250828 언리얼엔진 본캠프 18일차 디자인패턴 (1) | 2025.08.28 |
|---|---|
| 250826 언리얼엔진 본캠프 16일차 템플릿 (1) | 2025.08.26 |
| 250821 언리얼엔진 본캠프 13일차 구조체 (3) | 2025.08.21 |
| 250820 언리얼엔진 본캠프 12일차 STL 기초 (0) | 2025.08.20 |
| 250819 언리얼엔진 본캠프 11일차 포인터 (2) (0) | 2025.08.19 |