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 등 입사에 성공했습니다.
지금도 코딩을 ‘따라 치기만’ 하고 계신가요?
이젠 혼자 설계하고, 스스로 코딩하는 법을 배워야 할 때입니다.
얌얌코딩이 옆에서 함께하겠습니다. 