<aside> <img src="/icons/list_gray.svg" alt="/icons/list_gray.svg" width="40px" />
목차
</aside>
앞서 공부했던 내용은 절차(함수 | Procedure) 지향적 프로그래밍의 특징을 지니었다. 절차 지향 프로그래밍은 프로그램의 작업을 순차적인 함수들의 연속으로 보고, 이를 절차적으로 처리하는 방식이다.
// Globalization functions.
// ------------------------------------------------------------------
void enterLobby();
void enterCalculator();
// Mode
void ModeSum(int numbers[], int size);
void ModeAvg(int numbers[], int size);
void ModeSort(int numbers[], int size);
// Utility
void swapValue(int &a, int &b);
void sortArr(int numbers[], int size);
void sortArrInvert(int numbers[], int size);
// Mathematical
int sumNumbers(int numbers[], int size);
int avgNumbers(int numbers[], int size);
// Display
void inputArr(int numbers[], int size);
void outputArr(int numbers[], int size);
데이터와 함수가 서로 분리되어 있으며, 함수가 데이터를 가져와서 처리하는 방식으로 동작한다. 즉, 함수(Procedure) 기반으로 이루어진 실행 덩어리라는 소리다.
struct Player {
string name;
int hp;
int attack;
int posX;
int posY;
};
void MovePlayer(Player* player, int x, int y) {
player->posX += x;
player->posY += y;
}
void AttackPlayer(Player* attacker, Player* target) {
target->hp -= attacker->attack;
}
반면, 객체 지향 프로그래밍은 데이터와 이를 처리하는 함수를 하나의 **[객체(Object)]**로 묶어서 생각한다. 객체는 자신만의 데이터를 가지고 있으며, 이 데이터를 처리하는 함수도 함께 포함하고 있다.
절차 지향 vs 객체 지향
| 비교 | 절차 지향 | 객체 지향 |
|---|---|---|
| 구조 | 데이터와 함수가 분리 | 데이터와 함수가 객체로 통합 |
| 구성 | 구조체와 외부 함수 | 클래스 단위로 묶음 |
| 보안 | 기본적으로 public | 접근 제어자로 보호 가능 |
| 재사용 | 재사용과 유지보수 어려움 | 상속으로 확장 가능 |
그렇다면, 객체 지향의 주인공인 클래스를 한번 알아보도록 하자. 클래스는 객체 지향 프로그래밍의 핵심 요소로, 데이터와 기능을 하나로 묶어주는 역할을 한다.
class Player
{
private:
string _name;
int _hp;
int _attack;
int _defence;
int _posX;
int _posY;
public:
void Move(int x, int y)
{
_posX += x;
_posY += y;
}
void Attack(Player& target)
{
int _damage = _attack - target._defence;
if (_damage > 0)
target._hp -= _damage;
}
void Die() {
_hp = 0;
_posX = 0;
_posY = 0;
}
};
클래스는 일종의 객체를 만들기 위한 설계도다. 자동차를 만들기 위해 설계도가 필요하듯, 프로그램에서 객체를 만들 때도 클래스라는 설계도가 필요하다.
| 종류 | 요소 |
|---|---|
| 멤버 변수(Member Variables) | _name, _hp, _attack, _defence, _posX, _posY |
| 멤버 함수(Member Functions) | Move(), Attack(), Die() |
코드를 살펴보면, 내부에 **구조체(Struct)**와 비슷하게 변수를 선언할 수 있지만, 구조체와 다르게 함수까지 기입하여 하나의 객체로 사용할 수 있다는 것이 차이점이다.
기존의 절차 지향에서는 **데이터(구조체)**와 함수가 분리되어 있었다면, 클래스에서는 이 둘이 하나로 묶여있다는 점이 가장 큰 특징이다. 구조체의 멤버 변수들은 단순히 데이터를 저장하는 용도였지만, 클래스의 멤버 변수와 멤버 함수는 서로 긴밀하게 연관되어 하나의 객체로서 동작한다.
인스턴스화(Instantiate)
구조체를 사용하는 것과 비슷하게도, 클래스는 **인스턴스화(Instantiate)**하여 객체를 생성할 수 있다. main() 함수를 통해서 실행한 것을 살펴보자.
int main()
{
// Player 객체 생성
Player warrior;
Player archer;
delete warrior;
delete archer;
return 0;
}
class Player
{
private:
string _name;
int _hp;
int _attack;
public:
// 생성자 (Constructor)
Player()
{
cout << "플레이어 생성!" << endl;
_name = "Unknown";
_hp = 100;
_attack = 10;
}
// 매개변수가 있는 생성자
Player(string name, int hp, int attack)
{
cout << name << " 플레이어 생성!" << endl;
_name = name;
_hp = hp;
_attack = attack;
}
// 소멸자 (Destructor)
~Player()
{
cout << _name << " 플레이어 제거!" << endl;
}
};
클래스의 객체가 생성되고 소멸될 때 자동으로 호출되는 특별한 멤버 함수가 있다. 바로 **생성자(Constructor)**와 **소멸자(Destructor)**다.
생성자와 소멸자는 왜 굳이 존재할까? 라고 의문이 들겠지만, 가장 큰 이유는 객체의 생명주기를 관리하기 위해서다.
이 두 개의 특수 멤버 함수를 통해서 프로그램이 안정이고 효율적으로 메모리를 관리할 수 있게 된다.
생성자와 소멸자의 특징은 다음과 같다.
생성자(Constructor)
Player()
{
cout << "플레이어 생성!" << endl;
_name = "Unknown";
_hp = 100;
_attack = 10;
}
객체가 생성될 때 자동으로 호출되는 함수로, 객체의 초기화를 담당한다.
소멸자(Destructor)
~Player()
{
cout << _name << " 플레이어 제거!" << endl;
}
객체가 소멸될 때 자동으로 호출되는 함수로, 객체의 정리 작업을 담당한다.
위 코드에서 볼 수 있듯이, 객체가 생성될 때는 생성자가, 객체가 소멸될 때는 소멸자가 자동으로 호출된다. 이를 통해 객체의 생성과 소멸 시점에서 필요한 초기화와 정리 작업을 자동으로 수행할 수 있다.
은닉성..? 은닉이라는 용어는 보통 "숨긴다", **"감춘다"**라는 의미로 받아들이기 쉽다. **"도둑질한 물건을 은닉했다"**라는 표현처럼 부정적인 의미로 생각할 수도 있다.
하지만, 프로그래밍에서 쉽게 말하면 **"데이터를 보호하고 관리하기 위해 접근을 제한하는 것"**이라고 생각하면 된다. 마치 중요한 문서를 금고에 넣어두고, 특정 절차를 통해서만 접근할 수 있게 하는 것과 같다.

예를 들어, 자동차를 운전할 때를 생각해보면 이해하기 쉽다. 운전자는 시동 버튼, 핸들, 브레이크, 엑셀 같은 것들만 조작한다. 엔진이 어떻게 돌아가는지, 연료가 어떻게 분사되는지 같은 복잡한 내부 동작은 직접 건드리지 않는다. 이것이 바로 은닉성의 좋은 예시다.
이를 은닉성을 제어하는 접근 제어자를 구분하여 코드로 표현해보면 다음과 같다.
class Car
{
private:
// 외부에서 직접 접근할 수 없는 엔진 관련 변수들
bool engineOn;
int rpm;
float fuelLevel;
void startEngine() { /* 엔진 시동 로직 */ }
void stopEngine() { /* 엔진 정지 로직 */ }
public:
// 사용자가 실제로 사용하는 간단한 인터페이스
void turnKey()
{
if (!engineOn) {
startEngine();
engineOn = true;
}
}
void pressAccelerator(int power)
{
if (engineOn && fuelLevel > 0) {
rpm += power;
}
}
void pressBrake()
{
rpm = 0;
}
};