[C] 묶음상품이 많으니 코너로 만들자

장세민·2022년 11월 16일
0

📝 TIL

목록 보기
31/40

📌 구조체 활용

Before Starting

구조체는 데이터를 형태가 달라도 하나로 묶어 처리할 수 있는 장점이 있다.
그러나 데이터 종류가 많을 때는 구조체 변수의 크기가 커지는 아쉬움이 있다.

	struct stNumber
{
	int num;	// 정수라면 여기 저장
	double dnum;	// 실수라면 여기 저장
	int nums[3];	// 여럿이면 여기 저장
	char snum[10];	// 문자열이면 여기 저장
};

이럴 때 공용체를 쓰면 하나의 공간을 여러 멤버가 공유하여 최소한의 메모리만 사용할 수 있다.

📖 구조체 포인터와 -> 연산자

구조체 변수는 여러 개의 변수를 멤버로 가질 수 있지만, 그 자체는 단지 하나의 변수이다.

따라서 주소 연산자를 사용하면 특정 멤버의 주소가 아닌,
구조체 변수 전체의 주소가 구해진다.

그 값을 저장할 때는 구조체 포인터를 사용한다.

  1. #include <stdio.h>
  2.  
  3. struct score
  4. {
  5. int kor;
  6. int eng;
  7. int math;
  8. };
  9.  
  10. int main(void)
  11. {
  12. struct score yuni = { 90, 80, 70 };
  13. struct score *ps = &yuni;
  14.  
  15. printf("국어: %d\n", (*ps).kor);
  16. printf("영어: %d\n", ps -> eng);
  17. printf("수학: %d\n", ps -> math);
  18.  
  19. return 0;
  20. }

13행에서 구조체 포인터를 선언하면서 yuni의 주소로 초기화한다.
구조체 포인터는 가리키는 자료형으로 구조체를 사용하여 선언한다.

yuni 값을 ps에 저장하면 *연산을 수행하여 가리키는 변수 yuni를 사용할 수 있다.
그런데 yuni의 데이터는 멤버가 가지고 있으므로 추가로 멤버에 접근하는 과정이 필요하다.

🚨 주의사항

멤버에 접근하는 .연산자*연산자보다 우선순위가 높기 때문에
*연산자가 먼저 수행될 수 있도록
*연산자와 구조체 포인터 ps를 괄호()로 묶어야 한다.

만약 괄호를 쓰지 않으면?

우선순위에 따라 ps.kor이 먼저 수행되는데,
ps는 포인터로 kor 멤버가 없으므로 컴파일 오류가 발생!

매번 괄호를 사용하는 것이 번거롭다면 같은 기능을 하는 -> 연산자를 사용해도 됨!



📖 구조체 배열

구조체 변수는 멤버가 여러 개지만 구조체 변수 자체는 하나의 변수로 취급.
따라서 같은 형태구조체 변수가 많이 필요하면 배열 선언이 가능하다.

주소록을 만드는 프로그램을 통해 익혀보자.

내가 요즘 빠진 드라마 출연배우들

  1. #include <stdio.h>
  2.  
  3. struct address
  4. {
  5. char name[20];
  6. int age;
  7. char tel[20];
  8. char addr[80];
  9. };
  10.  
  11. int main(void)
  12. {
  13. struct address list[4] = {
  14. {"장세민", 22, "010-1234-5678", "경기 수원"},
  15. {"최시원", 37, "010-1111-2222", "서울 강남"},
  16. {"이다희", 37, "010-2222-3333", "서울 강남"},
  17. {"이대휘", 28, "010-3333-4444", "서울 서초"}
  18. };
  19. int i;
  20.  
  21. for (i = 0; i < 4; i++)
  22. {
  23. printf("%10s%5d%15s%20s\n",
  24. list[i].name, list[i].age, list[i].tel, list[i].addr);
  25. }
  26.  
  27. return 0;
  28. }
  29.  

3행이 주소록 데이터를 저장할 구조체 선언이고,
주소록은 여러 명의 데이터를 저장하므로 13행에서 구조체 변수를 배열로 선언한다.

배열을 선언하면 배열 요소가 하나의 구조체 변수가 되며,
각 요소는 일정한 크기로 연속된 저장 공간에 할당된다.

구조체 배열의 초기화는 배열의 초기화와 동일한 방법으로 중괄호 쌍 2개를 사용한다.

list[3].age

배열 요소를 사용할 때는 보통의 배열과 마찬가지로 첨자를 사용한다.
단, 배열 요소가 구조체 변수이므로 멤버에 접근할 때는 멤버 접근 연산자. 를 추가로 사용한다.



📖 구조체 배열을 처리하는 함수

구조체 배열의 이름은 첫 번째 요소의 주소이므로
이름을 인수로 받는 함수는 구조체 포인터를 매개변수로 선언한다.

주소록 출력하는 부분을 함수로 만들어 구조체 배열을 포인터로 다루는 방법을 알아보자.

  1. #include <stdio.h>
  2.  
  3. struct address
  4. {
  5. char name[20];
  6. int age;
  7. char tel[20];
  8. char addr[80];
  9. };
  10.  
  11. void print_list(struct address *lp);
  12.  
  13. int main(void)
  14. {
  15. struct address list[4] = {
  16. {"장세민", 22, "010-1234-5678", "경기 수원"},
  17. {"최시원", 37, "010-1111-2222", "서울 강남"},
  18. {"이다희", 37, "010-2222-3333", "서울 강남"},
  19. {"이대휘", 28, "010-3333-4444", "서울 서초"}
  20. };
  21.  
  22. print_list(list);
  23.  
  24. return 0;
  25. }
  26.  
  27. void print_list(struct address *lp)
  28. {
  29. int i;
  30.  
  31. for (i = 0; i < 4; i++)
  32. {
  33. printf("%10s%5d%15s%20s\n",
  34. (lp+i)->name, (lp+i)->age, (lp+i)->tel, (lp+i)->addr);
  35. }
  36. }

23행에서 print_list 함수를 호출할 때 배열명 list를 인수로 주는데,
배열명 list는 첫 번째 요소의 주소로 struct address 구조체 변수를 가리킨다.

따라서 print_list 함수의 매개변수로 struct address 구조체를 가리키는 포인터를 선언한다.

앞서 배웠던 것처럼, 포인터가 배열명을 저장하면 배열명처럼 사용할 수 있으므로,
매개변수 lp로 각 배열 요소를 참조하고 다음과 같이 멤버들을 출력할 수 있다.

printf("%10s%5d%15s%20s\n", lp[i].name, lp[i].age, lp[i].tel, lp[i].addr);

다음 세 가지 표현은 모두 같은 결과값을 갖는다.

lp[i].name	// 배열 표현
(*(lp+i)).name	// 포인터 표현
(lp+i)->name	// -> 연산자 사용


📖 자기 참조 구조체

개별적으로 할당된 구조체 변수들을 포인터로 연결하면 데이터를 하나로 묶어 관리할 수 있는데,
이때 자기 참조 구조체를 사용한다.

  1. #include <stdio.h>
  2.  
  3. struct list
  4. {
  5. int num;
  6. struct list *next;
  7. };
  8.  
  9. int main(void)
  10. {
  11. struct list a = {10, 0}, b = {20, 0}, c = {30, 0};
  12. struct list *head = &a, *current;
  13.  
  14. a.next = &b;
  15. b.next = &c;
  16.  
  17. printf("head->num : %d\n", head->num);
  18. printf("head->next->num: %d\n", head->next->num);
  19.  
  20. printf("list all: ");
  21. current = head;
  22. while (current != NULL)
  23. {
  24. printf("%d ", current->num);
  25. current = current -> next;
  26. }
  27. printf("\n");
  28.  
  29. return 0;
  30. }

3행의 구조체는 6행에서 자신의 구조체를 가리키는 포인터 멤버를 포함한다.

따라서 struct list 구조체 변수는 next 멤버로 다른 변수를 가리킬 수 있다.
14, 15행은 각각 a의 next 멤버로 b를 가리키고
b의 next 멤버로 c를 가리키도록 만들어 a, b, c를 연결한다.

연결 리스트

구조체 변수를 포인터로 연결한 것을 연결 리스트(linked list) 라고 한다.
첫 번째 변수의 위치만 알면 나머지 변수는 포인터를 따라가 모두 사용할 수 있으므로
대부분 12행처럼 첫 번째 변수의 위치를 head 포인터에 저장해 사용한다.

struct list *head = &a, *current;

17행의 head->num은 head가 가리키는 a의 num 멤버이므로 10이 된다.
18행의 head->next는 a의 next이고 a.next->num은 20이다.

이와 같이 포인터로 연결하여 사용할 수 있고,
만약 연결 리스트가 길면 head로 모든 값을 찾아가기 어렵기 때문에
21행처럼 다음 값을 찾아가는 별도의 포인터를 사용한다.

current = head;
   while (current != NULL)
   {
       printf("%d ", current->num);
       current = current->next;
   }

21행에서 current는 최초 a를 가리키다 25행을 수행하면서
다음 변수(b, c)를 가리키며 모든 num 값을 출력한다.

마지막으로 current가 c를 가리킬 때 current->next는 0이므로 그 값을 저장하면
널 포인터가 되어 22행에서 반복을 종료한다.

결국 current 포인터는 연결 리스트의 링크를 따라가며 모든 값을 출력하고,
이 과정은 head 포인터로도 수행할 수 있지만 head 포인터의 값을 바꾸면
다시 처음 위치를 찾아갈 수 없으므로
항상 연결 리스트의 시작 위치를 기억하도록 그 값을 바꾸지 말아야 한다.



📖 공용체

공용체는 구조체와 선언 방식이 비슷하지만
모든 멤버하나의 저장 공간을 같이 사용한다.

장점만 있지는 않을 것 같은데..

  1. // 공용체를 사용한 학번과 학점 데이터 처리
  2.  
  3. #include <stdio.h>
  4.  
  5. union student
  6. {
  7. int num;
  8. double grade;
  9. };
  10.  
  11. int main()
  12. {
  13. union student s1 = { 315 };
  14.  
  15. printf("학번: %d\n", s1.num);
  16. s1.grade = 4.4;
  17. printf("학점: %.1lf\n", s1.grade);
  18. printf("학번: %d\n", s1.num);
  19.  
  20. return 0;
  21. }

공용체도 마찬가지로 자료형을 선언한 후에 사용한다.
예약어 union를 사용하며 그 외 부분은 구조체 선언 형식과 같다.

공용체형으로 변수를 선언할 때, 저장 공간이 할당되는 방식과 초기화는 다음을 따른다.

🚨 규칙1. 공용체 변수의 크기는 멤버 중에서 크기가 가장 큰 멤버로 결정된다.

11행에서 union student의 변수를 선언하면 double형 멤버의 크기인 8바이트 공간이 할당되고
num과 grade 멤버가 하나의 공간을 공유한다.

저장 공간이 하나이므로 초기화 하는 방법이 구조체와 다르다.

🚨 규칙2. 공용체 변수의 초기화는 중괄호를 사용하여 첫 번째 변수만 초기화한다.

만약 첫 번째 멤버가 아닌 멤버를 초기화 할 때는 멤버 접근 연산자 . 로 멤버를 직접 지정해야 한다.

union student a = { .grade = 3.4 };

11행의 공용체 변수는 첫 번째 멤버 num만 초기화 됐고,
14행에서 grade 멤버에 값을 저장한 후 다시 num을 출력한 것이다.

즉, 처음 초기화했던 num 값이 grade 멤버에 의해 바뀐 것이다.
이처럼 공용체 멤버는 언제든지 다른 멤버에 의해 값이 변할 수 있으므로
항상 각 멤버의 값을 확인해야 하는 단점이 있다.

그러나 하나의 저장 공간을 공유하므로 메모리 절약
같은 저장 공간에 저장된 값을 여러가지 형태로 사용할 수 있는 장점이 있다.



📖 열거형

열거형은 변수에 저장할 수 있는 정수 값을 기호로 정의하여 나열한 것이다.

음... 무슨 말이지

  1. // 열거형을 사용한 프로그램
  2.  
  3. #include <stdio.h>
  4.  
  5. enum season {SPRING, SUMMER, FALL, WINTER};
  6.  
  7. int main(void)
  8. {
  9. enum season ss;
  10. char *pc = NULL;
  11.  
  12. ss = SPRING;
  13. switch (ss)
  14. {
  15. case SPRING:
  16. pc = "incline"; break;
  17. case SUMMER:
  18. pc = "swimming"; break;
  19. case FALL:
  20. pc = "trip"; break;
  21. case WINTER:
  22. pc = "skiing"; break;
  23. }
  24. printf("나의 레저 활동 => %s\n", pc);
  25.  
  26. return 0;
  27. }
  28.  

3행에서 예약어 enum과 열거형 이름을 짓고 괄호 안에 멤버를 콤마로 나열한다.
열거형을 정의하면 이름을 직접 사용할 수 있으므로 훨씬 읽기 쉬운 코드를 작성할 수 있다.



📖 typedef를 사용한 형 재정의

구조체, 공용체, 열거형의 이름은 항상 예약어와 함께 써야하므로 불편하다.
이때 typedef를 사용하면 예약어를 생략할 수 있다.

  1. /// typedef를 사용한 자료형 재정의
  2.  
  3. #include <stdio.h>
  4.  
  5. struct student
  6. {
  7. int num;
  8. double grade;
  9. };
  10. typedef struct student Student;
  11. void print_data(Student *ps);
  12.  
  13. int main(void)
  14. {
  15. Student s1 = { 315, 4.2 };
  16.  
  17. print_data(&s1);
  18.  
  19. return 0;
  20. }
  21.  
  22. void print_data(Student *ps)
  23. {
  24. printf("학번: %d\n", ps->num);
  25. printf("학점: %.1lf\n", ps->grade);
  26. }

구조체를 선언한 후에 8행에서 typedef로 자료형을 재정의했다.
재정의 이후에는 13행과 같이 구조체 struct student를 Student로 간단히 쓸 수 있다.

재정의 하기 전의 자료형을 굳이 사용할 필요가 없다면
다음과 같이 형 선언과 동시에 재정의하는 방법도 있다.

typedef struct
{
    int num;
    double grade;
} Student;


오늘 배운 내용들 꼭 잊지말자.
처음에 세웠던 공부 계획보다 많이 늦춰졌지만, 그래도 꾸준히 하려고 하는 나를 칭찬하고 싶다.
이제부터는 훨씬 더 어려운 자료구조를 들어가지만, 쫄지 않고 열심히 해보자.

얼른 이 지긋지긋한 c를 끝내보자!

profile
분석하는 남자 💻

0개의 댓글