
[03-1]에서 설명한 C++의 구조체는 클래스의 일종이다. 그렇다면 클래스와 구조체의 차이점은 뭘까?
클래스와 구조체의 유일한 차이점
키워드 struct를 대신해 class를 사용하면, 구조체가 아닌 클래스가 된다. 아래의 코드는 클래스의 정의이다.
class Car {
char gamerID[CAR_CONST::ID_LEN];
int fuelGauge;
int curSpeed;
void ShowCarState() { . . . . }
void Accel() { . . . . }
void Break() { . . . . }
키워드 struct가 아닌 class를 사용한 것이 코드상에서의 유일한 차이점이다. 그런데 이렇게 class로 키워드를 바꿔놓으면 [03-1]에서 보인 예제와 같이 변수(구조체 변수)를 선언하지 못한다.
Car run99 = {"run99", 100, 0}; // (X)
이유는 클래스 내에 선언된 함수에서가 아닌, 다른 영역에서 변수를 초기화하려 했기 때문이다.
클래스는 본적으로(별도의 선언을 하지 않으면) 클래스 내에 선언된 변수는 클래스 내에 선언된 함수에서만 접근 가능하다. 따라서 다음과 같은 형태로 클래스 변수를 선언해야 한다.
Car run99; // (O)
그럼 어떻게 초기화를 어떻게 하는가? 클래스 내에 선언된 변수는 클래스 내에 선언된 함수에서만 접근이 가능한데??
클래스는 정의를 하는 과정에서 각각의 변수 및 함수의 접근 허용범위를 별도로 선언해야 한다. 그럼 '접근제어 지시자'에 대해서 알아야 한다.
접근제어 지시자(접근제어 레이블)
C++의 접근제어 지시자는 다음과 같이 세 가지가 존재한다.
이 중에서 protected는 '상속'과 관련이 있으므로 다음에 살펴보고, public과 private에 대해 다음 예제를 통해 알아보자.
RacingCarClassBase.cpp
#include <iostream>
using namespace std;
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]; // 소유자 ID
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()
{
cout<<"소유자ID: "<<gamerID<<endl;
cout<<"연료량: "<<fuelGauge<<"%"<<endl;
cout<<"현재속도: "<<curSpeed<<"km/s"<<endl<<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(void) {
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
위의 예제를 분석해보자.
class Car
{
private:
char gamerID[CAR_CONST::ID_LEN]; // 소유자 ID
int fuelGauge; // 연료량
int curSpeed; // 현재속도
public:
void InitMembers(char * ID, int fuel);
void ShowCarState();
void Accel();
void Break();
};
여기서 private 아래에 있는 변수들은 class 내부에서만 접근이 가능해졌고,
public 아래에 있는 함수들은 어디서든지 접근이 가능해졌다. 이러한 접근제어 지시자를 지정하지 않으면 디폴트 값은 private이기에 모든 변수, 함수들이 class 내부에서만 접근이 가능해지는 것이다.
void Car::InitMembers(char * ID, int fuel)
{
strcpy(gamerID, ID);
fuelGauge=fuel;
curSpeed = 0;
}
[03-1]에서는 클래스 안에 선언된 변수의 초기화를 main함수 내에서도 할 수 있었으나 여기 예제에는 private선언으로 인해 class밖에서는 초기화를 할 수 없으므로 변수의 초기화를 목적으로 위의 InitMembers 함수를 class 내부에서 선언하고 class 외부에 정의했다.
main함수에서는 이 함수의 호출을 통해 클래스 내부 선언된 변수를 초기화할 수 있게 된 것이다.
용어정리: 객체(Object), 멤버변수, 멤버함수
앞서 계속 얘기해온 구조체 변수, 클래스 변수라는 표현은 이제 어울리지 않는다. 왜냐하면 구조체와 클래스는 변수의 성격만 지니는 것이 아니기 때문이다. 그래서 변수라는 표현을 대신해 '객체(Object)'라는 표현을 사용한다.
여기서 RacingCarClassBase.cpp의 run99sms '변수'가 아닌 '객체'라고 표현을 바꿔야 한다.
클래스를 구성하는(클래스 내에 선언된) 변수를 '멤버변수'라 한다.
char gamerID[CAR_CONST::ID_LEN];
int fuelGauge;
int curSpeed;
클래스를 구성하는(클래스 내에 정의된) 함수를 '멤버함수'라 한다.
void InitMembers(char * ID, int fuel);
void ShowCarState();
void Accel();
void Break();
C++에서의 파일분할
C++에서의 파일분할에 대해 이야기해 보자. 클래스 Car을 대상으로 파일을 나눌 떄에는 보통 다음과 같이 파일을 구분한다.
여기서 말하는 클래스의 선언은 다음과 같다.
class Car
{
private:
char gamerID[CAR_CONST::ID_LEN]; // 소유자 ID
int fuelGauge; // 연료량
int curSpeed; // 현재속도
public:
void InitMembers(char * ID, int fuel);
void ShowCarState();
void Accel();
void Break();
};
Car 클래스와 관련된 문장의 오류를 잡아내는데 필요한 최소한의 정보로써, 클래스의 외형적인 틀을 보여준다. 이를 '클래스의 선언(declaration)'이라 한다.
'클래스의 정의(definition)'에 해당하는 다음 함수의 정의는 다른 문장의 컴파일에 필요한 정보를 가지고 있지 않다. 따라서 함수의 정의는 컴파일 된 이후에, 링커에 의해 하나의 실행파일로 묶이기만 하면 된다.
void Car::InitMembers(char *ID, int fuel) { . . . . }
void Car::ShowCarState() { . . . . }
void Car::Accel() { . . . . }
void Car::Break() { . . . . }
결론은, '클래스의 선언'은 헤더파일에, '클래스의 정의'는 소스파일에 저장한다.
앞서 보인 예제 RacingCarClassBase.cpp를 총 3개의 파일로 적절히 나눠보자.
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]; // 소유자 ID
int fuelGauge; // 연료량
int curSpeed; // 현재속도
public:
void InitMembers(char * ID, int fuel);
void ShowCarState();
void Accel();
void Break();
};
#endif __CAR_H__
#ifndef CAR_H, #define CAR_H, #endif CAR_H은 헤더파일의 중복포함 문제를 해결하기 위한 매크로 선언이다.
Car.cpp
#include <iostream>
#include <cstring>
#include "Car.h"
using namespace std;
void Car::InitMembers(char * ID, int fuel)
{
strcpy(gamerID, ID);
fuelGauge=fuel;
curSpeed = 0;
}
void Car::ShowCarState()
{
cout<<"소유자ID: "<<gamerID<<endl;
cout<<"연료량: "<<fuelGauge<<"%"<<endl;
cout<<"현재속도: "<<curSpeed<<"km/s"<<endl<<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(void) {
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
인라인 함수는 헤더파일에 함께 넣어야 해요.
인라인 함수는 컴파일을 할 때, 함수의 호출부분을 몸체로 바꿔버린다. 따라서 선언과 동시에 정의가 필요하다. 이는 따로 분리를 하면 안된다는 말이다.
결론은 인라인 함수 선언과 정의를 한꺼번에 헤더파일(.h) 안에 통째로 넣자!
앞서 보인 예제의 Car함수와 Break함수를 인라인 함수로 바꿔서 코드를 구성하면 아래의 예제가 된다.
CarInline.h
#ifndef __CAR_H__
#define __CAR_H__
#include <iostream>
using namespace std;
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]; // 소유자 ID
int fuelGauge; // 연료량
int curSpeed; // 현재속도
public:
void InitMembers(char * ID, int fuel);
void ShowCarState();
void Accel();
void Break();
};
inline void Car::ShowCarState()
{
cout<<"소유자ID: "<<gamerID<<endl;
cout<<"연료량: "<<fuelGauge<<"%"<<endl;
cout<<"현재속도: "<<curSpeed<<"km/s"<<endl<<endl;
}
inline void Car::Break()
{
if(curSpeed<CAR_CONST::BRK_STEP)
{
curSpeed=0;
return;
}
curSpeed-=CAR_CONST::BRK_STEP;
}
#endif __CAR_H__
CarInline.cpp
#include <cstring>
#include "CarInline.h"
using namespace std;
void Car::InitMembers(char * ID, int fuel)
{
strcpy(gamerID, ID);
fuelGauge=fuel;
curSpeed = 0;
}
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;
}
RacingInlineMain.cpp
#include "CarInline.h"
int main(void) {
Car run99;
run99.InitMembers("run99", 100);
run99.Accel();
run99.Accel();
run99.ShowCarState();
run99.Break();
run99.ShowCarState();
return 0;
}