[Chap 03] 클래스의 기본
💎 C++에서의 구조체
🔆 C++에서의 구조체 변수의 선언
#include <iostream>
#define ID_LEN 20
#define MAX_SPD 200
#define FUEL_STEP 2
#define ACC_STEP 10
#define BRK_STEP 10
struct Car
{
char gamerID[ID_LEN]; // 소유자 ID
int fuelGauge; // 연료량
int curSpeed; // 현재 속도
};
void ShowCarState(const Car &car)
{
std::cout << "소유자ID: " << car.gamerID << std::endl;
std::cout << "연료량: " << car.fuelGauge << "%" << std::endl;
std::cout << "현재 속도: " << car.curSpeed << "km/s" << std::endl
<< std::endl;
}
void Accel(Car &car)
{
if (car.fuelGauge <= 0)
{
return;
}
else
{
car.fuelGauge -= FUEL_STEP;
}
if (car.curSpeed + ACC_STEP >= MAX_SPD)
{
car.curSpeed = MAX_SPD;
return;
}
car.curSpeed += ACC_STEP;
}
void Break(Car &car)
{
if (car.curSpeed < BRK_STEP)
{
car.curSpeed = 0;
return;
}
car.curSpeed -= BRK_STEP;
}
int main()
{
Car run99 = {"run99", 100, 0};
Accel(run99);
Accel(run99);
ShowCarState(run99);
Break(run99);
ShowCarState(run99);
Car sped77 = {"sped77", 100, 0};
Accel(sped77);
Break(sped77);
ShowCarState(sped77);
return 0;
}
>>>
소유자ID: run99
연료량: 96%
현재 속도: 20km/s
소유자ID: run99
연료량: 96%
현재 속도: 10km/s
소유자ID: sped77
연료량: 98%
현재 속도: 0km/s
여기서 문제는 ShowCarState, Accel, Break에 해당하는 함수는 모두 Car 구조체에 해당하는 함수들이지만 전역 함수 성격을 띈다는 점이다. => 이 문제를 어떻게 해결할 수 있을까?
🔆 구조체 안에 함수 삽입하기
구조체 Car에 종속적인 함수들을 구조체 안에 함께 묶어버리면 되지 않을까?
이렇게 묶어버리면 구조체 안에 변수들을 함수 내에서 바로 사용할 수 있다. 즉, 어떤 변수에 대한 정보인지만 알게 된다면 변수에 직접접근이 가능하다.
#include <iostream>
#define ID_LEN 20
#define MAX_SPD 200
#define FUEL_STEP 2
#define ACC_STEP 10
#define BRK_STEP 10
struct Car
{
char gamerID[ID_LEN]; // 소유자 ID
int fuelGauge; // 연료량
int curSpeed; // 현재 속도
void ShowCarState()
{
std::cout << "소유자ID: " << gamerID << std::endl;
std::cout << "연료량: " << fuelGauge << "%" << std::endl;
std::cout << "현재 속도: " << curSpeed << "km/s" << std::endl
<< std::endl;
}
void Accel()
{
if (fuelGauge <= 0)
{
return;
}
else
{
fuelGauge -= FUEL_STEP;
}
if (curSpeed + ACC_STEP >= MAX_SPD)
{
curSpeed = MAX_SPD;
return;
}
curSpeed += ACC_STEP;
}
void Break()
{
if (curSpeed < BRK_STEP)
{
curSpeed = 0;
return;
}
curSpeed -= BRK_STEP;
}
};
int main()
{
Car run99 = {"run99", 100, 0};
run99.Accel();
run99.Accel();
run99.ShowCarState();
run99.Break();
run99.ShowCarState();
Car sped77 = {"sped77", 100, 0};
sped77.Accel();
sped77.Break();
sped77.ShowCarState();
return 0;
}
>>>
소유자ID: run99
연료량: 96%
현재 속도: 20km/s
소유자ID: run99
연료량: 96%
현재 속도: 10km/s
소유자ID: sped77
연료량: 98%
현재 속도: 0km/s
어떤 변수에 대한 정보인지만 알면 되므로 구조체 안에 함수들에는 따로 변수에 대한 정보를 주지 않아도 된다.
🔆 구조체 안에 enum 상수의 선언
위 예시에서 본 매크로 상수들 역시 Car 구조체에만 해당하는 상수들이므로 얘들도 구조체 내에 포함시키는 것이 좋을 수 있다. => 이러한 경우 열거형 enum을 이용해서 구조체 내에서만 유효한 상수를 정의할 수 있다.
이런 식으로 정의할 수 있다.
#include <iostream>
struct Car
{
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
};
char gamerID[ID_LEN]; // 소유자 ID
int fuelGauge; // 연료량
int curSpeed; // 현재 속도
void ShowCarState()
{
std::cout << "소유자ID: " << gamerID << std::endl;
std::cout << "연료량: " << fuelGauge << "%" << std::endl;
std::cout << "현재 속도: " << curSpeed << "km/s" << std::endl
<< std::endl;
}
void Accel()
{
if (fuelGauge <= 0)
{
return;
}
else
{
fuelGauge -= FUEL_STEP;
}
if (curSpeed + ACC_STEP >= MAX_SPD)
{
curSpeed = MAX_SPD;
return;
}
curSpeed += ACC_STEP;
}
void Break()
{
if (curSpeed < BRK_STEP)
{
curSpeed = 0;
return;
}
curSpeed -= BRK_STEP;
}
};
int main()
{
Car run99 = {"run99", 100, 0};
run99.Accel();
run99.Accel();
run99.ShowCarState();
run99.Break();
run99.ShowCarState();
Car sped77 = {"sped77", 100, 0};
sped77.Accel();
sped77.Break();
sped77.ShowCarState();
return 0;
}
하지만 이 방법이 부담스럽다면 이름 공간을 이용해서 상수가 사용되는 영역을 명시하는 것도 또 다른 방법이 될 수 있다.
#include <iostream>
namespace CAR_CONST
{
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
};
}
struct Car
{
char gamerID[CAR_CONST::ID_LEN]; // 소유자 ID
int fuelGauge; // 연료량
int curSpeed; // 현재 속도
void ShowCarState()
{
std::cout << "소유자ID: " << gamerID << std::endl;
std::cout << "연료량: " << fuelGauge << "%" << std::endl;
std::cout << "현재 속도: " << curSpeed << "km/s" << std::endl
<< std::endl;
}
void Accel()
{
if (fuelGauge <= 0)
{
return;
}
else
{
fuelGauge -= CAR_CONST::FUEL_STEP;
}
if (curSpeed + CAR_CONST::ACC_STEP >= CAR_CONST::MAX_SPD)
{
curSpeed = CAR_CONST::MAX_SPD;
return;
}
curSpeed += CAR_CONST::ACC_STEP;
}
void Break()
{
if (curSpeed < CAR_CONST::BRK_STEP)
{
curSpeed = 0;
return;
}
curSpeed -= CAR_CONST::BRK_STEP;
}
};
int main()
{
Car run99 = {"run99", 100, 0};
run99.Accel();
run99.Accel();
run99.ShowCarState();
run99.Break();
run99.ShowCarState();
Car sped77 = {"sped77", 100, 0};
sped77.Accel();
sped77.Break();
sped77.ShowCarState();
return 0;
}
>>>
소유자ID: run99
연료량: 96%
현재 속도: 20km/s
소유자ID: run99
연료량: 96%
현재 속도: 10km/s
소유자ID: sped77
연료량: 98%
현재 속도: 0km/s
CAR_CONST::ID_LEN
이런식으로 상수가 사용되고 있기 때문에 어느 영역에서 선언되고 사용되는 상수인지 쉽게 알 수 있다.
🔆 함수는 외부로 뺄 수 있다.
코드를 분석하는 경우는 코드의 흐름 및 골격 위주로 분석하는 경우가 많으므로 함수의 종류와 기능이 한눈에 들어오게끔 코드를 작성하는 것이 좋다. => HOW?
#include <iostream>
namespace CAR_CONST
{
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10,
};
}
// 🌟 이렇게 함수이름만 구조체에 넣을 수 있다.
struct Car
{
char gamerID[CAR_CONST::ID_LEN];
int fuelGauge;
int curSpeed;
void ShowCarState(); // 상태정보 출력
void Accel(); // 엑셀, 속도 증가
void Break(); // 브레이크, 속도 감속
};
// 🌟 그렇게 되는 경우 아래와 같이 정의를 해주어야 한다.
void Car::ShowCarState()
{
std::cout << "소유자ID: " << gamerID << std::endl;
std::cout << "연료량: " << fuelGauge << "%" << std::endl;
std::cout << "현재속도: " << curSpeed << "km/s" << std::endl
<< std::endl;
}
void Car::Accel()
{
if (fuelGauge <= 0)
{
return;
}
else
{
fuelGauge -= CAR_CONST::FUEL_STEP;
}
if ((curSpeed + CAR_CONST::ACC_STEP) >= CAR_CONST::MAX_SPD)
{
curSpeed = CAR_CONST::MAX_SPD;
return;
}
curSpeed += CAR_CONST::MAX_SPD;
}
void Car::Break()
{
if (curSpeed < CAR_CONST::BRK_STEP)
{
curSpeed = 0;
return;
}
curSpeed -= CAR_CONST::BRK_STEP;
}
int main()
{
Car run99 = {"run99", 100, 0};
run99.Accel();
run99.ShowCarState();
run99.Break();
run99.ShowCarState();
return 0;
}
>>>
소유자ID: run99
연료량: 98%
현재속도: 200km/s
소유자ID: run99
연료량: 98%
현재속도: 190km/s
💎 클래스와 객체
키워드 struct를 대신해서 class를 사용하면 구조체가 아닌 클래스가 된다.
#include <iostream>
#include <cstring>
namespace CAR_CONST
{
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
};
}
class Car
{
private:
char gamerID[CAR_CONST::ID_LEN];
int fuelGauge;
int curSpeed;
public:
void InitMembers(char *ID, int fuel);
void ShowCarState();
void Accel();
void Break();
};
void Car::InitMembers(char *ID, int fuel)
{
strcpy(gamerID, ID);
fuelGauge = fuel;
curSpeed = 0;
}
void Car::ShowCarState()
{
std::cout << "소유자ID: " << gamerID << std::endl;
std::cout << "연료량: " << fuelGauge << "%" << std::endl;
std::cout << "현재속도: " << curSpeed << "km/s" << std::endl
<< std::endl;
}
void Car::Accel()
{
if (fuelGauge <= 0)
{
return;
}
else
{
fuelGauge -= CAR_CONST::FUEL_STEP;
}
if ((curSpeed + CAR_CONST::ACC_STEP) >= CAR_CONST::MAX_SPD)
{
curSpeed = CAR_CONST::MAX_SPD;
return;
}
curSpeed += CAR_CONST::ACC_STEP;
}
void Car::Break()
{
if (curSpeed < CAR_CONST::BRK_STEP)
{
curSpeed = 0;
return;
}
curSpeed -= CAR_CONST::BRK_STEP;
}
int main()
{
Car run99;
run99.InitMembers("run99", 100);
run99.Accel();
run99.Accel();
run99.ShowCarState();
run99.Break();
run99.ShowCarState();
return 0;
}
>>>
소유자ID: run99
연료량: 96%
현재속도: 20km/s
소유자ID: run99
연료량: 96%
현재속도: 10km/s
코드를 보면 클래스 내에 private와 public이 있다. 자바처럼 클래스라는 개념은 private와 public으로 분리가 되고, private는 클래스 내에서 정의된 함수나 변수에서만 접근이 가능하다. 즉, main에서는 접근이 불가능하다. 하지만 public의 경우는 클래스 외의 main에서도 접근이 가능하다.
따라서 클래스 내에 선언된 private 변수들을 접근하기 위해서는 public의 InitMembers 함수를 통해서 할 수 있다.
함수의 경우는 따로 빼져 있지만 그럼에도 동일 클래스 내에서 정의되고 있기 때문에 private 변수에 접근이 가능한 것이다.
클래스를 선언한 것을 보면 구조체와 달리 클래스의 변수들에 한 번에 초기화를 하지 못한다. 왜냐하면 위에서도 말했듯이 변수들이 private로 되어있기 때문에 main에서 접근하지 못하여 InitMembers 함수를 통해서만 접근할 수 있기 때문이다. 그렇기 때문에 구조체 공부를 하면서는 보지 못했던 InitMembers 함수가 들어가게 된 것이다.
구조체의 경우는 별도의 접근 제어 지시자를 선언하지 않으면, 모든 변수와 함수는 public으로 선언된다.
하지만 클래스의 경우에는 별도의 접근 제어 지시자를 선언하지 않으면 모든 변수와 함수는 private으로 선언된다.
🔆 용어 정리: 객체, 멤버변수, 멤버함수
- 객체: 구조체와 클래스,,,자세한 내용은 이후 캡터에서 설명
- 멤버변수: 클래스를 구성하는 변수
- 멤버함수: 클래스를 구성하는 함수
🔆 C++에서의 파일 분할
📌 파일 분할 시 상식(?)
- 헤더파일의 역할
- 헤더파일의 중복포함을 막기 위해서 사용하는 메크로 #ifndef ~ #endif 가 있다.
- 링커의 역할
파일 분할
-
Car.h: 클래스의 선언을 담는다.
class Car { private: char gamerID[CAR_CONST::ID_LEN]; int fuelGauge; int curSpeed; public: void InitMembers(char *ID, int fuel); void ShowCarState(); void Accel(); void Break(); };
-
Car.cpp: 클래스의 정의를 담는다.
void Car::InitMembers(char *ID, int fuel) { strcpy(gamerID, ID); fuelGauge = fuel; curSpeed = 0; } void Car::ShowCarState() { std::cout << "소유자ID: " << gamerID << std::endl; std::cout << "연료량: " << fuelGauge << "%" << std::endl; std::cout << "현재속도: " << curSpeed << "km/s" << std::endl << std::endl; } void Car::Accel() { if (fuelGauge <= 0) { return; } else { fuelGauge -= CAR_CONST::FUEL_STEP; } if ((curSpeed + CAR_CONST::ACC_STEP) >= CAR_CONST::MAX_SPD) { curSpeed = CAR_CONST::MAX_SPD; return; } curSpeed += CAR_CONST::ACC_STEP; } void Car::Break() { if (curSpeed < CAR_CONST::BRK_STEP) { curSpeed = 0; return; } curSpeed -= CAR_CONST::BRK_STEP; }
Car.h
#ifndef __CAR_H__
#define __CAR_H__
namespace CAR_CONST
{
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
};
}
class Car
{
private:
char gamerID[CAR_CONST::ID_LEN];
int fuelGauge;
int curSpeed;
public:
void InitMembers(char *ID, int fuel);
void ShowCarState();
void Accel();
void Break();
};
#endif
Car.cpp
#include <iostream>
#include <cstring>
#include "Car.h"
void Car::InitMembers(char *ID, int fuel)
{
strcpy(gamerID, ID);
fuelGauge = fuel;
curSpeed = 0;
}
void Car::ShowCarState()
{
std::cout << "소유자ID: " << gamerID << std::endl;
std::cout << "연료량: " << fuelGauge << "%" << std::endl;
std::cout << "현재속도: " << curSpeed << "km/s" << std::endl
<< std::endl;
}
void Car::Accel()
{
if (fuelGauge <= 0)
{
return;
}
else
{
fuelGauge -= CAR_CONST::FUEL_STEP;
}
if ((curSpeed + CAR_CONST::ACC_STEP) >= CAR_CONST::MAX_SPD)
{
curSpeed = CAR_CONST::MAX_SPD;
return;
}
curSpeed += CAR_CONST::ACC_STEP;
}
void Car::Break()
{
if (curSpeed < CAR_CONST::BRK_STEP)
{
curSpeed = 0;
return;
}
curSpeed -= CAR_CONST::BRK_STEP;
}
RacingMain.cpp
#include "Car.h"
int main()
{
Car run99;
run99.InitMembers("run99", 100);
run99.Accel();
run99.Accel();
run99.ShowCarState();
run99.Break();
run99.ShowCarState();
return 0;
}
vscode에서 이를 실행시키기 위해서는 일단 해당 파일들을 하나의 폴더로 모은다
그런 다음 g++ Car.cpp RacingMain.cpp -o filename
명령어를 입력한다. .cpp은 파일 이름
그런 다음 ./filename
을 입력한다.
🔆 인라인 함수는 헤더파일에 함께 넣어야 해요.
C++은 인라인 함수(inline function)라는 내부에서 작성된 코드의 속도와 함수의 장점을 결합하는 방법을 제공한다. inline 키워드는 컴파일러에서 함수를 인라인 함수로 처리하도록 요청한다.
따라서 inline 함수의 경우는 클래스의 선언과 동일한 파일에 저장되어서 컴파일러가 동시에 참조할 수 있게 하영하 한다. (.h 파일에 넣어야 된다는 말!)
💎 객체 지향 프로그래밍의 이해
객체지향 프로그래밍은 현실에 존재하는 사물과 대상, 그리고 그에 따른 행동을 있는 그대로 실체화시키는 형태의 프로그래밍이다.
객체를 이루는 것은 데이터(상태 정보, 뭘 하고 있는지)와 기능(행동, 해당 객체가 무엇을 하고 있는지)입니다.
🔆 객체간의 대화 방법
아래 코드에서 SaleApples와 BuyApples와 같은 함수를 말한다. BuyApples를 구매자가 요구를 하면 판매자는 SaleApples를 통해서 구매자에게 사과를 준다.
이런 과정을 메시지 패싱이라고 한다.
#include <iostream>
// 판매자 클래스
class FruitSeller
{
private:
int APPLE_PRICE;
int numOfApples;
int myMoney;
public:
void InitMembers(int price, int num, int money)
{
APPLE_PRICE = price;
numOfApples = num;
myMoney = money;
}
int SaleApples(int money)
{
int num = money / APPLE_PRICE;
numOfApples -= num;
myMoney += money;
return num;
}
void ShowSalesResult()
{
std::cout << "남은 사과: " << numOfApples << std::endl;
std::cout << "판매 수익: " << myMoney << std::endl
<< std::endl;
}
};
// 구매자 클래스
class FruitBuyer
{
int myMoney;
int numOfApples;
public:
void InitMembers(int money)
{
myMoney = money;
numOfApples = 0;
}
void BuyApples(FruitSeller &seller, int money)
{
numOfApples += seller.SaleApples(money);
myMoney -= money;
}
void ShowBuyResult()
{
std::cout << "현재 잔액: " << myMoney << std::endl;
std::cout << "사과 개수: " << numOfApples << std::endl
<< std::endl;
}
};
int main()
{
FruitSeller seller;
seller.InitMembers(100, 20, 0);
FruitBuyer buyer;
buyer.InitMembers(50000);
buyer.BuyApples(seller, 2000);
std::cout << "과일 판매자의 현황" << std::endl;
seller.ShowSalesResult();
std::cout << "과일 구매자의 현황" << std::endl;
buyer.ShowBuyResult();
return 0;
}
>>>
과일 판매자의 현황
남은 사과: 0
판매 수익: 2000
과일 구매자의 현황
현재 잔액: 48000
사과 개수: 20
댓글남기기