본문 바로가기
Language&Framework&Etc/C++

클래스의 기본(03-2) 클래스(Class)와 객체(Object)-2

by 머리올리자 2020. 12. 24.

용어정리 : 객체(Object), 멤버변수, 멤버함수

구조체 변수, 클래스 변수라는 표현은 어울리지 않는다.

 

왜냐하면 구조체와 클래스는 변수의 성격만 지니는 것이 아니기 떄문.

 

변수라는 표현을 대신해서 '객체(Object)' 라는 표현을 사용

 

"객체는 무엇을 의미?" → 나중에 설명

 

위에서 선언된 car는 변수가 아니고 '객체'이다.

 

그리코 클래스를 구성하는(클래스 내에 선언된) 변수를 가리켜 '멤버변수'라 하고, 클래스를 구성하는(클래스 내에 정의된) 함수를 가리켜 '멤버함수'

 

멤버변수

char ID[CAR_CONST::ID_LEN];
int fuel;
int curspd;

멤버함수

void init(const char* ID, int fuel);
void showstate();
void accel();
void brake();

C++에서의 파일분할

어떤 프로그램이건 하나의 파일에 모든 것을 담진 않는다.

 

파일을 나누는 기준(C언어 과정 中)

  • 헤더파일의 역할을 알고 있다.
  • C언어를 대상으로 헤더파일에 들어가야 할 내용을 구분할 수 있다.
  • 헤더파일의 중복포함을 막기 위해서 사용하는 매크로 #ifndef~#endif을 알고 있다.
  • 둘 이상의 파일을 컴파일해서 하나의 실행파일을 만드는 법을 알고 있다.
  • 링커(Linker)가 하는 일을 알고 있다.

C++에서의 파일 분할

  • Car.h    : 클래스의 선언을 담는다.
  • Car.cpp : 클래스의 정의(멤버함수의 정의)를 담는다.

클래스의 선언

class Car
{
private:
	char ID[CAR_CONST::ID_LEN];
	int fuel;
	int curspd;
public:
	void init(const char* ID, int fuel);
	void showstate();
	void accel();
	void brake();
};

 

이는 컴파일러가 Car 클래스와 관련된 문장의 오류를 잡아내는데 필요한 최소한의 정보로써, 클래스를 구성하는 외형적은 틀을 보여준다.

 

따라서 이를 가리켜 '클래스의 선언(declaration)'이라 한다.

 

즉, 위의 정보는 클래스 Car와 관련된 올고 그름을 판단하는데 사용된다.

 

예를 들면 아래의 코드를 컴파일하는데 있어서 위의 정보는 반드시 필요하다.

 

int main(void)
{
	Car car;
	car.fuel = 100; // fuel가 private임을 확인하고 에러를 발생
	car.accel(20);  // accel 함수의 매개변수가 void형임을 알고 에어를 발생시킴
}

반면, '클래스의 정의(definition)'에 해당하는 다음 함수의 정의는 다른 문장의 컴파일에 필요한 정보를 가지고 있지 않다.

 

따라서 함수의 정의는 컴파일 된 이후에, 링커에 의해 하나의 실행파일로 묶이기만 하면 된다.

 

void Car::init(char* input_ID, int input_fuel){....}
void Car::showstate(){....}
void Car::accel(){....}
void Car::brake(){....}

결론

 

'클래스의 선언' : 클래스와 관련된 문장의 컴파일 정보로 사용, 헤더파일에 저장을 해서, 필요한 위치에 포함될 수 있도록

'클래스의 정의' : 소스파일에 저장해서 컴파일이 되도록

 

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 ID[CAR_CONST::ID_LEN];
	int fuel;
	int curspd;
public: // 함수의 원형 선언
	void init(const char* input_ID, int input_fuel);
	void showstate();
	void accel();
	void brake();
};

#endif

car.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
#include "Car.h"
using namespace std;

void Car::init(const char* input_ID, int input_fuel)
{
	strcpy(ID, input_ID);
	fuel = input_fuel;
	curspd = 0;
}

void Car::showstate()
{
	cout << "ID : " << ID << endl;
	cout << "연료량 : " << fuel << endl;
	cout << "현재속도 : " << curspd << endl << endl;;
}

void Car::accel()
{
	if (fuel <= 0)
		return;
	else
		fuel -= CAR_CONST::FUEL_STEP;

	if ((curspd + CAR_CONST::ACC_STEP) >= CAR_CONST::MAX_SPD)
	{
		curspd = CAR_CONST::MAX_SPD;
		return;
	}
	curspd += CAR_CONST::ACC_STEP;
}

void Car::brake()
{
	if (curspd < CAR_CONST::BRK_STEP)
	{
		curspd = 0;
		return;
	}
	curspd -= CAR_CONST::BRK_STEP;
}

main.cpp

#include "CAR.h"

int main(void)
{
	Car car;
	car.init("himan", 100);
	car.accel();
	car.accel();
	car.accel();
	car.showstate();
	car.brake();
	car.showstate();
	return 0;
}

 

파일을 분할해 놓고 보니, 클래스 Car를 구성하는 멤버의 파악도 한결 수월.

 

그리고 뭔가 좀 정리되었다는 느낌.

 

익숙해지면, 하나의 파일에 모든 것을 집어넣는 것이 오히려 더 이상하게 느껴질 것.


인라인 함수는 헤더파일에 함께 넣어야 함

init, showstate, accel, brake를 다음과 같이 인라인화 한 다음에 그대로 소스 파일에 두면 컴파일 에러가 발생

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
#include "Car.h"
using namespace std;

void Car::init(const char* input_ID, int input_fuel)
{
	strcpy(ID, input_ID);
	fuel = input_fuel;
	curspd = 0;
}

inline void Car::showstate()
{
	cout << "ID : " << ID << endl;
	cout << "연료량 : " << fuel << endl;
	cout << "현재속도 : " << curspd << endl << endl;;
}

inline void Car::accel()
{
	if (fuel <= 0)
		return;
	else
		fuel -= CAR_CONST::FUEL_STEP;

	if ((curspd + CAR_CONST::ACC_STEP) >= CAR_CONST::MAX_SPD)
	{
		curspd = CAR_CONST::MAX_SPD;
		return;
	}
	curspd += CAR_CONST::ACC_STEP;
}

inline void Car::brake()
{
	if (curspd < CAR_CONST::BRK_STEP)
	{
		curspd = 0;
		return;
	}
	curspd -= CAR_CONST::BRK_STEP;
}

컴파일 에러 이유?

 

인라인 함수의 특징 중

 

"컴파일 과정에서 함수의 호출 문이 있는 곳에 함수의 몸체 부분이 삽입되어야 하므로"

 

main함수를 컴파일 한다고 가정

 

int main(void)
{
	Car car;
	car.init("himan", 100);
	car.accel();
	car.accel();
	car.accel();
	car.showstate();
	car.brake();
	car.showstate();
	return 0;
}

이 때 brake 함수가 인라인 함수가 아니라면, brake 함수가 Car 클래스의 멤버함수인지만 확인하고 컴파일은 완료

 

그러나 brake 함수가 인라인 함수이기 때문에, 함수의 호출문장은 컴파일러에 의해서 함수의 몸체로 대체되어야 함.

 

때문에 인라인 함수는 클래스의 선언과 동일한 파일에 저장되어서 컴파일러가 동시에 참조할 수 있게 해야 한다.

 

Car.h(인라인 함수 적용)

#ifndef __CAR_H__
#define __CAR_H__

#define _CRT_SECURE_NO_WARNINGS
#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 ID[CAR_CONST::ID_LEN];
	int fuel;
	int curspd;
public: // 함수의 원형 선언
	void init(const char* input_ID, int input_fuel);
	void showstate();
	void accel();
	void brake();
};

inline void Car::init(const char* input_ID, int input_fuel)
{
	strcpy(ID, input_ID);
	fuel = input_fuel;
	curspd = 0;
}

inline void Car::showstate()
{
	cout << "ID : " << ID << endl;
	cout << "연료량 : " << fuel << endl;
	cout << "현재속도 : " << curspd << endl << endl;;
}

inline void Car::accel()
{
	if (fuel <= 0)
		return;
	else
		fuel -= CAR_CONST::FUEL_STEP;

	if ((curspd + CAR_CONST::ACC_STEP) >= CAR_CONST::MAX_SPD)
	{
		curspd = CAR_CONST::MAX_SPD;
		return;
	}
	curspd += CAR_CONST::ACC_STEP;
}

inline void Car::brake()
{
	if (curspd < CAR_CONST::BRK_STEP)
	{
		curspd = 0;
		return;
	}
	curspd -= CAR_CONST::BRK_STEP;
}

#endif

 

main.cpp

#include "CAR.h"

int main(void)
{
	Car car;
	car.init("himan", 100);
	car.accel();
	car.accel();
	car.accel();
	car.showstate();
	car.brake();
	car.showstate();
	return 0;

}

컴파일러는 파일 단위로 컴파일을 한다. 즉, A.cpp와 B.cpp를 동시에 컴파일해서 하나의 실행파일을 만든다고 해도,

 

A.cpp 컴파일 과정에서 B.cpp를 참조하지 않으며, B.cpp의 컴파일 과정에서 A.cpp를 참조하지 않는다.

 

그래서 위 예제와 같이 클래스 선언과 인라인 함수의 정의를 함꼐 묶어둔 것.

 

참고 : [윤성우 열혈 C++ 프로그래밍] - 대부분의 내용 및 코드는 이 책에서 개인 공부 정리 목적으로 참고하였습니다.