포스트

구조체(Struct)

구조체(Struct)는 연관된 여러 데이터를 하나의 단위로 묶어 처리하기 위해 사용되는 구조화 된 데이터입니다.

만약, C에서 struct를 사용하지 않고 프로그래밍을 하라고 한다면 데이터 관리나 함수 호출 등에서 가독성이 좋지 않게 되고, 유지보수도 어려워 질 것입니다.

예를 들어, 학생 정보를 관리하기 위해 이름, 나이, 학점 등을 각각의 변수로 사용한다면 각각 char, int, float가 필요할텐데, 이들을 매개변수로 넘기거나 데이터를 묶어서 관리하려면 상당히 복잡해질 것입니다.

따라서, 구조체가 도입된 주요 이유는 복잡한 데이터 구조를 보다 쉽게 관리하고, 다양한 데이터 타입을 하나의 논리적인 단위로 묶어 처리할 수 있게 하기 위함이 아니었을까 생각합니다.

구조체 정의와 사용법

struct사용자 정의 자료형이라고 간주합니다.

그렇게 생각하는 이유는 C언어에서 구조체를 사용할 때, 마치 기본 자료형처럼 앞에 struct를 붙이고 사용자가 정의한 이름을 작성한 다음 변수명을 작성하기 때문입니다.

이는 마치 처음 struct를 설계할 때, 사용자가 만드는 기본 자료형이라는 느낌을 주고 싶었던게 아니었을까? 하고 생각하게 만듭니다.

실제로 struct를 전달할 때, 배열과 다르게 값으로 전달되고, 기본 자료형처럼 크기가 고정(컴파일 타임)되어 있고, 기본 자료형처럼 포인터로 동적 메모리 공간을 할당하거나 포인터로 선언할 수도 있습니다. 또한, 일급 객체인 기본 자료형처럼, 똑같이 함수의 매개변수로 사용되거나 리턴될 수 있기도 합니다.

구조체 정의

어쨌든, 구조체를 선언할 때, 구조체임을 알리는 struct를 붙이고 구조체명을 작성한 다음 선언 끝에 ;으로 선언 종료를 알립니다.

1
2
3
4
5
6
7
8
9
10
11
12
struct StructName	// 구조체 이름
{
	// 각 멤버 변수의 타입과 이름
	int Member1;
	float Member2;
	char Member3;
};	// ※ 구조체 선언 끝엔 ;을 붙인다.

int main()
{
	struct StructName StructInstance;	// 구조체 변수 선언
}

정의시 주의점

C언어에서의 구조체는 C++과 다르게 멤버 변수를 선언과 동시에 초기화 할 수 없습니다.

구조체 사용

구조체를 사용 할 때에는 구조체의 멤버 변수에 접근할 수 있도록 멤버 접근 연산자(.)를 사용합니다.

예를 들어, StructInstance의 각 멤버에 접근하려면 아래와 같이 접근하여 사용할 수 있습니다.

1
2
3
StructInstance.Member1 = 10;	// 구조체 변수의 멤버 변수에 접근
StructInstance.Member2 = 3.14f;
StructInstance.Member3 = 'A';

구조체 포인터

위에서 잠깐 언급한 바와 같이 구조체도 기본 자료형처럼 포인터로 만들 수 있습니다.

구조체 포인터를 선언하는 방법은 기본 자료형과 같지만, 구조체와 기본 자료형의 다른점이 하나 있습니다.

그건 바로 멤버 변수라는게 존재한다는 것입니다.

따라서, 멤버 변수로 접근하기 위해서는 위에서 언급한 멤버 접근 연산자(.)를 사용합니다.

1
2
3
4
5
6
7
8
9
int* pInt;							// int형 포인터 변수 선언
struct StructName* pStructInstance;	// 구조체 포인터 변수 선언

pInt = &a;							// int형 포인터 변수에 int형 변수의 주소를 대입
pStructInstance = &StructInstance;	// 구조체 포인터 변수에 구조체 변수의 주소를 대입

*pInt = 10;
(*pStructInstance).Member1 = 10;	// 구조체 포인터 변수로 구조체 변수의 멤버 변수에 접근
pStructInstance->Member1 = 10;		// 위와 같은 의미

대부분의 사용법이 기본 자료형과 같지만, 구조체 포인터의 멤버에 접근하기 위해서는 해당 포인터로 접근 한 다음 멤버 변수에 접근해야 합니다.

즉, 연산자 우선순위에 의해 다음과 같은 순서로 코드를 작성하게 됩니다.

  1. 먼저 포인터에 접근한다. (*pStructInstance)
  2. 멤버 변수에 접근하기 위해 멤버 접근 연산자를 사용해야 하지만, 연산자 우선순위가 높으므로 괄호를 사용한다.((*pStructInstance).Member1)

그러나, 늘 이런 연산자를 여러번 작성하다 보면 귀찮아지는데, 이를 줄인 것이 위 코드에 작성한 포인터에 대한 멤버 접근 연산자(->)가 되겠습니다.

C++에서의 구조체

C언어에서 C++로 넘어오면서 구조체는 여러 편의성안전성을 위해 추가/수정되었고, 특히 객체 지향의 기능을 포함하기 위해 여러 기능이 추가되기도 했습니다.

struct 생략 가능

기존에는 구조체 변수를 선언할 때, typedef를 사용해 구조체 이름을 간소화 할 수 있었습니다. 하지만, C++로 넘어오면서 이를 생략할 수 있게 되었습니다.

1
2
3
4
StructName StructInstance2;			// struct 키워드 생략 가능
StructInstance2.Member1 = 20;
StructInstance2.Member2 = 3.14f;
StructInstance2.Member3 = 'B';

생성자와 소멸자

C++의 구조체는 생성자와 소멸자를 호출할 수 있게 되어, 객체 초기화 및 정리를 더 안전하고 효율적으로 할 수 있게 되었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct CPPStruct
{
	CPPStruct()
	{	// 생성자에서 초기화 가능
		Member1 = 0;
		Member2 = NULL;
	}
	~CPPStruct()
	{	// 소멸자에서 정리 가능
		if (Member2 != NULL)
		{
			delete[] Member2;
		}
	}

	int Member1;
	char* Member2;
};

멤버 변수 초기화 가능

C++11부터는 구조체의 멤버 변수를 정의와 동시에 초기화할 수 있게 되어, 초기화되지 않은 멤버 변수로 인한 문제를 방지할 수 있게 되었습니다.

1
2
3
4
5
struct CPP11Struct
{
	int Member1 = 0;
	char* Member2 = nullptr;	// nullptr로 초기화
};

멤버 함수 추가

객체 지향의 설계에 맞게 구조체 내부에 멤버 함수를 선언할 수 있게 되었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct CPPStruct
{
	// 멤버 함수
	int GetMember1() const
	{
		return Member1;
	}
	void PrintMember2() const
	{
		printf("%s\n", Member2);
	}
	// 멤버 변수
	int Member1 = 0;
	char* Member2;
};

상속 기능

객체 지향의 설계에 맞게 상속을 할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
struct InheritanceStruct : public CPPStruct
{
	float Member3;
};
int main()
{
	InheritanceStruct InheritanceInstance;
	InheritanceInstance.Member1 = 10;
	InheritanceInstance.Member2 = nullptr;
	InheritanceInstance.Member3 = 3.14f;
}

참고

구조체 패딩 (structure padding)

이 기사는 저작권자의 CC BY-NC-ND 4.0 라이센스를 따릅니다.