절차지향 프로그래밍으로 textRPG 코드를 예시로 짜보면 다음과 같다
이렇게 프로그램을 짜게 된다면 순서를 이 상태로 설계를 했기 때문에 순서를 바꾸기 힘들어 유연성이 사라지며 코드의 확장이 어렵다. 그렇기 때문에 객체지향언어로 확장성에 용이하게 프로그래밍할 수 있다. C++은 절차지향언어인 C언어에 객체지향언어의 개념인 클래스가 추가되었기 때문에 애매한 포지션에 있지만 그래도 객체지향언어이다. 일단 객체지향언어의 몇 가지 개념에 대해 알아보고 가자.
객체지향언어의 특징을 적용하면 역할과 구현을 구분하며 내부 동작을 외부로 최소화하여 노출해 객체들 간의 직접적인 결합을 피하고, 상위 클래스와 공유되는 속성과 기능을 통해 반복적인 코드를 최소화하고, 느슨한 관계 설정을 통해 보다 유연하고 변경이 용이한 프로그램 설계가 가능하다.
설계원칙은 각 5가지 원칙의 앞글자를 따서 SOLID 원칙이라고도 부른다.
처음 객체지향언어를 배우면서 이같은 특징과 원칙을 적용하는 것은 쉽지 않으므로 나중에 언어와 엔진에 관해 차차 적응한 후 시도해보면 좋다고 생각한다. 배워야할 것은 한 번에 하나씩, 내가 모르는 개념이나 영역이 한 영역으로만 있어야 공부하기가 수월하니 말이다.
A user-defined type or data structure declared with keyword class that has data and functions as its members whose access is governed by the three access specifiers private, protected or public
// 클래스의 선언
class 클래스이름
{
private: // 접근 지정자, 외부로의 노출을 막고 싶을 때
멤버변수타입 멤버변수이름1;
protected: // 접근 지정자, 상속 관계에서만 노출되고 싶을 때
멤버변수타입 멤버변수이름2;
public: // 접근 지정자, 공개하고 싶을 때
멤버변수타입 멤버변수이름3;
// 접근 지정자는 여러번 사용할 수 있음
// 다음 접근 지정자 선언까지 해당 지정자의 접근 범위로 멤버들이 포함됨
// 가독성이 좋게 멤버 변수와 멤버 함수를 나눠서 사용함
private:
반환형 멤버함수이름1(매개변수의자료형 매개변수이름1)
{
문장들; // return 키워드 포함
}
protected:
반환형 멤버함수이름2(매개변수의자료형 매개변수이름2);
public:
반환형 멤버함수이름3(매개변수의자료형 매개변수이름3);
}; // 클래스 선언의 끝에 세미콜론을 붙여줘야함
반환형 클래스이름::멤버함수이름2(매개변수의자료형 매개변수이름2)
{
문장들; // return 키워드 포함
}
inline 반환형 클래스이름::멤버함수이름3(매개변수의자료형 매개변수이름3)
{
문장들; // return 키워드 포함
}
클래스의 구성은 다음 두 가지로 이루어져있고 클래스의 정의라는 말은 쓰지 않는 것 같다. 멤버함수의 선언은 클래스의 선언 내에 있기도하고 멤버변수는 클래스의 선언에 포함되며 정의할 것이 없에 멤버변수의 정의는 없다.
접근 지정자라는 것은 아래에서 설명하도록하고 그외의 각 다른 멤버 함수의 정의를 보자. 각 다른 방식으로 멤버함수 3개를 정의했는데, 멤버함수이름1은 클래스 내부에 있기 때문에 스코프 연산자(::, 범위 지정 연산자)를 사용하지 않아도 되지만 나머지 두 멤버함수는 클래스 외부에서 정의하고 있기 때문에 어떤 클래스에 속해있는지 알려주기 위해 함수 이름 왼편에 스코프 연산자를 같이 사용한다. 또한 멤버함수이름1과 멤버함수이름3은 인라인함수이다. 멤버함수이름2는 일반 멤버함수이다.
게임 개발을 위해 배우는 C++이니 textRPG 를 구현해가며 객체지향을 실습해보도록 한다. 자동차를 이용하여 배우는 객체지향이 깔끔하고 많이 사용하긴하지만 이 방식을 시도해보도록한다.
코드는 게임이 돌아가게끔 만들면서도 현재 실습해야하는 개념들만 적용할 수 있도록 노력해볼 것이다. 그래서 더 효율적인 방식이 있겠지만 바로 배울 것만 사용하도록한다.
// textRPG Table Size
const int TILE_ROW = 10;
const int TILE_COL = 10;
// textRPG
class Specialty // Class라는 이름을 쓰고 싶지만, 키워드이므로 특기라는 Specialty 사용
{
public: // 접근 지정자
void Initialize() // 자동 인라인 함수
{
m_posX = 0;
m_posY = 0;
}
void Print();
void Move(int x, int y);
private: // 접근 지정자
bool IsSafePosition(int x, int y);
protected: // 접근 지정자
int m_posX;
int m_posY;
};
void Specialty::Print() // 멤버함수 정의
{
cout << "Pos : " << m_posX << "," << m_posY << endl;
}
void Specialty::Move(int x, int y) // 멤버함수 정의
{
if (IsSafePosition(x, y))
{
m_posX = x;
m_posY = y;
}
}
bool Specialty::IsSafePosition(int x, int y) // 멤버함수 정의
{
if (x < 0 || y < 0 || TILE_ROW <= x || TILE_COL <= y)
{
return false;
}
return true;
}
클래스에서 각 멤버변수나 멤버함수 앞에 붙여 각각에 대한 접근 권한을 지정
객체를 사용할 수 있는 범위라면 어디서나 접근 가능한 공개된 멤버
해당 멤버가 속한 클래스의 상속 관계에서도 사용 가능
해당 멤버가 속한 클래스의 멤버함수에서만 사용 가능
클래스를 사용하기 위해서는 그 클래스의 타입의 객체를 선언해야하고 이렇게 메모리에 할당된 객체를 말함
클래스를 선언하는 것으로는 메모리 상에 적재되지 않는다. 구조체도 이와 마찬가지다. 우리가 구조체를 정의한다고해서 이 정의된 구조체의 멤버 변수를 바로 다룰 수 없었다. 이 구조체의 변수를 선언해서 선언된 변수의 멤버를 조작했다. 변수를 선언할 때 메모리에 적재되고 우리는 이 메모리에 들어있는 값을 조작하는 것이다. 클래스도 마찬가지다. 정의된 클래스 타입으로 변수를 생성하고 이에 접근해서 사용해야하고, 변수를 생성했을 때 그제서야 메모리에 적재된다.
int main()
{
Specialty player1;
player1.Initialize();
Specialty player2;
player2.Initialize();
player2.Move(2, 3);
player1.Print();
player2.Print();
// player1.IsSafePosition(1,2) // IsSafePosition 함수가 private이기에 클래스 밖인 main에서는 호출 불가능
// protected도 동일하다고 생각하면 됨
}
// 출력 결과
// Pos : 0,0
// Pos : 2,3
멤버의 접근 방식은 구조체와 동일하다. .와 → 같은 멤버 참조 연산자를 이용한다.
컴파일러에 의해 생성되는 포인터
멤버함수를 호출한 객체를 가르키고 멤버함수에서만 사용 가능
void Specialty::Move(int x, int y)
{
if (IsSafePosition(x, y))
{
this->m_posX = x; // this-> 를 명시적으로 붙여준 것
this->m_posY = y; // this-> 를 명시적으로 붙여준 것
}
}
// this 포인터를 실체화했을 때의 모습
void Specialty::Move(Specialty* const this, int x, int y) // this 포인터가 숨은 인자로 전달됨, const는 주소를 변경하지 못하게함
{
if (IsSafePosition(x, y))
{
this->m_posX = x;
this->m_posY = y;
}
}
player1.Move(&player1, 2, 3) // 호출 예
// 잘못된 방법을 보여주는 예
// 매개변수는 m_을 붙이지 않지만 예시를 보이기 위해 붙임(member가 아니기에 붙이지 않음)
// this->이름과 이름의 차이점을 알기 위해서 멤버변수랑 이름을 같게 만듬
void Specialty::Move(int m_posX, int m_posY) // 매개변수의 이름을 멤버변수와 동일하게 한다면
{
if (IsSafePosition(m_posX, m_posY))
{
m_posX = m_posX; // 멤버변수는 명시적으로 적어주어야 멤버변수에 대입이됨
m_posY = m_posY; // 적지 않는다면 매개변수에 매개변수값을 대입하는 것이므로 의미가 없고 원하는 기능이 아닌 오류임
}
}
참고한 글