객체 지향 프로그래밍의 핵심: 클래스
들어가며
객체 지향 프로그래밍(OOP)은 현대 소프트웨어 개발의 주요 패러다임 중 하나로, 코드 재사용, 유지보수 용이성, 확장성 측면에서 많은 이점을 제공한다. OOP의 핵심 개념은 클래스와 객체이며, 이번 포스팅에서는 클래스에 대해 심층적으로 논의하고자 한다.
클래스: 사용자 정의 타입
클래스는 객체를 생성하기 위한 청사진 또는 템플릿으로, 객체의 속성(데이터)과 행동(메서드)을 정의한다. 즉, 클래스는 사용자가 직접 정의하는 데이터 타입이라고 할 수 있다.
예를 들어, '학생'이라는 객체를 표현하기 위해 Student
클래스를 정의할 수 있다. Student
클래스는 학생의 이름, 학번, 성적과 같은 속성과, 수업 등록, 성적 확인, 정보 수정과 같은 행동을 포함할 수 있다.
정리하면, 클래스에는 다음과 같이 2가지 주요한 요소가 있다.
- 속성 (Properties): 객체의 상태를 나타내는 변수들이다.
Student
클래스의 경우,name
,id
,grade
등이 속성이 될 수 있다. - 행동 (Behaviors): 객체가 수행할 수 있는 동작을 나타내는 메서드들이다.
Student
클래스의 경우,enrollCourse()
,checkGrade()
,updateInfo()
등이 행동이 될 수 있다.
객체: 클래스의 인스턴스
객체는 클래스를 기반으로 생성된 실체이다. 즉, 클래스가 청사진이라면 객체는 그 청사진을 바탕으로 만들어진 건물과 같다. 하나의 클래스로부터 여러 개의 객체를 생성할 수 있으며, 각 객체는 고유한 속성 값을 가진다.
예를 들어, Student
클래스를 이용하여 student1
이라는 이름의 객체를 생성할 수 있다. student1
객체는 자신의 이름, 학번, 성적 정보를 가진다.
C++에서의 클래스와 구조체
C++에서는 클래스와 유사한 개념으로 구조체(struct)가 있다. 둘 다 데이터와 메서드를 캡슐화할 수 있다는 점에서 유사하지만, 중요한 차이점이 있다. 바로 접근 제어 지시자(Access Specifier)이다.
클래스는 public
, private
, protected
세 가지 접근 제어 지시자를 사용하여 멤버 변수 및 메서드에 대한 접근을 제한할 수 있다. (default : private)
public
: 모든 곳에서 접근 가능private
: 클래스 내부에서만 접근 가능protected
: 클래스 내부 및 파생 클래스에서 접근 가능
반면 구조체는 기본적으로 모든 멤버가 public
으로 설정된다. 이러한 차이점은 객체 지향 프로그래밍의 핵심 원리 중 하나인 캡슐화(Encapsulation)를 구현하는 데 중요한 역할을 한다. 캡슐화는 객체의 내부 데이터를 외부로부터 보호하고, 메서드를 통해서만 접근하도록 제한하여 데이터 무결성을 유지하는 것을 의미한다.
struct vs class |
위와 같은 code가 있다고 할 때, struct는 오류가 발생하지 않지만, class에서는 오류가 발생한다. 이는 struct의 경우 모든 멤버가 public으로 설정되어 있어 외부에서 값을 직접적으로 initialize할 수 있는 반면, class는 모든 멤버가 기본적으로 private로 설정되어 있어 불가능하다는 점에서 기인한다.
impossible to initialize in class |
*참고) Dereferencing - pointer 다시보기
Student student;를 선언하면 student
라는 이름의 Student
객체가 스택 메모리에 생성된다. 이때 student
는 객체 자체를 나타내는 것이므로, 멤버 변수나 멤버 함수에 접근할 때 .
연산자를 사용한다.
예를 들어, student
객체의 id
멤버 변수에 값을 할당하려면 다음과 같이 작성한다.
student.id = 20230001;
반면에, Student* studentPtr = new Student();
와 같이 선언하는 경우는 studentPtr
이라는 이름의 포인터 변수가 스택 메모리에 생성되고, 이 포인터 변수가 힙 메모리에 할당된 Student
객체를 가리키게 된다. 이 경우에는 studentPtr
이 포인터이므로 멤버 변수나 멤버 함수에 접근할 때 (* object name)이나 ->
연산자를 사용한다.
studentPtr->id = 20230002;
요약하자면,
Student student;
➡️ 객체 직접 사용, 스택 메모리 할당,.
연산자 사용Student* studentPtr = new Student();
➡️ 객체를 포인터로 사용, 힙 메모리 할당, (* object name or->
연산자 사용
생성자: 객체 초기화
그렇다면 클래스에서 initialize할 방법은 없을까? 물론 존재한다. 클래스는 객체 생성 시 멤버 변수를 초기화하기 위해 생성자(Constructor)를 사용한다. 생성자는 클래스와 이름이 같은 특수한 메서드로, 객체 생성 시 자동으로 호출된다.
class Student {
private:
int* m_pID;
string m_name;
public:
Student(); // 생성자
Student(int, string); // 생성자 오버로딩
};
Student::Student() {
m_pID = new int(0);
m_name = "Alice";
}
Student::Student(int id, string name) {
m_pID = new int(id);
m_name = name;
}
위 예제에서 Student
클래스는 두 개의 생성자를 가지고 있다.(생성자 오버로딩) 첫 번째 생성자는 인자 없이 호출되며, m_pID
를 0으로, m_name
을 "Alice"로 초기화한다. 두 번째 생성자는 정수형 id
와 문자열 name
을 인자로 받아 m_pID
와 m_name
을 초기화한다.
이렇게 생성자 오버로딩을 한다면 다양한 방식으로 멤버 변수들을 초기화할 수 있다.
소멸자: 객체 소멸 시 자원 해제
소멸자(Destructor)는 객체가 소멸될 때 자동으로 호출되는 특수한 메서드로, 객체가 사용하던 자원을 해제하는 역할을 한다. 주로 동적으로 할당된 메모리를 해제하거나 파일을 닫는 등의 작업을 수행한다.
class Student {
private:
int* m_pID;
string m_name;
public:
Student();
~Student(); // 소멸자
};
Student::Student() {
m_pID = new int(0);
m_name = "Alice";
}
Student::~Student() {
delete m_pID; // m_pID가 가리키는 메모리 해제
}
위 예제에서 ~Student()
소멸자는 생성자에서 동적으로 할당된 m_pID
메모리를 해제한다.
this 포인터
this
포인터는 객체 자신을 가리키는 포인터이다. 멤버 변수와 매개변수의 이름이 같은 경우, this
포인터를 사용하여 멤버 변수임을 명확히 할 수 있다.
class Student {
private:
int m_id;
public:
void SetID(int m_id) {
this->m_id = m_id; // this->m_id는 클래스의 멤버 변수 m_id를 의미
}
};
마치며
이번 포스팅에서는 객체 지향 프로그래밍의 핵심 개념인 클래스에 대해 알아보았다. 클래스는 객체를 생성하기 위한 템플릿이며, 속성과 행동으로 구성된다. C++에서는 접근 제어 지시자를 사용하여 멤버 변수 및 메서드에 대한 접근을 제한할 수 있으며, 생성자를 통해 객체를 초기화하고 소멸자를 통해 자원을 해제한다. this
포인터는 객체 자신을 가리키는 포인터로, 멤버 변수와 매개변수의 이름이 같은 경우 유용하게 사용된다.
추천글 :
[C++][OOP] Array & Pointer
(https://hyeonb.blogspot.com/2024/09/coop-array-pointer.html)
[C++][OOP] Array & Pointer - dynamic allocation, reference
(https://hyeonb.blogspot.com/2024/10/coop-array-pointer-dynamic-allocation.html)