구조체의 등장배경은 무엇인가?
"연관 있는 데이터를 하나로 묶으면, 프로그램의 구현 및 관리가 용이하다"
"소프트웨어 = 데이터의 표현 + 데이터의 처리"
"표현해야 하는 데이터"는 항상 부류를 형성하기 마련.
그리고 이렇게 부류를 형성하는데이터들은 함께 생성, 이동 및 소멸된다는 특성
그래서 구조체는 연관 있는 데이터를 묶을 수 있는 문법적 장치로 데이터의 표현에 매우 큰 도움을 준다.
예를 들어, 레이싱게임의 '자동차'를 표현한다고 가정, 아래의 유사한 정보들이 모여서 게임상의 자동차가 표현
- 소유주
- 연료량
- 현재속도
- 취득점수
- 취득아이템
게임을 종료하면, 위 정보는 데이터베이스(또는 파일)에 함께 저장되어야 하며
게임을 시작하면, 저장된 위의 정보는 함께 복원되어야 한다.
이들 정보를 이용해서 구조체를 정의하면 프로그래밍이 한결 수월해진다.
C++에서의 구조체 변수의 선언
C언어에서의 구조체 변수 선언
struct Car basicCar;
struct Car simpleCar;
삽입된 키워드 struct는 이어서 선언되는 자료형이 구조체 를 기반으로 정의된 자료형임을 나타낸다.
struct를 생략하려면 typedef 선언을 추가해야 한다.
그러나 C++에서는 별도의 typedef 선언 없이도 다음과 같이 변수를 선언할 수 있다.
Car basicCar;
Car simpleCar;
구조체 기반 예제 작성
#include <iostream>
using namespace std;
/* 매크로 변수 */
#define ID_LEN 20
#define MAX_SPD 200
#define FUEL_STEP 2
#define ACC_STEP 10
#define BRK_STEP 10
/* 구조체 정의 */
struct Car
{
char ID[ID_LEN]; // ID
int fuel; // 연료량
int curspd; // 현재속도
};
void showstate(const Car& car) // 단순히 정보 출력 용도이기 때문에 값을 변경하지 못하는 const 참조자를 넣음
{
cout << "ID : " << car.ID << endl;
cout << "연료량 : " << car.fuel << endl;
cout << "현재속도 : " << car.curspd << endl << endl;;
}
/* 악셀 */
void accel(Car& car) // 참조자
{
if (car.fuel <= 0) // 연료량이 0이하면 반환
return; // 함수 종료
else
car.fuel -= FUEL_STEP; // 0보다 많으면 2를 깎는다
if (car.curspd + ACC_STEP >= MAX_SPD) // 현재속도 + 악셀 > 최대속도
{
car.curspd = MAX_SPD; // 악셀시 최대속도를 초과하지 않게
return;
}
car.curspd += ACC_STEP; // 위 조건들이 모두 맞지 않을 때, 현재 속도 = 현재 속도 + 악셀을 시행
}
void brake(Car& car) // 참조자
{
if (car.curspd < BRK_STEP) // 차의 현재속도가 BRK_STEP보다 작으면(즉, 속도가 매우 드리면)
{
car.curspd = 0; // 현재속도를 0으로 만들어버린다
return;
}
car.curspd -= BRK_STEP; // 아니면 BRK_STEP 만큼 속도를 낮춘다.
}
int main(void)
{
Car sedan = { "himan", 100, 0 };
accel(sedan);
accel(sedan);
showstate(sedan);
brake(sedan);
showstate(sedan);
Car truck = { "hiwoman", 100, 0 };
accel(truck);
brake(truck);
showstate(truck);
return 0;
}
위 세개의 함수들은
"구조체 Car와 함께 부류를 형성하여, Car와 관련된 데이터 처리를 담당하는 함수들"
따라서 위의 함수들은 구조체 Car에 종속적인 함수들이라고 말할 수 있다.
그럼에도 불구하고 전역함수의 형태를 띠기 때문에, 이 함수들이 구조체 Car에 종속적임을 나타내지 못하고 있는 상황이다.
따라서 엉뚱하게도 다른 영역에서 이 함수를 호출하는 실수도 범할 수 있는 상황
구조체 안에 함수 삽입하기
구조체 Car에 종속적인 함수들을 구조체 안에 함께 묶어버리면 어떰?
그렇게 되면 자동차와 관련된 데이터와 함수를 모두 묶는 셈이 되기 때문에 보다 확실한 구분이 가능하다.
#include <iostream>
using namespace std;
/* 매크로 상수 */
#define ID_LEN 20
#define MAX_SPD 200
#define FUEL_STEP 2
#define ACC_STEP 10
#define BRK_STEP 10
/* 구조체 정의 */
struct Car
{
char ID[ID_LEN]; // ID
int fuel; // 연료량
int curspd; // 현재속도
/* 구조체 안에 관련 함수 정의 */
void showstate() // 단순히 정보 출력 용도이기 때문에 값을 변경하지 못하는 const 참조자를 넣음
{
// 위에 선언된 변수들에 접근
cout << "ID : " << ID << endl;
cout << "연료량 : " << fuel << endl;
cout << "현재속도 : " << curspd << endl << endl;;
}
/* 악셀 */
void accel() // 참조자
{
if (fuel <= 0) // 연료량이 0이하면 반환
return; // 함수 종료
else
fuel -= FUEL_STEP; // 0보다 많으면 2를 깎는다
if (curspd + ACC_STEP >= MAX_SPD) // 현재속도 + 악셀 > 최대속도
{
curspd = MAX_SPD; // 악셀시 최대속도를 초과하지 않게
return;
}
curspd += ACC_STEP; // 위 조건들이 모두 맞지 않을 때, 현재 속도 = 현재 속도 + 악셀을 시행
}
void brake() // 참조자
{
if (curspd < BRK_STEP) // 차의 현재속도가 BRK_STEP보다 작으면(즉, 속도가 매우 드리면)
{
curspd = 0; // 현재속도를 0으로 만들어버린다
return;
}
curspd -= BRK_STEP; // 아니면 BRK_STEP 만큼 속도를 낮춘다.
}
};
int main(void)
{
Car sedan = { "himan", 100, 0 };
sedan.accel();
sedan.accel();
sedan.showstate();
sedan.brake();
sedan.showstate();
Car truck = { "hiwoman", 100, 0 };
truck.accel();
truck.brake();
truck.showstate();
return 0;
}
이전과 달리 함수의 매개변수로 구조체의 참조자를 정의하지 않는다.
연산의 대상(구조체)에 대한 정보가 불필요한 이유는, 함수가 구조체 내에 삽입되면서 구조체 내에 선언된 변수에 직접접근이 가능해졌기 때문
Car sedan = { "himan", 100, 0 };
Car truck = { "hiwoman", 100, 0 };
따라서 위와 같이 구조체 변수를 선언하면
위의 형태로 구조체 변수가 생성된다.
구조체 안에 enum 상수의 선언
다음은 매크로 상수의 선언
#define ID_LEN 20
#define MAX_SPD 200
#define FUEL_STEP 2
#define ACC_STEP 10
#define BRK_STEP 10
이들 상수 역시 구조체 Car에게만 의미가 있는 상수들
즉, 다른 영역에서 사용하도록 정의된 상수가 아니니, 이들 상수도 구조체 내에 포함시키는 것이 좋을 수 있다.
따라서 이러한 경우에는 열거형 enum을 사용해서 다음과 같이 구조체 내에서만 유효한 상수를 정의하면 된다.
struct Car
{
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
}
char ID[ID_LEN];
int fuel;
int curspd;
void showstate(){....}
void accel(){....}
void brake(){....}
만약 enum 선언을 구조체 내부에 삽입하는 것이 부담스러우면, namespace를 이용해서 상수가 사용되는 영역을 명시하는 것도 가능
몇몇 구조체들 사이에서만 사용하는 상수들을 선언할 때 특히 도움이 되며, 위에서 보인 방법보다 가독성도 좋아지는 경향이 있다.
#include <iostream>
using namespace std;
namespace CAR_CONST
{
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
};
}
/* 구조체 정의 */
struct Car
{
char ID[CAR_CONST::ID_LEN]; // ID
int fuel; // 연료량
int curspd; // 현재속도
/* 구조체 안에 관련 함수 정의 */
void showstate() // 단순히 정보 출력 용도이기 때문에 값을 변경하지 못하는 const 참조자를 넣음
{
// 위에 선언된 변수들에 접근
cout << "ID : " << ID << endl;
cout << "연료량 : " << fuel << endl;
cout << "현재속도 : " << curspd << endl << endl;;
}
/* 악셀 */
void accel() // 참조자
{
if (fuel <= 0) // 연료량이 0이하면 반환
return; // 함수 종료
else
fuel -= CAR_CONST::FUEL_STEP; // 0보다 많으면 2를 깎는다
if (curspd + CAR_CONST::ACC_STEP >= CAR_CONST::MAX_SPD) // 현재속도 + 악셀 > 최대속도
{
curspd = CAR_CONST::MAX_SPD; // 악셀시 최대속도를 초과하지 않게
return;
}
curspd += CAR_CONST::ACC_STEP; // 위 조건들이 모두 맞지 않을 때, 현재 속도 = 현재 속도 + 악셀을 시행
}
void brake() // 참조자
{
if (curspd < CAR_CONST::BRK_STEP) // 차의 현재속도가 BRK_STEP보다 작으면(즉, 속도가 매우 드리면)
{
curspd = 0; // 현재속도를 0으로 만들어버린다
return;
}
curspd -= CAR_CONST::BRK_STEP; // 아니면 BRK_STEP 만큼 속도를 낮춘다.
}
};
int main(void)
{
Car sedan = { "himan", 100, 0 };
sedan.accel();
sedan.accel();
sedan.showstate();
sedan.brake();
sedan.showstate();
Car truck = { "hiwoman", 100, 0 };
truck.accel();
truck.brake();
truck.showstate();
return 0;
}
함수는 외부로 뺄 수 있다.
함수가 포함되어 있는 C++의 구조체를 보는 순간, 다음의 정보들이 쉽게 눈에 들어와야 코드의 분석이 용이하다.
- 선언되어 있는 변수정보
- 정의되어 있는 함수정보
보통 프로그램을 분석할 때, 흐름 및 골격 위주로 분석하는 경우가 많다.
그리고 이러한 경우에는 함수의 기능만 파악을 하지, 함수의 세부구현까지 신경을 쓰지 않는다.
따라서 구조체를 보는 순간, 정의되어 있는 함수의 종류와 기능이 한눈에 들어오게끔 코드를 작성하는 것이 좋다.
따라서 구조체 내에 정의되어 있는 함수의 수가 많거나 그 길이가 길다면, 다음과 같이 구조체 밖으로 함수를 빼낼 필요가 있다.
struct Car
{
....
void showstate(); // 함수의 원형선언
void accel(); // 함수의 원형선언
....
}
void Car::showstate()
{
....
}
void Car::Accel()
{
....
}
함수의 원형선언을 구조체 안에 두고, 함수의 정의를 구조체 밖으로 빼내는 것.
다만, 빼낸 다음에 함수가 어디에 정의되어 있는지에 대한 정보만 추가해주면 됨.
#include <iostream>
using namespace std;
namespace CAR_CONST
{
enum
{
ID_LEN = 20,
MAX_SPD = 200,
FUEL_STEP = 2,
ACC_STEP = 10,
BRK_STEP = 10
};
}
/* 구조체 정의 */
struct Car
{
char ID[CAR_CONST::ID_LEN]; // ID
int fuel; // 연료량
int curspd; // 현재속도
/* 함수의 원형 선언 */
void showstate(); // 상태정보 출력
void accel(); // 엑셀, 속도증가
void brake(); // 브레이크, 속도감소
};
/* 구조체 안에 관련 함수 정의 */
void Car::showstate() // 단순히 정보 출력 용도이기 때문에 값을 변경하지 못하는 const 참조자를 넣음
{
// 위에 선언된 변수들에 접근
cout << "ID : " << ID << endl;
cout << "연료량 : " << fuel << endl;
cout << "현재속도 : " << curspd << endl << endl;;
}
/* 악셀 */
void Car::accel() // 참조자
{
if (fuel <= 0) // 연료량이 0이하면 반환
return; // 함수 종료
else
fuel -= CAR_CONST::FUEL_STEP; // 0보다 많으면 2를 깎는다
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) // 차의 현재속도가 BRK_STEP보다 작으면(즉, 속도가 매우 드리면)
{
curspd = 0; // 현재속도를 0으로 만들어버린다
return;
}
curspd -= CAR_CONST::BRK_STEP; // 아니면 BRK_STEP 만큼 속도를 낮춘다.
}
int main(void)
{
Car sedan = { "himan", 100, 0 };
sedan.accel();
sedan.accel();
sedan.showstate();
sedan.brake();
sedan.showstate();
Car truck = { "hiwoman", 100, 0 };
truck.accel();
truck.brake();
truck.showstate();
return 0;
}
사실 구조체 안에 함수가 정의되어 있으면, 다음의 의미가 더불어 내포된다.
"함수를 인라인으로 처리해라!"
반면, 위의 예제와 같이 함수를 구조체 밖으로 빼내면, 이러한 의미가 사라진다.
따라서 인라인의 의미를 그대로 유지하려면 다음과 같이 키워드 inline을 이용해서 인라인 처리를 명시적으로 지시해야 한다.
inline void Car::showstate(){....}
inline void Car::accel(){....}
inline void Car::brake(){....}
c++에서의 구조체는 클래스의 일종으로 간주. 그래서 구조체 안에 함수를 정의할 수 있었던 것.
참고 : [윤성우 열혈 C++ 프로그래밍] - 대부분의 내용 및 코드는 이 책에서 개인 공부 정리 목적으로 참고하였습니다.
'Language&Framework&Etc > C++' 카테고리의 다른 글
클래스의 기본(03-2) 클래스(Class)와 객체(Object)-2 (0) | 2020.12.24 |
---|---|
클래스의 기본(03-2) 클래스(Class)와 객체(Object)-1 (0) | 2020.12.24 |
C언어 기반의 C++ 2(02-6) C++에서 C언어의 표준함수 호출하기 (0) | 2020.12.23 |
C언어 기반의 C++ 2(02-5) malloc & free를 대신하는 new & delete (0) | 2020.12.23 |
C언어 기반의 C++ 2(02-4) 참조자(Reference)와 함수 (0) | 2020.12.22 |