구조체란 사용자가 새롭게 정의하는 사용자 정의 자료형이다. 구조체를 이용해 기본 자료형만으로는 표현할 수 없는 복잡한 데이터들을 표현할 수 있다.
예를들어 학생을 나타내는 데이터로는 이름, 학년, 평균 학점 등을 생각할 수 있을 것이다.
char Name[10]; // 이름(문자열)
int Grade; // 학년(정수형)
double GPA; // 평균학점(실수형)
그러나 이들의 자료형은 모두 달라 배열로는 묶을 수 없으며 학생 한 명 한 명마다 개별적인 변수들로 정의하여 사용하기에는 매우 번거롭다.
이러한 경우 구조체를 사용할 수 있다.
struct Student
{
char Name[10]; // 이름(문자열)
int Grade; // 학년(정수형)
double GPA; // 평균학점(실수형)
};

배열이 여러 개의 같은 자료형을 하나로 묶는 것이라면 구조체는 서로 다른 자료형을 하나로 묶는 것이다. 이때 구조체를 구성하는 변수들을 구조체의 멤버 또는 멤버 변수라고 한다.
C++의 구조체는 함수까지도 멤버로 가질 수 있으며 객체 지향 프로그래밍의 핵심이 되는 클래스의 기초가 된다.
구조체는 struct 라는 키워드를 사용하여 다음과 같은 형식으로 정의된다.
struct StructName
{
MemberValType MemberValName;
...
};
struct 는 구조체 선언 시 사용하는 키워드이다.StuctName 은 구조체와 구조체를 구별하기 위해 붙여지는 이름이다.MemberValType 은 멤버 변수의 자료형을 나타낸다. int, float, double …MemberValName 은 멤버 변수에 접근하기 위해 사용된다; 을 붙여주어야 한다.struct Student
{
char Name[10]; // 이름(문자열)
int Grade; // 학년(정수형)
double GPA; // 평균학점(실수형)
};
구조체 정의는 구조체 안에 어떤 변수들이 들어간다는 것을 말해주어 새로운 자료형을 생성한 것이기 때문에 변수 선언과 다르다.
즉, 구조체의 정의는 구조체의 형태만 정의한 것이다. 이 형태를 가지고 구조체 변수를 생성할 수 있다. 구조체 변수 생성에는 구조체의 이름을 이용한다.
struct StructName StructValName;
StructName StructValName;
struct Student Student1; // Student 구조체 변수 Student1 생성
Student Student2; // Student 구조체 변수 Student2 생성
{} 를 통해 구조체 변수를 초기화 할 수 있다.
StructValName = { MemberVal1, MemberVal2, ... };
구조체 정의에서 멤버 변수가 정의된 순서에 따라 차례대로 초기값이 설정되며, 초기값이 설정되지 않은 나머지 멤버 변수들을 기본값으로 초기화된다.
#include <iostream>
// 구조체 선언
struct Student
{
char Name[10]; // 이름(문자열)
int Grade; // 학년(정수형)
double GPA; // 평균학점(실수형)
};
int main()
{
// 구조체 변수 생성
struct Student Student1;
Student Student2;
// 구조체 변수 초기화
Student1 = { "철수", 2, 3.5 };
Student2 = { "영희", 3 };
std::cout << "Student1" << std::endl;
std::cout << "이름 : " << Student1.Name << std::endl;
std::cout << "학년 : " << Student1.Grade << std::endl;
std::cout << "학점 : " << Student1.GPA << std::endl;
std::cout << std::endl;
std::cout << "Student2" << std::endl;
std::cout << "이름 : " << Student2.Name << std::endl;
std::cout << "학년 : " << Student2.Grade << std::endl;
std::cout << "학점 : " << Student2.GPA << std::endl;
return 0;
}
Student1
이름 : 철수
학년 : 2
학점 : 3.5
Student2
이름 : 영희
학년 : 3
학점 : 0
구조체에서는 멤버 연산자 . 를 이용해 구조체의 멤버에 접근할 수 있다.
#include <iostream>
// 구조체 선언
struct Student
{
char Name[10]; // 이름(문자열)
int Grade; // 학년(정수형)
double GPA; // 평균학점(실수형)
};
int main()
{
// 구조체 변수 생성
struct Student Student1;
Student Student2;
// 구조체 변수 초기화
Student1 = { "철수", 2, 3.5 };
Student2 = { "영희", 3 };
// 구조체 멤버 접근
Student2.GPA = 4.5;
std::cout << "Student1" << std::endl;
std::cout << "이름 : " << Student1.Name << std::endl;
std::cout << "학년 : " << Student1.Grade << std::endl;
std::cout << "학점 : " << Student1.GPA << std::endl;
std::cout << std::endl;
std::cout << "Student2" << std::endl;
std::cout << "이름 : " << Student2.Name << std::endl;
std::cout << "학년 : " << Student2.Grade << std::endl;
std::cout << "학점 : " << Student2.GPA << std::endl;
return 0;
}
Student1
이름 : 철수
학년 : 2
학점 : 3.5
Student2
이름 : 영희
학년 : 3
학점 : 4.5
일반적인 경우 구조체의 크기는 모든 멤버들의 크기를 합한 것이지만 항상 그렇지는 않다.
아래의 Student 구조체의 경우 name 은 10 byte, grade 는 4 byte, gpa 는 8 byte 이므로 구조체의 크기는 10 + 4 + 8 = 22 byte 로 예상된다.
#include <iostream>
struct Student
{
char Name[10]; // 10 byte
int Grade; // 4 byte
double GPA; // 8 byte
};
int main()
{
Student Student1;
std::cout << "sizeof(Name) : " << sizeof(Student1.Name) << std::endl;
std::cout << "sizeof(Grade) : " << sizeof(Student1.Grade) << std::endl;
std::cout << "sizeof(GPA) : " << sizeof(Student1.GPA) << std::endl;
std::cout << "sizeof(Student1) : " << sizeof(Student1) << std::endl;
return 0;
}
sizeof(Name) : 10
sizeof(Grade) : 4
sizeof(GPA) : 8
sizeof(Student1) : 24
그러나 확인 결과 Student 구조체의 크기는 24 byte로 예상 크기보다 더 큰 값으로 측정되었다.
이는 컴파일러에서 접근 속도를 더 빠르게 하기 위해 더 많은 메모리를 할당한 경우로 이를 바이트 패딩(Byte Padding) 이라고 한다.
따라서 구조체의 크기를 알기 위해서는 sizeof 연산자를 이용하는 것이 정확하다.
구조체의 멤버로는 어떤 자료형도 사용 가능하다. 따라서 구조체도 다른 구조체의 멤버가 될 수 있다.
#include <iostream>
// 생년월일 구조체
struct Date
{
int Year;
int Month;
int Day;
};
struct Student
{
char Name[10];
int Grade;
double GPA;
Date Birth; // 생년월일 구조체를 변수로 가짐
};
int main()
{
Student Student1;
// 멤버연산자(.)를 이용해 멤버접근
Student1.Birth.Year = 1998;
Student1.Birth.Month = 3;
Student1.Birth.Day = 15;
std::cout << "Year : " << Student1.Birth.Year << std::endl;
std::cout << "Month : " << Student1.Birth.Month << std::endl;
std::cout << "Day : " << Student1.Birth.Day << std::endl;
return 0;
}
Year : 1998
Month : 3
Day : 15
구조체 변수의 대입과 비교에서는 다음과 같은 연산들이 허용된다.
구조체를 다른 구조체에 대입하는 연산을 통해 하나의 구조체 변수에 들어 있는 자료들을 다른 구조체 변수로 복사할 수 있다.
#include <iostream>
// 좌표를 나타내는 구조체
struct Point
{
int X;
int Y;
};
int main()
{
Point P1;
Point P2;
P1 = { 3, 4 };
P2 = P1; // 구조체 변수 대입
std::cout << "P1(X, Y) : " << P1.X << ", " << P1.Y << std::endl;
std::cout << "P2(X, Y) : " << P2.X << ", " << P2.Y << std::endl;
return 0;
}
P1(X, Y) : 3, 4
P2(X, Y) : 3, 4
멤버 변수끼리의 대입 연산 또한 가능하다.
#include <iostream>
// 좌표를 나타내는 구조체
struct Point
{
int X;
int Y;
};
int main()
{
Point P1;
Point P2;
P1 = { 3, 4 };
// 멤버 변수 대입
P2.X = P1.X;
P2.Y = P1.Y;
std::cout << "P1(X, Y) : " << P1.X << ", " << P1.Y << std::endl;
std::cout << "P2(X, Y) : " << P2.X << ", " << P2.Y << std::endl;
return 0;
}
P1(X, Y) : 3, 4
P2(X, Y) : 3, 4
구조체 변수를 비교하려면 멤버 변수마다 별도의 비교 수식을 적어주어야 한다. 구조체 변수와 구조체 변수를 서로 비교하는 것은 허용되지 않는다.
#include <iostream>
// 좌표를 나타내는 구조체
struct Point
{
int X;
int Y;
};
int main()
{
Point P1;
Point P2;
P1 = { 3, 4 };
P2 = P1;
if ((P1.X == P2.X) && (P1.Y == P2.Y))
{
std::cout << "P1과 P2가 같습니다." << std::endl;
std::cout << "P1(X, Y) : " << P1.X << ", " << P1.Y << std::endl;
std::cout << "P2(X, Y) : " << P2.X << ", " << P2.Y << std::endl;
}
else
{
std::cout << "P1과 P2가 다릅니다." << std::endl;
}
// 구조체 변수 끼리의 비교는 컴파일 오류
//if (P1 == P2)
//{
// std::cout << "P1과 P2가 같습니다." << std::endl;
// std::cout << "P1(X, Y) : " << P1.X << ", " << P1.Y << std::endl;
// std::cout << "P2(X, Y) : " << P2.X << ", " << P2.Y << std::endl;
//}
return 0;
}
P1과 P2가 같습니다.
P1(X, Y) : 3, 4
P2(X, Y) : 3, 4
구조체 또한 다른 자료형들과 마찬가지로 배열로 사용될 수 있다. 구조체 배열의 선언 방법은 일반적인 배열의 선언 방법과 동일하며 선언과 동시에 초기화할 경우 {} 를 이용해 초기화를 진행할 수 있다.
#include <iostream>
struct Point
{
int X;
int Y;
};
int main()
{
// 구조체의 배열 및 초기화
Point P[3] =
{
{ 0, 1 }, // 첫 번째 요소 초기화
{ 2, 3 } // 두 번째 요소 초기화
};
P[2] = { 3, 4 }; // 세 번째 요소 초기화
for (int i = 0; i < 3; i++)
{
std::cout << "P[" << i << "] : " << P[i].X << ", " << P[i].Y << std::endl;
}
return 0;
}
P[0] : 0, 1
P[1] : 2, 3
P[2] : 3, 4
여러 개의 구조체가 필요할 경우 위처럼 구조체의 배열을 사용할 수 있다.
변수를 가리키는 포인터처럼 구조체를 가리키는 포인터도 만들 수 있다.
구조체 포인터는 다음과 같이 선언된다.
#include <iostream>
struct Student
{
char Name[10];
int Grade;
double GPA;
};
int main()
{
Student NewStudent = { "Kim", 4, 3.8 };
// 구조체(NewStudent)를 가리키는 포인터 P
Student* P = &NewStudent;
return 0;
}
참조 연산자 * 와 멤버 연산자 . 또는 간접 멤버 연산자 -> 를 통해 포인터를 이용하여 구조체의 멤버에 접근할 수 있다.