구조체
C/C++에서 구조화된 데이터를 처리할 때 사용
- 하나 이상의 변수(같거나 다른 데이터형을 갖는)를 묶어 새로운 자료형을 정의하는 도구
- 원시 자료형(int, float 등) 외에 새로운 데이터 타입을 직접 정의할 수 있게 함
- C++에서는 기본 접근 제어자가 다른 것 말고는 클래스와 동일하다. (구조체-public, 클래스-private)
구조체 생성
구조체 정의
- 구조체는 다음과 같이 정의한다.
- 구조체를 정의할 때는 멤버 변수를 초기화할 수 없다.
// 정의 방법 1: 구조체만 정의 struct 구조체이름 { /*멤버들*/ int age; string name; }; // 마지막에 무조건 ;(세미콜론) 붙여야 함
// 정의 방법 2: 구조체와 함께 구조체 변수를 정의 struct 구조체이름 { /*멤버들*/ int age; string name; } 구조체변수이름;
구조체 변수 정의 및 멤버 변수 접근
- 다음과 같이 구조체 변수를 정의한다.
struct 구조체명 구조체변수명
- '.' (온점)을 이용하여 멤버 변수에 접근한다.
// 구조체명.구조체변수명 human.age = 19;
Ex) 간단한 구조체 예제
#include <iostream> #include <string> using namespace std; struct human { int age; double height; string name; }; int main() { // 1. 구조체 변수 정의 후 멤버 변수 초기화 struct human person1; person1.age = 19; person1.height = 182.3; person1.name = "minwoo"; // 2. 구조체 변수 정의와 동시에 멤버 변수 초기화 struct human person2 = {20, 178.5, "hyunsoo"}; cout << person1.name << ", " << person1.age << ", " << person1.height << endl; cout << person2.name << ", " << person2.age << ", " << person2.height << endl; return 0; }
minwoo, 19, 182.3 hyunsoo, 20, 178.5
- 하나의 새로운 구조체를 정의하여 서로 다른 데이터형을 담아 정리할 수 있다.
- 구조체 변수를 정의한 후 멤버 변수를 초기화하는 방식과, 정의와 동시에 초기화하는 방식이 있다.
Ex) 구조체 정의와 동시에 구조체 변수 정의 및 멤버 초기화
#include <iostream> #include <string> using namespace std; struct human { int age; double height; string name; } person = { 23, 162.5, "Minji" }; // 구조체 변수 정의 및 멤버 초기화 int main() { cout << person.name << ", " << person.age << ", " << person.height << endl; return 0; }
Minji, 23, 162.5
- 구조체를 정의하면서 동시에 구조체 변수를 정의하고, 구조체 변수의 멤버를 초기화할 수 있다.
구조체 배열 만들기
- 구조체에도 배열 자료구조를 사용할 수 있다.
#include <iostream> #include <string> using namespace std; struct human { int age; double height; string name; }; int main() { struct human person[2] = { {19, 182.3, "minwoo"}, {20, 178.5, "hyunsoo"} }; cout << person[0].name << ", " << person[0].age << ", " << person[0].height << endl; cout << person[1].name << ", " << person[1].age << ", " << person[1].height << endl; return 0; }
minwoo, 19, 182.3 hyunsoo, 20, 178.5
구조체와 메모리
- 구조체가 메모리에 어떤 식으로 저장되는지 확인한다.
Ex) 구조체 메모리 확인하기
#include <iostream> #include <string> using namespace std; struct human { int age; double height; string name; }; int main() { struct human person[2] = { {19, 182.3, "minwoo"}, {20, 178.5, "hyunsoo"} }; cout << "person[0]의 크기: " << sizeof(person[0]) << endl; cout << "person[0]의 시작주소: " << &person[0] << endl; cout << "person[0].age의 시작주소: " << &person[0].age << endl; cout << "person[0].height의 시작주소: " << &person[0].height << endl; cout << "person[0].name의 시작주소: " << &person[0].name << endl << endl; cout << "person[1]의 크기: " << sizeof(person[1]) << endl; cout << "person[1]의 시작주소: " << &person[1] << endl; cout << "person[1].age의 시작주소: " << &person[1].age << endl; cout << "person[1].height의 시작주소: " << &person[1].height << endl; cout << "person[1].name의 시작주소: " << &person[1].name << endl; return 0; }
person[0]의 크기: 48 person[0]의 시작주소: 006FFB58 person[0].age의 시작주소: 006FFB58 person[0].height의 시작주소: 006FFB60 person[0].name의 시작주소: 006FFB68 person[1]의 크기: 48 person[1]의 시작주소: 006FFB88 person[1].age의 시작주소: 006FFB88 person[1].height의 시작주소: 006FFB90 person[1].name의 시작주소: 006FFB98
- 4bytes(int) + 8bytes(double) + 28bytes(string) = 40bytes가 되어야 한다고 생각했지만, 총 크기는 48bytes이다.
- 사실, 각 멤버 변수는 메모리 상에 1word 단위로 저장된다. ▶ 1word(32bit) - 4bytes, 1word(64bit) - 8bytes
- 필자의 컴퓨터는 64bit이므로 1word가 8bytes 단위이다.
- int와 double은 모두 1word 단위로 저장되어 8bytes만큼의 메모리를 할당받았다.
- string은 기본 메모리 크기가 28bytes이지만, 1word의 배수가 되어야 하기에 32bytes만큼 할당되었다.
- 따라서 구조체는 8bytes(int) + 8bytes(double) + 32bytes(string) = 48bytes의 메모리 크기를 갖는다.
- 멤버변수끼리, 구조체끼리는 서로 순차적으로 연결되어 메모리 공간에 저장됨을 주소를 통해 알 수 있다.
내용이 틀릴 수도 있으니 잘못된 부분은 댓글 부탁드립니다.
이에 관련된 자세한 내용은 아래 블로그에서 잘 정리하셨으니 참고바랍니다.
C언어 구조체의 메모리 사이즈(크기 계산)
* 구조체를 배우고 얼마 안되서, 구조체를 선언하여 그 크기를 계산해본적이 있다. 그런데...내가 상상했던...
blog.naver.com
구조체 포인터
- 말 그대로 구조체를 가리키는 포인터를 말한다.
- 구조체도 하나의 타입(형)이며, 메모리 공간에 저장되기 때문에 포인터로 시작주소를 가리킬 수 있다.
구조체 포인터 생성
#include <iostream> using namespace std; struct dummy { int a, b; }; int main() { struct dummy d; struct dummy *ptr; // 구조체 포인터 정의 ptr = &d; // 구조체 포인터이기 때문에 & 연산자 이용 (*ptr).a = 1; (*ptr).b = 2; cout << "d의 멤버 a: " << d.a << endl; cout << "d의 멤버 b: " << d.b << endl; cout << "ptr의 size: " << sizeof(ptr) << endl; return 0; }
d의 멤버 a: 1 d의 멤버 b: 2 ptr의 size: 4
- 여기서 ptr은 struct dummy라는 형을 가리키는 포인터이다.
- 구조체 포인터 ptr도 다른 모든 포인터와 동일하게 4byte의 메모리 공간을 차지한다.
- 구조체는 변수이지, 배열이 아니다. ▶따라서 구조체 포인터를 만들 때, 구조체 변수의 이름이 아닌 & 연산자 + 구조체 변수 이름을 사용하여 구조체가 정의된 메모리 주소를 가져온다.
-> 연산자
- 포인터의 경우 () 소괄호의 역할이 매우 크다. 다음 예시를 보자.
Ex) 포인터에 ( ) 소괄호를 붙이지 않는 경우
#include <iostream> using namespace std; struct dummy { int a, b; }; int main() { struct dummy d; struct dummy *ptr; ptr = &d; *ptr.a = 1; // 소괄호를 붙이지 않는 경우 (*ptr).b = 2; cout << "d의 멤버 a: " << d.a << endl; cout << "d의 멤버 b: " << d.b << endl; return 0; }

- 소괄호를 넣지 않아서 오류가 발생하였다.
- 이는 *(asterisk)과 .(period)연산자의 우선 순위에 의해 발생한 오류이다. 아래의 우선순위 표를 보자.

- .(period)가 *(asterisk)보다 우선순위가 빠르다.
- 따라서 *ptr.a는 *(ptr.a)의 순서로 처리된다. ▶ 여기서 ptr은 포인터로, 구조체가 아니기 때문에 멤버 변수 a를 가지고 있지 않다. 그러므로 a 멤버에 접근 시 오류가 발생한다.
- 그렇기 때문에 구조체 포인터에서 ( ) 소괄호를 빠뜨리지 않고 꼭 적어야 한다.
- 그런데 ( ) 소괄호를 계속 적는 것은 매우 귀찮은 일이다. ▶ 프로그래머들은 이 문제를 해결하기 위해 새로운 연산자를 개발했다. 바로 -> 연산자이다.
Ex) -> 연산자 이용하여 포인터로 멤버 변수에 접근하기
#include <iostream> using namespace std; struct dummy { int a, b; }; int main() { struct dummy d; struct dummy *ptr; ptr = &d; ptr->a = 1; // -> 연산자 이용하기 (*ptr).b = 2; // () 소괄호를 이용하는 기존 방식 cout << "d의 멤버 a: " << d.a << endl; cout << "d의 멤버 b: " << d.b << endl; return 0; }
d의 멤버 a: 1 d의 멤버 b: 2
- ptr->a = 1 이라는 표현은 (*ptr).a = 1 과 정확히 일치하는 문장이다.
- 단순히 편의를 위해 -> 연산자가 도입되었으며, 이를 사용하면 귀찮게 ( ) 소괄호를 적을 필요가 없다.
구조체 포인터 연습
Ex) 포인터 멤버 연습 1
#include <iostream> using namespace std; struct dummy { int* pointer; }; int main() { struct dummy d; struct dummy* ptr; // 구조체 포인터 정의 int i = 0; ptr = &d; d.pointer = &i; // pointer 멤버가 포인터이므로, i의 주소값을 넘김. (*ptr).pointer = &i; 와 동일 *d.pointer = 3; // d의 멤버 pointer가 가리키는 변수 값을 3으로 변경. *(d.pointer) = 3; 와 동일 cout << "i의 값: " << i << endl; *ptr->pointer = 4; // -> 연산자 이용하여 pointer 멤버가 가리키는 변수 값을 4로 변경 cout << "i의 값: " << i << endl; return 0; }
i의 값: 3 i의 값: 4
- 멤버 변수에는 포인터를 포함시킬 수 있다.
Ex) 포인터 멤버 연습2 (Call-by-reference)
#include <iostream> using namespace std; int plus_ten(int*); struct dummy { int i; }; int main() { struct dummy d; struct dummy* ptr; // 구조체 포인터 정의 ptr = &d; ptr->i = 0; // 멤버 i 초기화 cout << "i의 값(함수 호출 전): " << ptr->i << endl; plus_ten(&d.i); // 값 변경을 위해 구조체 멤버의 주소를 전달. &(d.i)와 동일 cout << "i의 값(함수 호출 후1): " << d.i << endl; plus_ten(&ptr->i); // 값 변경을 위해 -> 연산자 이용. &(ptr->i)와 동일 cout << "i의 값(함수 호출 후2): " << ptr->i << endl; return 0; } int plus_ten(int* num) { *num += 10; return 0; }
i의 값(함수 호출 전): 0 i의 값(함수 호출 후1): 10 i의 값(함수 호출 후2): 20
- 구조체 포인터를 이용하여 call-by-reference를 구현한 예제이다.
- 연산자의 우선순위를 잘 따져야 한다. ▶ &d.i → &(d.i)
구조체 복사하기
- 구조체는 대입 연산을 통해 간단히 복사가 가능하다.
#include <iostream> using namespace std; struct dummy { int i; string str; }; int main() { struct dummy a, b; a.i = 10; a.str = "Hello!"; b = a; // 대입하여 복사하기 cout << "a의 멤버 i: " << a.i << endl; cout << "a의 멤버 str: " << a.str << endl; cout << "b의 멤버 i: " << b.i << endl; cout << "b의 멤버 str: " << b.str << endl; return 0; }
a의 멤버 i: 10 a의 멤버 str: Hello! b의 멤버 i: 10 b의 멤버 str: Hello!
구조체 매개변수
- 구조체를 인자(매개변수)로 전달할 수 있다.
- 구조체를 인자로 전달할 때 call-by-reference가 이루어져야 함을 유의한다.
구조체 인자 전달하기
#include <iostream> using namespace std; int set_struct(struct dummy*, int, string); struct dummy { int i; string str; }; int main() { struct dummy a; set_struct(&a, 10, "Hello!!"); cout << "a의 멤버 i: " << a.i << endl; cout << "a의 멤버 str: " << a.str << endl; return 0; } int set_struct(struct dummy *sr, int i, string str) { sr->i = i; sr->str = str; return 0; }
a의 멤버 i: 10 a의 멤버 str: Hello!!
- 구조체 변수 a의 주소를 인자로 전달함으로써 구조체 멤버에 접근하여 값을 초기화할 수 있다.
- 이때 함수 set_struct의 매개변수 sr은 구조체 포인터이며, 절대 구조체가 아니다. ▶ 단지 sr은 구조체 변수 a가 저장된 메모리의 시작주소를 담는다.
구조체 안의 구조체
- 구조체는 구조체를 멤버로 가질 수 있다.
#include <iostream> using namespace std; struct profiles { int gradeNum; int classNum; int studentNum; string name; }; struct school { struct profiles data; string schoolName; }; int main() { struct school High; High.schoolName = "Seoul"; High.data.gradeNum = 2; High.data.classNum = 7; High.data.studentNum = 17; High.data.name = "minji"; cout << "학교이름: " << High.schoolName << " High School" << endl; cout << "학년: " << High.data.gradeNum << endl; cout << "반: " << High.data.classNum << endl; cout << "이름: " << High.data.name << endl; cout << "번호: " << High.data.studentNum << endl; return 0; }
학교이름: Seoul High School 학년: 2 반: 7 이름: minji 번호: 17
- 구조체 또한 int, float와 같은 하나의 형(타입)이기 때문에 구조체가 구조체를 멤버로 가질 수 있는 것은 당연하다.
- 연산자 우선순위에 의해 High.data.name은 (High.data).name로 처리된다. ▶ High의 멤버 data의 멤버 name
구조체 반환하기
- 구조체는 하나의 타입(형)이기 때문에 반환(return)이 가능하다.
- 아래는 구조체를 반환하는 함수를 구현한 예제이다.
#include <iostream> using namespace std; struct dummy func(int); struct dummy { int a; }; int main() { struct dummy d; d = func(10); // return한 구조체를 복사 cout << "d의 멤버 a: " << d.a << endl; return 0; } struct dummy func(int i) { struct dummy A; A.a = i; return A; }
d의 멤버 a: 10
구조체 안에 함수 넣기
- 구조체 안에 함수를 넣고, 함수에 구조체 멤버 변수들을 활용할 수 있다.
- 구조체 안의 들어있는 함수 또한 멤버이며, 이를 멤버 함수 또는 멤버 메서드라고 한다.
#include <iostream> using namespace std; struct dummy { int a, b; // 멤버 함수(멤버 메서드) int mul() { return a * b; // 멤버 변수 사용 가능 } }; int main() { struct dummy d = {5, 10}; cout << d.a << " x " << d.b << " = " << d.mul() << endl; return 0; }
5 x 10 = 50