✅ 주제

C++을 기반으로 객체지향 프로그래밍(OOP)의 본질과 기초 문법을 학습합니다.

  • 절차지향 프로그래밍의 한계를 이해하고 객체지향 프로그래밍으로의 전환 필요성을 파악
  • 클래스(class), 객체(instance), 멤버 변수 및 멤버 함수의 구조와 역할 학습
  • 생성자/소멸자, 복사 생성자, this 포인터의 정확한 의미와 활용
  • 실전 예제와 메모리 구조를 통해 객체 중심 설계의 핵심 사고방식을 체득

📚 개념

1. 절차지향 프로그래밍 (Procedural Programming)

  • 프로그램의 기본 구성 요소는 함수
  • 데이터를 별도로 선언하고, 함수에서 조작
  • 규모가 작을 땐 단순하지만, 프로젝트가 커질수록 구조적 유지보수가 어려움
struct Knight {
    int hp;
    int attack;
    int defence;
};

void HealMe(Knight& k, int value) {
    k.hp += value;
}

위처럼 구조체와 함수를 따로 작성. 재사용성과 확장성이 부족함.


2. 객체지향 프로그래밍 (Object-Oriented Programming)

  • 데이터와 관련된 동작을 객체 단위로 묶어서 설계
  • 클래스(class)는 객체를 만들기 위한 설계도
  • 객체(object)는 클래스의 인스턴스로, 현실 세계를 코드로 표현하는 중심 개념
class Knight {
public:
    void HealMe(int value) { _hp += value; }

    int _hp;
    int _attack;
    int _defence;
};

각 객체는 자신만의 상태와 행동을 가지고 있음


3. 클래스(class)와 객체(object)의 차이

구분클래스객체
개념설계도클래스 기반 실체
메모리존재하지 않음스택(또는 힙)에 할당됨
목적생성 규칙 정의실제 데이터 처리 및 동작

4. 멤버 변수와 멤버 함수

  • 멤버 변수: 객체가 가지는 데이터 (상태)
  • 멤버 함수: 객체가 수행할 수 있는 동작 (행동)

예시:

class Player {
public:
    void Attack();           // 행동
    void HealMe(int value);  // 행동

    int _hp;                 // 상태
    int _attack;
    int _defence;
};

객체가 만들어져야만 멤버 변수는 메모리에 실제로 할당됨


5. this 포인터

  • 모든 멤버 함수 내부에는 숨겨진 포인터 this가 전달됨
  • this는 현재 함수가 호출된 객체 자신의 주소를 의미
  • 같은 이름의 변수와 멤버 변수가 혼동될 때 this->를 사용하여 명확히 구분
Knight(int _hp, int _attack, int _defence) {
    this->_hp = _hp; // 오른쪽은 매개변수, 왼쪽은 멤버 변수
}

내부적으로는 Knight* this로 시작하며, this->_hp 식으로 작동


6. 생성자 (Constructor)

  • 객체가 생성될 때 자동 호출되는 함수
  • 이름은 클래스명과 같고 리턴 타입 없음
  • 멤버 변수를 초기화하는 용도로 사용
class Knight {
public:
    Knight() {
        _hp = 0;
        _attack = 0;
        _defence = 0;
    }
};

기타 생성자 (오버로딩)

Knight(int hp, int attack, int defence) {
    this->_hp = hp;
    this->_attack = attack;
    this->_defence = defence;
}

생성자 오버로딩이 존재하면, 컴파일러는 기본 생성자를 자동 생성하지 않음


7. 소멸자 (Destructor)

  • 객체가 소멸될 때 자동 호출되는 함수
  • 리턴 타입 없음, 이름 앞에 ~ 사용
~Knight() {
    cout << "~Knight()" << endl;
}

주로 메모리 해제, 로그 출력 등 정리 작업에 사용됨


8. 복사 생성자

  • 객체를 복사할 때 호출되는 생성자
  • 다른 객체의 값을 그대로 복사하여 새 객체를 초기화
Knight(const Knight& other) {
    this->_hp = other._hp;
    this->_attack = other._attack;
    this->_defence = other._defence;
}

매개변수는 const 참조로 받아야 원본 수정 방지 + 성능 향상


9. struct vs class

항목structclass
기본 접근제어publicprivate
사용 목적데이터 묶음객체지향 프로그래밍 설계
상속 여부가능가능
struct Knight1 {
    int hp;  // public
};

class Knight2 {
    int hp;  // private
};

📁 파일 분리 예시 (Header & Source)

Player.h

#pragma once
class Player {
public:
    void Attack();
    void Die();
    void HealMe(int value);

    int _hp;
    int _attack;
    int _defence;
};

Player.cpp

#include "Player.h"
#include <iostream>
using namespace std;

void Player::Attack() { cout << "Attack" << endl; }
void Player::Die() { cout << "Die" << endl; }
void Player::HealMe(int value) {
    _hp += value;
    cout << "Heal " << _hp << endl;
}

🔄 객체 메모리 구조

  • 멤버 변수: 객체가 생성될 때마다 스택 또는 힙에 개별적으로 생성됨
  • 멤버 함수: 코드 영역에 한 번만 생성되어 모든 객체가 공유
  • this 포인터: 객체를 호출한 인스턴스를 가리키는 포인터 (자동 전달)
Knight k1;
k1.Attack();   // this == &k1

✅ 주제

이번 파트의 중심은 객체 생성과 관련된 생성자 종류 및 규칙, 복사 생성자의 의미, 디버깅을 통한 메모리 확인, 그리고 객체 접근 제어와 오류 해결 전략입니다.

특히 실무에서 자주 마주치는 오류 메시지와 메모리 확인법, 그리고 클래스 설계 원칙을 통해 실전 감각까지 학습할 수 있도록 구성하였습니다.


📘 생성자의 규칙

1. 기본 생성자(Default Constructor)

class Knight {
public:
    Knight() {
        _hp = 0;
        _attack = 0;
        _defence = 0;
    }
};
  • 인자가 없는 생성자
  • 컴파일러가 자동 생성해주기도 하지만, 다른 생성자(오버로딩)가 있으면 자동 생성되지 않음

📌 이 점을 모르면 다음과 같은 오류 발생 가능:

Knight k2;  // 오류 C2512: 적절한 기본 생성자가 없습니다.

해결 방법: 직접 기본 생성자를 명시해줄 것.


2. 기타 생성자(Overloaded Constructor)

Knight(int hp, int attack, int defence) {
    this->_hp = hp;
    this->_attack = attack;
    this->_defence = defence;
}
  • 매개변수를 통해 객체를 원하는 값으로 초기화
  • 동일한 이름이 겹치므로 this->를 사용해 멤버 변수를 구분

객체 생성 방식들

Knight k1(100, 10, 1);         // 직접 초기화
Knight k2 = Knight(100, 10, 1); // 복사 초기화
Knight k3{100, 10, 1};         // 유니폼 초기화 (C++11 이상)

3. 복사 생성자(Copy Constructor)

Knight(const Knight& other) {
    this->_hp = other._hp;
    this->_attack = other._attack;
    this->_defence = other._defence;
}
  • 객체를 복사할 때 호출됨
  • 반드시 const 참조로 받아야 성능 및 안정성 확보
  • 예시:
Knight k1(100, 10, 1);
Knight k2(k1);  // 복사 생성자 호출

🧪 디버깅과 객체 상태 확인

1. 초기화하지 않은 객체의 상태

Knight k1;
// _hp, _attack, _defence 모두 초기화하지 않으면 쓰레기값

🔎 디버깅 창 예시:

_hp = -858993460
_attack = -858993460
_defence = -858993460

📌 이유: 스택에 할당된 지역 변수는 초기화되지 않으면 이전 메모리의 찌꺼기 값(쓰레기값) 을 갖게 됨


2. 생성자에서 초기화한 객체의 상태

Knight() {
    _hp = 0;
    _attack = 0;
    _defence = 0;
}

🔎 디버깅 창 예시:

Knight()
_hp = 0
_attack = 0
_defence = 0
~Knight()

→ 안전하게 초기화되었기 때문에 예측 가능한 값 출력됨


3. 디버깅으로 확인 가능한 this 포인터 구조

🔍 예시 이미지 분석:

  • 객체가 생성되면 메모리 주소(예: 300)에 _hp, _attack, _defence가 연속으로 배치됨
  • 멤버 함수가 실행될 때, this 포인터는 해당 주소를 참조함
this->hp   → *(300 + 0)
this->attack → *(300 + 4)
this->defence → *(300 + 8)

❌ 오류 분석

오류 메시지: C2512

Knight: 사용할 수 있는 적절한 기본 생성자가 없습니다.

🔎 원인:

  • 생성자 오버로딩만 있고 기본 생성자가 없을 경우
  • 예: 아래 코드에서 기본 생성자 호출 시 오류 발생
Knight k1(100, 10, 1);
Knight k2;  // 오류! 기본 생성자 없음

✔ 해결:

Knight() {
    _hp = 0; _attack = 0; _defence = 0;
}

🛡️ 접근 지정자 정리

지정자설명기본값
public어디서든 접근 가능struct
private클래스 내부에서만 접근 가능class
protected파생 클래스까지 접근 가능-
struct Knight1 {
    int hp;  // public
};

class Knight2 {
    int hp;  // private (기본값)
};

Knight1 k1;
k1.hp = 10;  // O

Knight2 k2;
k2.hp = 10;  // ❌ 오류

🧠 핵심

항목내용
절차지향 vs 객체지향객체지향은 상태와 행동을 묶어 코드의 응집도를 높임
클래스/객체클래스는 설계도, 객체는 실체. 클래스 없이는 객체 생성 불가
this 포인터현재 객체 자기 자신을 가리키는 주소. 멤버 함수 내부에서 자동 전달됨
생성자 종류기본 생성자, 오버로딩 생성자, 복사 생성자. 자동 생성 조건 주의
소멸자객체 종료 시 자동 호출. 메모리 정리 시 유용
오류 C2512기본 생성자 없을 때 발생. 명시적으로 만들어줘야 함
struct vs class접근제어 기본값(public vs private) 차이. class는 객체지향 용도로 사용 권장
파일 분리.h에 선언, .cpp에 정의. #pragma once로 중복 방지

profile
李家네_공부방

0개의 댓글