구조체
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의 메모리 크기를 갖는다.
- 멤버변수끼리, 구조체끼리는 서로 순차적으로 연결되어 메모리 공간에 저장됨을 주소를 통해 알 수 있다.
내용이 틀릴 수도 있으니 잘못된 부분은 댓글 부탁드립니다.
이에 관련된 자세한 내용은 아래 블로그에서 잘 정리하셨으니 참고바랍니다.
구조체 포인터
- 말 그대로 구조체를 가리키는 포인터를 말한다.
- 구조체도 하나의 타입(형)이며, 메모리 공간에 저장되기 때문에 포인터로 시작주소를 가리킬 수 있다.
구조체 포인터 생성
#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