Company
교육 철학

LV01 클래스와 구조체 차이, OOP

C++ 클래스와 객체지향 프로그래밍 기초

클래스(Class)란?

클래스의 정의와 개념

C++에서 클래스(class)란 구조체의 상위 호환으로 이해할 수 있습니다. C++의 구조체는 멤버로 함수를 포함할 수 있기에, C언어의 구조체보다 좀 더 확장된 의미를 가집니다.
C++에서 이러한 구조체와 클래스의 차이는 기본 접근 제어의 차이일 뿐, 나머지는 거의 같습니다.
구조체 vs 클래스:
struct: 기본 접근 제어가 public
class: 기본 접근 제어가 private
C++에서는 이러한 클래스를 가지고 객체 지향 프로그램을 작성할 수 있습니다.

객체지향 프로그래밍(OOP, Object-Oriented Programming)

객체지향 프로그래밍의 핵심 개념:
모든 데이터를 **객체(object)**로 취급
객체가 바로 프로그래밍의 중심이 됨
객체(object)란 실생활에서 우리가 인식할 수 있는 사물로 이해 가능
객체의 구성 요소:
상태(state): 객체가 가지고 있는 데이터 (멤버 변수)
행동(behavior): 객체가 수행할 수 있는 동작 (멤버 함수)
이러한 객체의 상태(state)와 행동(behavior)을 구체화하는 형태의 프로그래밍이 바로 객체지향 프로그래밍입니다.
클래스와 객체의 관계:
클래스(class): 객체를 만들어 내기 위한 과 같은 개념
객체(object): 클래스를 바탕으로 실제로 생성된 인스턴스

구조체를 이용한 전통적인 방법

일반 함수를 이용한 구조체 처리

일반적으로 함수를 이용해서 인스턴스를 넘겨줘서 출력을 해줬을 것입니다.
#include <iostream> using namespace std; struct Cat { int age; int weight; char name[20]; }; void PrintCat(const Cat* cat) { cout << "Cat's name: " << (*cat).name << endl; cout << "Cat's age: " << (*cat).age << endl; cout << "Cat's weight: " << (*cat).weight << endl; } int main() { Cat yamyami; yamyami.age = 7; yamyami.weight = 4; // 문자열 복사 (배열에는 직접 대입 불가) const char name[20] = "yamyami"; for (int i = 0; i < 20; i++) { yamyami.name[i] = name[i]; } Cat geegee; geegee.age = 3; geegee.weight = 5; const char geegee_name[20] = "geegee"; for (int i = 0; i < 20; i++) { geegee.name[i] = geegee_name[i]; } PrintCat(&yamyami); PrintCat(&geegee); return 0; }
C++
복사

전통적인 방법의 단점

반복적인 작업의 불편함:
함수 호출 시마다 해당 클래스 인스턴스(객체)를 인자로 넣어줘야 하기 때문에 귀찮습니다
데이터와 그 데이터를 처리하는 함수가 분리되어 있어 관리가 어려움
실수로 잘못된 객체를 전달할 위험성

멤버 함수 (Member Function)

클래스 내부에 함수 포함

그래서 함수를 클래스 또는 구조체 안에 넣어버려서 멤버 변수처럼 멤버함수를 만들었습니다.

멤버함수의 동작 원리

C++ 멤버함수는 내부적으로 어떻게 동작할까요? 실제로는 C 언어와 비슷하게 동작합니다. 컴파일러가 자동으로 this 포인터를 첫 번째 매개변수로 전달합니다.
C 언어 스타일 (컴파일러가 실제로 하는 일):
// 컴파일러가 내부적으로 변환하는 방식 struct Cat { int age; int weight; char name[20]; }; // 멤버함수는 실제로 이렇게 변환됨 void Cat_PrintCat(Cat* this) { cout << "Cat's name: " << this->name << endl; cout << "Cat's age: " << this->age << endl; cout << "Cat's weight: " << this->weight << endl; } void Cat_SetAge(Cat* this, int age) { this->age = age; } // 함수 호출도 이렇게 변환됨 int main() { Cat yamyami; // yamyami.PrintCat(); → 실제로는 이렇게 변환 Cat_PrintCat(&yamyami); // yamyami.SetAge(7); → 실제로는 이렇게 변환 Cat_SetAge(&yamyami, 7); return 0; }
C++
복사
C++ 클래스 스타일 (우리가 작성하는 방식):
#include <iostream> using namespace std; class Cat { public: void PrintCat(/*Cat* this*/) { // 컴파일러가 자동으로 this->를 붙여줌 cout << "Cat's name: " << name << endl; // this->name cout << "Cat's age: " << age << endl; // this->age cout << "Cat's weight: " << weight << endl; // this->weight } void SetAge(int age) { // 매개변수와 멤버변수 이름이 같을 때는 this-> 필수 this->age = age; // this->age = 매개변수 age } int age; int weight; char name[20]; }; int main() { Cat yamyami; yamyami.age = 7; yamyami.weight = 4; const char name[20] = "yamyami"; for (int i = 0; i < 20; i++) { yamyami.name[i] = name[i]; } Cat geegee; geegee.age = 3; geegee.weight = 5; const char geegee_name[20] = "geegee"; for (int i = 0; i < 20; i++) { geegee.name[i] = geegee_name[i]; } // 멤버 함수 호출 - 훨씬 간편해짐! yamyami.PrintCat(); // 내부적으로 Cat_PrintCat(&yamyami) 호출 geegee.PrintCat(); // 내부적으로 Cat_PrintCat(&geegee) 호출 return 0; }
C++
복사

this 포인터의 이해

this 포인터란?

this 포인터는 현재 객체의 주소를 가리키는 특별한 포인터입니다. 모든 멤버함수는 암시적으로 this 포인터를 첫 번째 매개변수로 받습니다.
this 포인터의 특징:
자동 전달: 컴파일러가 자동으로 전달
현재 객체: 멤버함수를 호출한 객체의 주소
암시적 사용: 보통은 생략 가능
명시적 사용: 필요시 직접 사용 가능

this 포인터의 타입

this 포인터의 타입은 상황에 따라 결정됩니다:
class MyClass { public: void normalFunction() // this의 타입: MyClass* { // this를 통해 멤버 변수 수정 가능 } void constFunction() const // this의 타입: const MyClass* { // this를 통해 멤버 변수 수정 불가 // value = 10; // 오류! } private: int value; };
C++
복사

멤버 함수의 장점

코드 간소화:
PrintCat(&yamyami)yamyami.PrintCat()
객체 자신의 멤버 변수에 자동으로 접근 가능
별도의 매개변수 전달 불필요
객체지향적 설계:
데이터(멤버 변수)와 기능(멤버 함수)이 하나의 클래스로 묶임
관련된 기능들이 한 곳에 모여 유지보수성 향상

접근 제어와 캡슐화

Private 접근 제어의 필요성

하지만 대게 또 멤버변수는 접근제한 private 가려놓는게 좋습니다. 왜? 데이터 임의접근해서 값을 바꾸는 행위는 프로그램에 큰 문제를 야기시킬 수 있기 때문에 대게 감춰둡니다. 접근하지 못하도록 합니다.
그래서 우리는 값을 바꾸거나 가져오는 행위를 가능하게 하기 위해서 get, set 함수를 만들어서 사용합니다.

완전한 캡슐화 구현

#include <iostream> using namespace std; // 객체 지향 프로그래밍 완성형 class Cat { public: // 정보 출력 함수 void PrintCat() { cout << "Cat's name: " << mName << endl; cout << "Cat's age: " << mAge << endl; cout << "Cat's weight: " << mWeight << endl; } // Setter 함수들 (값 설정) void SetAge(int age) { if (age >= 0 && age <= 30) // 유효성 검사 { mAge = age; } else { cout << "Invalid age!" << endl; } } void SetWeight(int weight) { if (weight > 0 && weight <= 50) // 유효성 검사 { mWeight = weight; } else { cout << "Invalid weight!" << endl; } } void SetName(const char* name) { // 안전한 문자열 복사 for (int i = 0; i < 19; i++) // 19까지만 복사 (null 문자 공간 확보) { if (name[i] == '\0') break; // 문자열 끝에 도달 mName[i] = name[i]; } mName[19] = '\0'; // null 문자 보장 } // Getter 함수들 (값 조회) int GetAge() const { return mAge; } int GetWeight() const { return mWeight; } const char* GetName() const { return mName; } private: int mAge; int mWeight; char mName[20]; }; int main() { Cat yamyami; // 직접 접근 불가 (컴파일 오류) // yamyami.mAge = 7; // Error! // Setter 함수를 통한 안전한 접근 yamyami.SetAge(7); yamyami.SetWeight(4); yamyami.SetName("yamyami"); Cat geegee; geegee.SetAge(3); geegee.SetWeight(5); geegee.SetName("geegee"); // 정보 출력 yamyami.PrintCat(); geegee.PrintCat(); // Getter 함수 사용 예시 cout << "Yamyami's age: " << yamyami.GetAge() << endl; return 0; }
C++
복사

접근 제어 지시자

접근 제어의 종류

접근 제어
설명
접근 범위
public
어디서든 접근 가능
클래스 외부에서도 접근
private
같은 클래스 내부에서만 접근
클래스 내부 멤버 함수만
protected
상속받은 클래스에서 접근 가능
상속 관계에서 사용

멤버 변수 명명 규칙

일반적인 명명 규칙:
mAge, mWeight, mName: 'm' 접두사로 멤버 변수 표시
_age, _weight, _name: 언더스코어 접두사 사용
age_, weight_, name_: 언더스코어 접미사 사용
class Example { private: int mAge; // Microsoft 스타일 int _weight; // 일반적인 스타일 int height_; // Google 스타일 char name[20]; // 접두사 없이 사용 };
C++
복사

캡슐화의 장점

데이터 보호와 유효성 검사

캡슐화(Encapsulation)의 핵심 장점:
1.
데이터 보호: 외부에서 직접 수정 불가
2.
유효성 검사: Setter에서 올바른 값인지 확인
3.
일관성 유지: 데이터 변경 시 추가 작업 수행 가능
4.
인터페이스 안정성: 내부 구현 변경해도 외부 코드 영향 없음

실제 활용 예시

class BankAccount { public: void Deposit(int amount) { if (amount > 0) { mBalance += amount; cout << amount << "원이 입금되었습니다." << endl; } else { cout << "입금액은 0보다 커야 합니다." << endl; } } void Withdraw(int amount) { if (amount > 0 && amount <= mBalance) { mBalance -= amount; cout << amount << "원이 출금되었습니다." << endl; } else { cout << "출금할 수 없습니다. 잔액을 확인하세요." << endl; } } int GetBalance() const { return mBalance; } private: int mBalance = 0; // 직접 접근 금지! };
C++
복사
캡슐화가 없다면:
account.mBalance = -1000; (음수 잔액 가능!)
account.mBalance = 999999999; (불법적인 잔액 조작!)
캡슐화로 보호받는 경우:
입출금은 반드시 Deposit(), Withdraw() 함수를 통해서만
잔액 조회는 GetBalance()로만 가능
모든 거래에 유효성 검사 적용

객체지향 프로그래밍의 장점

코드 재사용성과 유지보수성

전통적인 절차지향 vs 객체지향:
// 절차지향 (불편함) PrintCat(&cat1); PrintCat(&cat2); SetCatAge(&cat1, 5); SetCatWeight(&cat1, 3); // 객체지향 (간편함) cat1.PrintCat(); cat2.PrintCat(); cat1.SetAge(5); cat1.SetWeight(3);
C++
복사
객체지향의 핵심 장점:
직관적: 실세계의 사물을 그대로 모델링
재사용성: 한 번 만든 클래스를 여러 번 사용
유지보수성: 관련 기능이 한 곳에 모여 있음
확장성: 상속을 통한 기능 확장 가능
안전성: 캡슐화를 통한 데이터 보호

“강의는 많은데, 내 실력은 왜 그대로일까?”

혼자서 공부하다 보면
이런 생각 들지 않으셨나요?
강의는 다 듣고도 직접 코드는 못 짜겠고,
복습할 땐 어디서부터 다시 시작해야 할지 막막하고,
질문하려 해도 물어볼 사람이 없고,
유튜브 영상도 정답만 보고 따라 치는 느낌
그렇다면 지금이 바로
“나만을 위한 코칭”이 필요한 순간입니다.

당신도 할 수 있습니다.

지금 멤버십을 넘어, 코칭에 도전해보세요.
수많은 수강생들이 얌얌코딩 코칭으로 넥슨, 크래프톤, NC 등 입사에 성공했습니다.
프리미엄 코칭 안내 바로가기
또는 카톡 오픈채팅: 얌얌코딩 상담방
지금도 코딩을 ‘따라 치기만’ 하고 계신가요?
이젠 혼자 설계하고, 스스로 코딩하는 법을 배워야 할 때입니다.
얌얌코딩이 옆에서 함께하겠습니다.