티스토리 뷰
주의 사항!
- 이 글은 제가 직접 공부하는 중에 작성되고 있습니다.
- 따라서 제가 이해하는 그대로의 내용이 포함됩니다.
- 따라서 이 글은 사실과는 다른 내용이 포함될 수 있습니다.
C언어로 프로그램을 구현하면 구조체가 항상 따르기 마련입니다. 구조체를 이렇게 자주 활용하는 이유는 서로 연관 있는 데이터를 묶어서 관리하기 용이하게 만들기 때문입니다.
C++에도 C언어의 구조체와 같은 개념이 있습니다. C++에서는 이를 '클래스'라고 부릅니다.
C언어에서는 구조체의 형식을 선언하고, 이후 구조체 변수를 선언할 때 다음과 같이 struct 키워드를 사용해서 선언했습니다.
//구조체의 형식 선언
struct Player
{
int level;
char name[20];
int HP;
int MP;
};
int main(void)
{
struct Player player; //구조체 변수 선언
return 0;
}
만약 구조체 변수를 선언할 때 struct 키워드를 사용하지 않으려면, 다음과 같이 typedef를 사용하여 구조체 형식을 선언하는 방법도 사용할 수 있었습니다.
//구조체의 형식 선언
typedef struct
{
int level;
char name[20];
int HP;
int MP;
} Player;
int main(void)
{
Player player; //구조체 변수 선언
return 0;
}
그런데 C++에서는 typedef를 사용하지 않아도 struct를 사용하지 않는 구조체 변수 선언이 가능합니다. 아래는 그 예시입니다.
//구조체의 형식 선언
struct Player
{
int level;
char name[20];
int HP;
int MP;
};
int main(void)
{
Player player; //구조체 변수 선언
return 0;
}
다음은 자동차 정보를 멤버로 갖는 구조체를 선언하고 이를 사용하는 예제입니다.
#include <iostream>
#define ID_LEN 20
#define MAX_SPD 200
#define FUEL_STEP 2
#define ACC_STEP 10
#define BRK_STEP 10
using std::cout;
using std::cin;
using std::endl;
struct Car
{
int fuelGauge; //연료량
int curSpeed; //현재속도
char gamerID[ID_LEN]; //소유자 ID
};
void showCarState(const Car &car)
{
cout << "소유자ID : " << car.gamerID << endl;
cout << "연료량 : " << car.fuelGauge << endl;
cout << "현재속도 : " << car.curSpeed << endl << 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(void)
{
Car run99 = { 100, 0, "run99" };
Accel(run99);
Accel(run99);
showCarState(run99);
Break(run99);
showCarState(run99);
Car sped77 = {100, 0, "sped77"};
Accel(sped77);
Break(sped77);
showCarState(sped77);
return 0;
}
/*
실행결과
소유자ID : run99
연료량 : 96
현재속도 : 20
소유자ID : run99
연료량 : 96
현재속도 : 10
소유자ID : sped77
연료량 : 98
현재속도 : 0
*/
위의 예제에서 Accel 함수와 Break함수, showCarState 함수는 모두 구조체 Car와 연관되어 Car와 관련된 데이터를 처리하는 함수입니다. 즉, 이들 함수는 구조체 Car와 관련이 없는 데이터는 처리하지 않습니다. 따라서 이 함수를 구조체 Car에 종속적이라고 할 수 있습니다. 그런데 실제로는 구조체 Car에 종속되어 있지 않고 전역 함수로 선언이 되었기 때문에
쓰기에 따라서는 구조체 Car와는 관련이 없는 전혀 다른 데이터를 처리하게 될 수도 있습니다.
어차피 구조체 Car와 관련된 데이터만 처리하는, Car에 종속적인 함수라면 그냥 구조체 Car 안에 선언되어 있으면 관리하기에 더 용이할 것 같습니다. C++에서는 구조체 안에 함수를 삽입하는 것을 허용합니다. 구조체 안에 함수를 정의하는 것으로 예제를 수정해 보겠습니다.
#include <iostream>
#define ID_LEN 20
#define MAX_SPD 200
#define FUEL_STEP 2
#define ACC_STEP 10
#define BRK_STEP 10
using std::cout;
using std::cin;
using std::endl;
struct Car
{
int fuelGauge; //연료량
int curSpeed; //현재속도
char gamerID[ID_LEN]; //소유자 ID
void showCarState()
{
cout << "소유자ID : " << gamerID << endl;
cout << "연료량 : " << fuelGauge << endl;
cout << "현재속도 : " << curSpeed << endl << 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(void)
{
Car run99 = { 100, 0, "run99" };
run99.Accel();
run99.Accel();
run99.showCarState();
run99.Break();
run99.showCarState();
Car sped77 = {100, 0, "sped77"};
sped77.Accel();
sped77.Break();
sped77.showCarState();
return 0;
}
/*
실행결과
소유자ID : run99
연료량 : 96
현재속도 : 20
소유자ID : run99
연료량 : 96
현재속도 : 10
소유자ID : sped77
연료량 : 98
현재속도 : 0
*/
앞선 예제와 차이점을 비교해 보겠습니다. 먼저 함수들이 구조체 안에 정의되면서 매개변수가 사라졌습니다. 또. main 함수에서 Accel, Break, showCarState 함수들을 호출할 때는 구조체 변수에 멤버 접근 연산자를 사용함으로써 함수를 호출하는 모습입니다. 구조체 안의 변수를 사용할 때와 사용법은 다르지 않습니다.
위의 예제를 다시 보면 매크로 상수들이 정의되어 있습니다. 그런데 이 매크로 상수들 또한 구조체 Car와 관련이 있고, 구조체 Car의 데이터를 처리할 때만 사용됩니다. 따라서 매크로 상수들을 매크로 상수로 정의하지 않고,구조체 안에 열거형으로 선언하여 사용하는 것이 더 용이해 보입니다.
만약 열거형을 구조체 안에 선언하는 것이 부담스럽다면 대신 네임스페이스를 이용할 수도 있습니다. 네임스페이스를 이용하면 몇몇 구조체들에서만 사용하는 상수들을 선언할 때 특히 유용합니다. 다음 예제를 살펴보겠습니다.
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
namespace CAR_CONST
{
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
};
}
struct Car
{
int fuelGauge; //연료량
int curSpeed; //현재속도
char gamerID[CAR_CONST::ID_LEN]; //소유자 ID
void showCarState()
{
cout << "소유자ID : " << gamerID << endl;
cout << "연료량 : " << fuelGauge << endl;
cout << "현재속도 : " << curSpeed << endl << 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(void)
{
Car run99 = { 100, 0, "run99" };
run99.Accel();
run99.Accel();
run99.showCarState();
run99.Break();
run99.showCarState();
Car sped77 = {100, 0, "sped77"};
sped77.Accel();
sped77.Break();
sped77.showCarState();
return 0;
}
/*
실행결과
소유자ID : run99
연료량 : 96
현재속도 : 20
소유자ID : run99
연료량 : 96
현재속도 : 10
소유자ID : sped77
연료량 : 98
현재속도 : 0
*/
네임스페이스 CAR_COUST를 만들고, 안에 열거형을 선언했습니다. 해당 네임스페이스 안의 상수를 이용할 때는 네임스페이스에 ::연산자를 사용하여 접근합니다.
구조체 멤버 함수의 선언과 정의 분리
보통 프로그램을 분석할 때, 흐름 및 골격 위주로 분석하는 경우가 많습니다. 그리고 이러한 경우에는 함수의 기능만 파악을 하지, 함수 내부의 세부 구현까지 신경 쓰지는 않습니다. 따라서 구조체에 함수가 정의되어 있다면, 구조체를 보는 순간 정의되어 있는 함수의 종류와 기능을 한눈에 파악할 수 있도록 코드를 작성하는 것이 좋습니다. 따라서 구조체 내에 정의된 함수의 수가 많거나 그 길이가 길다면, 다음과 같이 구조체 밖으로 함수를 빼낼 필요가 있습니다.
struct Car
{
...;
void ShowCarState();
void Accel();
...;
};
void Car::ShowCarState()
{
...;
}
void Car::Accel()
{
...;
}
즉, 함수의 형식 선언을 구조체 내에서 하고, 함수의 정의를 구조체 밖으로 빼냅니다. 그리고 해당 함수가 어디에 선언되어 있는 함수인지 알려주기위해 구조체 이름에 ::연산자를 사용하여 나타냅니다. 이런 형태로 예제를 수정해보겠습니다.
//functions.h 헤더파일로 저장
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
namespace CAR_CONST
{
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
};
}
struct Car
{
int fuelGauge; //연료량
int curSpeed; //현재속도
char gamerID[CAR_CONST::ID_LEN]; //소유자 ID
void showCarState();
void Accel();
void Break();
};
#endif
//functions.cpp 소스파일로 저장
#include <iostream>
#include "functions.h"
using std::cout;
using std::cin;
using std::endl;
void Car::showCarState()
{
cout << "소유자ID : " << gamerID << endl;
cout << "연료량 : " << fuelGauge << endl;
cout << "현재속도 : " << curSpeed << 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;
}
//main.cpp 소스파일로 저장
#include "functions.h"
int main(void)
{
Car run99 = { 100, 0, "run99" };
run99.Accel();
run99.Accel();
run99.showCarState();
run99.Break();
run99.showCarState();
Car sped77 = {100, 0, "sped77"};
sped77.Accel();
sped77.Break();
sped77.showCarState();
return 0;
}
/*
실행결과
소유자ID : run99
연료량 : 96
현재속도 : 20
소유자ID : run99
연료량 : 96
현재속도 : 10
소유자ID : sped77
연료량 : 98
현재속도 : 0
*/
함수를 정의하는 부분을 구조체 밖으로 뺐고, 동시에 분할 컴파일을 위해 하나의 헤더 파일과 두 개의 소스파일로 나누어봤습니다.
C++에서의 구조체는 잠시 후에 설명할 '클래스'의 일종으로 간주됩니다. 그래서 구조체 안에 함수를 정의할 수 있었습니다.
'공부 일지 > CPP 공부 일지' 카테고리의 다른 글
C++ | 객체지향 프로그래밍의 이해 (0) | 2021.08.01 |
---|---|
C++ | 클래스(Class)와 객체(Object) (0) | 2021.08.01 |
C++ | C++에서 C언어의 표준함수 호출하기 (0) | 2021.08.01 |
C++ | new & delete (0) | 2021.08.01 |
C++ | 참조자(Reference) (0) | 2021.08.01 |