일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 포르투갈 여행
- 지베르니
- push swap 설명
- 지베르니 계절 추천
- 42 libft
- gnl
- 와인 고르기
- so_long
- 와인선별방법
- 이지젯
- pipex
- 42 pipex
- libft
- ft_printf
- pipex 42
- 42 so_long
- 알고리즘 기초
- get_next_line
- printf
- 서울42
- get next line
- 42
- 에꼴42
- ecole42
- 파리 피크닉
- push swap
- 지베르니 여름
- 굿노트 스티커
- str함수
- 지베르니 가을
- Today
- Total
뇌 마음 반반저장소
[42] 왕초보의 구조체(struct) 뿌시기 본문
구조체는 지금 알아두지 않으면 두고두고 발목을 잡을 것 같다. 두 번째 서클로 들어오자 아예 구조체에 선언해 놓은 변수들로만 작문을 하기 시작한다.. 후덜덜.. 매일 할 때마다 계속 찾아봤던 구조체. 구조체를 파헤쳐보자!
!계속 찾다 보니 C언어와 C++의 구조체가 조금 다른 것을 확인했다. 이 설명은 C언어의 구조체임을 명시한다!
1. 구조체의 구조! struct & typedef
자, 구조체의 정의를 살펴보자.
C/C++ 프로그래밍 언어에서 구조화된 데이터를 처리할 때 struct를 사용하는데 이를 구조체라고 한다. 구조화되었다는 말은 의미가 연결되어 한 덩어리로 처리하는 방식을 말한다. 그리고 자료처리와 연관하여 데이터 구조와 연관이 되어 있다.
위키백과에 나온 설명이지만 역시나 못알아 듣겠다. 내가 이해한 바로는 이렇다.
다양한 변수들을 한 카테고리로 묶어줘서 편하게 찾아보고 관리하는 것!
만약에 요리책에 다양한 재료가 필요하다면, 이해하기 쉽게 같은 카테고리끼리 묶어 주는 것이다.
예를 들어서
구조체 이름) 야채 종류 나열 : 양파, 감자, 당근, 고추 : 구조체 별명) 야채
구조체 이름) 고기 종류 나열 : 소, 닭, 돼지, 양 : 구조체 별명) 고기
이런식으로 나눠서 카테고리별로 변수를 불러오는 것이다!
그러면 이것을 코드로 쓰면 어떨까?
struct list_vegetables_types
{
int onion;
int potato;
int carot;
int go_chu;
};
struct list_meat_types
{
int beef;
int chicken;
int pork;
int yang;
} gogi; //이렇게 별명을 붙여줄 수도 있다.
이렇게 선언된 구조체는 아래와 같이 사용할 수 있다.
int main()
{
struct list_vegetables_types onion = 10;
struct gogi beef = 5;
return();
}
이렇게 위에 하나하나 struct을 써줘야 하는 번거로움을 없애기 위해 사용하는 것이 바로 typedef! 뒤에 별명을 붙여주면 바로 전역 변수로 선언이 되어 바로 사용할 수 있다.
(전역 변수에 대한 설명은 : 왕초보의 헤더파일(Header file) 뿌시기)
typedef struct {
int beef;
int chicken;
int pork;
int yang;
} gogi;
구조체의 정의를 typedef와 함께 선언한다면 이름을 생략하고 아래와 같이 쓸 수가 있다!
이렇게 선언된 구조체는 아래와 같이 사용한다.
int main()
{
gogi gangnam; //강남점의 고기를 관리할 수 있게 되었습니다! 변수선언하기
return();
}
이렇게 간결함 덕분에 typedef를 선호한다! 위에 새로운 변수를 선언했다. 바로 강남점의 고기를 관리하는 변수를 선언했다. 저렇게 하면 송파구, 잠실구 여러 동네의 변수를 뒀을 때도 저 구조체를 사용해서 고기 변수를 불러올 수 있을 것이다!
참 쉽쥬? 👧🏻
2. 구조체 사용하기
1.구조체 변수, 직접 값 주기
구조체에 값을 직접 변수에다가 넣어보자.
1. 직접 변수를 입력하는 방법
#include <stdio.h>
typedef struct {
int beef; // 소 갯수
int chicken; // 닭 갯수
char *name; // 고기 관리자 이름
} gogi;
int main()
{
gogi gangnam;
gangnam.beef = 3; //직접 변수를 입력한다!
gangnam.chicken = 2;
gangnam.name = "김선생";
printf("%s(이)가 관리하는 소고기는 %d개, 닭고기는 %d개 입니다.\n", \
gangnam.name, gangnam.beef, gangnam.chicken);
return(0);
}
> gcc main.c
> .\a.exe
김선생(이)가 관리하는 소고기는 3개, 닭고기는 2개 입니다.
2. 배열로 값을 입력하는 방법
#include <stdio.h>
typedef struct {
int beef;
int chicken;
char *name;
} gogi;
int main()
{
gogi gangnam = { 3, 2, "김선생"}; //그냥 배열로 때려 넣을 수 있다!
printf("%s(이)가 관리하는 소고기는 %d개, 닭고기는 %d개 입니다.\n", \
gangnam.name, gangnam.beef, gangnam.chicken);
return(0);
}
> gcc main.c
> .\a.exe
김선생(이)가 관리하는 소고기는 3개, 닭고기는 2개 입니다.
3. 배열을 사용해 여러 값을 입력하는 방법
#include <stdio.h>
typedef struct {
int beef;
int chicken;
char *name;
} gogi;
int main()
{
gogi gangnam[2]; //배열을 준다음에
gangnam[0].beef = 3;
gangnam[0].chicken = 2;
gangnam[0].name = "김선생";
printf("%s(이)가 관리하는 소고기는 %d개, 닭고기는 %d개 입니다.\n", \
gangnam[0].name, gangnam[0].beef, gangnam[0].chicken);
gangnam[1].beef = 5;
gangnam[1].chicken = 6;
gangnam[1].name = "이선생";
printf("%s(이)가 관리하는 소고기는 %d개, 닭고기는 %d개 입니다.\n", \
gangnam[1].name, gangnam[1].beef, gangnam[1].chicken);
return(0);
}
> gcc main.c
> .\a.exe
김선생(이)가 관리하는 소고기는 3개, 닭고기는 2개 입니다.
2.구조체 변수, 포인터로 값 주기
구조체 포인트라고 쫄지말자! (저는 정말 쫄았어든요..블로그를 정리하면서 이해가 되네요..)
강남의 여러 지점을 만들고 싶어졌다. 위에처럼 1호점.. 2호점.. 3호점.. 같은 카테고리로 매장의 숫자만 늘리고 싶다. 그러면 포인터를 만들어서 해보자!
? 왜 굳이 포인터를 만들어서 사용해야 하나요?
위에 배열처럼 하면 되지 왜 복잡한 포인터를 사용해야 하는지 의문이 생겼다. (스압 주의.. 정말 제대로 팠다..)
이를 알기 위해서는 구조체와 변수들이 어디 저장되는지 알아야 한다.
*흥미로운 점 : 구조체와 변수들.. 데이터들은 어디에 저장이 되는 것일까?
여기서 헷갈리지 말아야 할 것은. 이 모든 정보는 구조체의 변수에 저장이 되는 것이 아니다! (거 당연한 거 아니오!라고 하시는 분들은.. 저는 왕초보라..)
이 정보들은, 위의 예시에서 사용하고 있는 int main() 함수의 gangnam과 group은 엄연히 지역변수이다.
구조체의 리스트는 데이터 영역에 저장되고(struct 부분),
함수 안에서 선언된 구조체와 연결된 변수의 값은 스택 영역에 존재한다. (왕초보의 헤더 파일 뿌시기 참조)
위의 상태, 즉 변숫값의 공간을 정해주지 않고 계속 값을 입력하면, 변경되는 데이터는 스택영역에 쭉쭉 쌓인다. 스택은 변강쇠 같은 캐릭터라고 생각하면 쉽다. 계속 쌓인 스택 데이터 더미 안에서 최초의 데이터를 찾으려고 돌아간다고 상상해보자. 스택 변강쇠는 "어? 그 데이터! 옙 마님!" 하면서 그동안 쌓아놓았던 데이터들을 헤치며, 모두 지우면서 최초의 데이터로 더듬어 내려가게 된다. (선입 후출 방식으로 위에서 아래로 내려가며 데이터를 찾겠죠?) 그러면 우리가 쌓아놓은 데이터는 모두 사라지고 만다..! (네이놈 ㅠㅠ)
그래서 우리는 필요한 데이터를 쪼개서 그. 것. 만. 가져올 수 있는 영역, 힙영역을 사용하는 것이다. 힙은 집사같은 캐릭터라고 생각하면 쉽다. 힙 영역의 데이터는 주욱~ 나열한 서랍장에 byte단위로 데이터를 저장한다. 그렇게 집사는 쌓이는 데이터를 서랍 안에 착착 넣어 놓는다. 내가 원하는 데이터를 호출하면 "그 데이터 말씀이십니까? 이 서랍장 안에 있습죠." 하면서 그 포인트로 가서 데이터를 꺼내 준다.
gogi *group; //포인터로 원하는 서랍장의 위치에 가도록 정한다.
group = (gogi *)malloc(sizeof(gogi)); //포인터 주소 위치부터 데이터 사이즈만큼 서랍장을 만든다.
하지만 힙 영역은 메모리를 순차적으로 사용하기 때문에, 빈 곳 없이 메모리를 차곡차곡 쌓아서 저장하는 습관을 길러야 한다고 한다.. 그래서 구조체 관련 메모리 설명이 유독 많다. 그러면 메모리를 차곡차곡 쌓아야 한다는 뜻은 무엇일까?
구조체 메모리는 선언된 순서대로 서랍장 공간을 할당한다. 컴퓨터는 자동적으로 4바이트씩 끊어서 메모리를 할당하쥬? 그렇기 때문에 서랍장 1개에는 4개의 1바이트 공간, 즉 4칸씩 있을 것이다.
예를 들어 보자. char은 1바이트, int는 4바이트이다.
struct A{
char a;
int b;
char c;
};
컴퓨터는 이 코드를 보고 위에서부터 읽을 것이다.
"아 1번 서랍에 1바이트 한 칸 넣고.. 다음은 다른 데이터 유형이네?
그러면 새로운 서랍장 2번 서랍에다가 4바이트 넣고.. 또 다른 유형이네?
그러면 새로운 서랍장 3번 서랍에 하나 넣고.."
그렇다면 총바이트의 합은 서랍장 3개, 즉 12칸, 12바이트가 되는 것이다. 1바이트만 들어간 서랍장에 나머지 3칸을 바이트 패딩이라고 부른다. 이렇게 데이터가 비효율적으로 바이트 패딩이 쌓이는 것을 막아 야 한다는 뜻이다.
struct A{
char a;
char c;
int b;
};
☝🏻자 이렇게 하면
1번 서랍장에 2칸 + 2칸 빈 공간,
2번 서랍장에 4칸,
= 총 8바이트가 된다.
struct A{
int b;
char a;
char c;
};
☝🏻자 이렇게 하면
1번 서랍장에 4칸 모두 사용하고
2번 서랍장에는 2칸만 넣게 돼서,
= 총 6바이트가 된다.
하지만 이렇게 남아도는 서랍장을 자동으로 정리해주는 전처 리어가 있으니.. 바로 #pragma pack()이라는 전처리어다. 하지만 42에서는 사용해도 되는지 모르겠다.. 이렇게 사용한다고 한다.
#pragma pack(push, 1) //1바이트씩 서랍장 만들라고 지시함
struct A{
char a;
int b;
char c;
};
#pragma pack(pop) //끝났으니 터지라고 지시함
끝으로 다 사용한 서랍장은 free를 사용해 메모리를 깔끔하게 정리해야 쾌적한 환경이 될 것이다.
구조체뿐만 아니라 코딩을 작문할 때 변수 데이터들을 저장할 pointer와 malloc을 사용해야 하는 이유이다!
참고 자료👇
구조체에서 데이터를 다른 형식으로 저장하는 키워드 공용체, 열거체(Union, Enum) : http://www.tcpschool.com/c/c_struct_unionEnum
[C#] C# 구조체(struct)로 메모리 절약하기 :https://kukuta.tistory.com/385
자 그러면! 드디어 포인터로 한번 코오딩을 해보자.
#include <stdio.h>
#include <stdlib.h> // 말록형님의 헤더
typedef struct {
int beef;
int chicken;
char *name;
} gogi;
int main()
{
gogi *group; //포인터로 힙영역에 서랍장 만들 위치를 잡아주고
group = (gogi *)malloc(sizeof(gogi)); //서랍장 하나를 만들어준다.
group->beef = 5;
group->chicken = 6;
group->name = "김선생";
printf("%s(이)가 관리하는 소고기는 %d개, 닭고기는 %d개 입니다.\n", \
group->name, group->beef, group->chicken);
return(0);
}
> gcc main.c
> .\a.exe
김선생(이)가 관리하는 소고기는 5개, 닭고기는 6개 입니다.
자 그러면 그룹 서랍장에 관리자가 관리하는 고기들의 변수가 채워졌다!
(드디어 이해했다... 나 자신 칭찬해 ㅠㅠ)
3. 구조체 변수, 중첩 구조체로 카테고리 만들기
그럼 각 호점의 다른 스톡을 관리하기 위한 구조체는 어떻게 사용할까?
일단 관리해야 할 고기 카테고리를 만든다.
typedef struct stock{
int beef;
int chicken;
} gogi;
그리고 관리해야할 매장의 정보 카테고리를 담은 구조체를 또 하나 만든다.
typedef struct meajang_info{
int hojum;
char *user_name;
char *jijum;
gogi gogi_info;
} info;
1. int hojum; 몇 호점인지 알려주는 숫자 변수!
2. char *user_name; 관리자의 이름
3. char *jijum; 지점 이름
4. gogi gogi_info; 이게 중요한데! 위에 있는 고기 리스트를 사용할 때 위의 구조체를 불러오는 변수 이름이다. "gogi라는 구조체에서 변수들 불러올건데, 그건 gogi_info라고 부르며 다음 변수들을 불러오겠소!"
최종 구현되는 중첩 구조체의 함수는 이렇게 이루어진다.
#include <stdio.h>
#include <stdlib.h>
typedef struct stock{
int beef;
int chicken;
} gogi;
typedef struct meajang_info{
int hojum;
char *user_name;
char *jijum;
gogi gogi_info;
} info;
int main()
{
info *group; //모든 정보를 시작하는 포인터 지점을 만든다. 서랍이 시작되는 지점.
group = (info *)malloc(sizeof(info)); //모든 정보를 담을 수 있는 서랍장을 만든다.
group->jijum = "강남";
group->hojum = 1;
group->user_name = "김선생";
group->gogi_info.beef = 3; //고기인포에서 소고기 변수에다가 3을 넣어주시오.
group->gogi_info.chicken = 2;
printf("%s %d호점을 관리하는 %s의 소고기는 %d개, 닭고기는 %d개 입니다.\n", \
group->jijum, group->hojum, group->user_name, group->gogi_info.beef, group->gogi_info.chicken);
group->jijum = "강남";
group->hojum = 2;
group->user_name = "이선생";
group->gogi_info.beef = 5;
group->gogi_info.chicken = 1;
printf("%s %d호점을 관리하는 %s의 소고기는 %d개, 닭고기는 %d개 입니다.\n", \
group->jijum, group->hojum, group->user_name, group->gogi_info.beef, group->gogi_info.chicken);
free(group);
return(0);
}
> gcc main.c
> .\a.exe
강남 1호점을 관리하는 김선생의 소고기는 3개, 닭고기는 2개 입니다.
강남 2호점을 관리하는 이선생의 소고기는 5개, 닭고기는 1개 입니다.
(찢었다..)
4. 구조체 변수, 자기 참조 구조체로 한 카테고리 연결하기!
자기 참조 구조체는 보통 관련된 카테고리의 내용을 엮어서 불러올 때 사용한다고 한다. 자기 자신을 구조체 안에 구조체로 선언해서 연결할 수 있는 리스트를 생성한다. (윽엑윽엑)
자기 참고 구조체는 필요할 때 한번 좌르륵 정리해보고자 한다.. 다음에 다시 돌아왔을 때, 아래의 블로그를 참조하자 자신아!
참고 👇
https://dev-ku.tistory.com/109
구조체를 하고 나니 더욱더 자신감이 솟는 것 같다! 이제 진짜 libft 하러 총총총....
도움을 주고 싶으신 내용이나
틀린 내용이 있다면 댓글로 남겨주시고,
도움이 되었다면 공감 한 번씩 눌러주세요👍❤️
'좌뇌' 카테고리의 다른 글
[42] 왕초보의 헤더파일(Header file) 뿌시기 (0) | 2022.11.30 |
---|---|
[42] 왕초보의 Makefile 뿌시기 (2) | 2022.11.28 |