[C] '사과' '3'개 '한 세트'로 팝니다@@

장세민·2022년 11월 9일
0

📝 TIL

목록 보기
30/40

Before Starting

배열은 같은 형태의 데이터를 묶어 반복문으로 처리할 수 있는 자료형이다.

만약, 학번(123), 이름(장세민), 학점(4.5)처럼 다른 자료형을 하나의 배열로 선언하는 것은?

불가능하다.

각각의 자료형의 형태가 다르기 때문이다.
그렇다면 다른 형태의 데이터들을 하나로 묶어 단일 자료형으로 다루면 해결될 것이고,
이를 도와주는 것이 오늘 배울 구조체 이다.



📌 구조체

구조체는 다양한 자료형을 하나로 묶을 수 있는 복합 자료형으로
다양한 형태의 데이터를 처리할 때 기본으로 사용된다.

📖 구조체 선언과 멤버 사용

구조체는 하나의 자료형으로 변수 선언이 가능하지만,
구조체의 형태를 컴파일러에 미리 알려주는 구조체 선언을 해야한다.

구조체 선언이 끝나면 새로운 자료형이 만들어지며 그 이후부터는
구조체의 변수를 사용할 수 있다.

구조체를 선언하고 멤버를 사용해보자.

  1. #include <stdio.h>
  2.  
  3. struct student
  4. {
  5. int num;
  6. double grade;
  7. };
  8.  
  9. int main(void)
  10. {
  11. struct student s1;
  12.  
  13. s1.num = 2;
  14. s1.grade = 2.7;
  15. printf("학번: %d\n", s1.num);
  16. printf("학점: %.1lf\n", s1.grade);
  17.  
  18. return 0;
  19. }

3행에서 7행까지 struct 예약어를 사용하여 구조체를 선언한다.

struct student      // struct는 예약어, student는 구조체 이름

이름은 구조체 성격에 맞춰 붙이며 블록 안에 멤버를 나열한다.
멤버 선언은 구조체를 구성하는 자료형 종류이름을 컴파일러에 알리는 것이다.

🚨 구조체 선언이 main 함수 앞에 있으면 프로그램 전체, 함수 안에 선언하면 그 안에서만 사용 가능!


struct student s1;    // 11행.

구조체는 struct 예약어와 구조체 이름을 함께 하나의 자료형 이름으로 사용한다.
11행에서는 struct student가 int와 double로 이루어진 새로운 자료형이 된다.

s1은 구조체 변수명으로 사용자가 임의로 이름을 붙인 것이다.

구조체 변수를 선언하면 각 멤버의 공간이 메모리에 연속으로 할당되며
모든 멤버를 더한 전체 저장 공간이 하나의 구조체 변수가 되므로
변수의 크기는 각 멤버의 크기를 더한 값이 된다.

선언된 구조체 변수에서 특정 멤버를 골라서 사용할 때
멤버 접근 연산자 (.)가 필요하다.

s1.num;    // 13행. 멤버 접근 연산자 .

이렇게 하면 멤버를 독립된 변수로 사용할 수 있다.
즉, s1은 구조체 변수지만 s1.num은 int형 변수가 된다.


구조체 변수의 크기

데이터를 빠르게 읽고 쓰기 위해서는 일정한 크기 단위로 메모리에 접근해야 한다.

따라서 컴파일러는 구조체 멤버의 크기가 일정하지 않을 경우
멤버 사이에 패딩 바이트를 넣어 멤버들을 정렬한다.

이를 바이트 얼라인먼트라고 한다.

크기가 가장 큰 멤버(grade)가 메모리를 할당하는 기준 단위가 된다.

기준 블록 내 크기가 작은 멤버들은 각 자료형의 크기 단위로 할당된다.

  • char형: 1 byte
    short형: 2 byte
    int형: 4 byte
    double형: 8 byte

결국 멤버의 순서에 따라 구조체의 크기가 달라질 수 있으므로
패딩 바이트가 가장 작도록 구조체를 선언하면 메모리를 아낄 수 있다.

또는 다음과 같이 컴파일러에 패딩 바이트를 넣지 않도록 지시할 수 있다.

#pragma pack(1);    // 바이트 얼라인먼트를 1로 설정하면 패딩 바이트가 필요 없음

이 경우 데이터를 읽고 쓰는 시간은 더 걸리지만
구조체의 크기는 모든 멤버의 크기를 더한 것과 같아지므로
사용하는 메모리의 크기는 최소화할 수 있다.



📖 다양한 구조체 멤버

구조체 멤버로 배열, 포인터는 물론 이미 선언된 다른 구조체도 멤버로 쓸 수 있다.

배열과 포인터를 멤버로 갖는 구조체를 사용해보자.

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4.  
  5. struct profile
  6. {
  7. char name[20];
  8. int age;
  9. double height;
  10. char *intro;
  11. };
  12.  
  13. int main(void)
  14. {
  15. struct profile semin;
  16.  
  17. strcpy(semin.name, "장세민");
  18. semin.age = 22;
  19. semin.height = 185.0;
  20.  
  21. semin.intro = (char *)malloc(80);
  22. printf("자기소개: ");
  23. gets(semin.intro);
  24.  
  25. printf("이름: %s\n", semin.name);
  26. printf("나이: %d\n", semin.age);
  27. printf("키: %.1lf\n", semin.height);
  28. printf("자기소개: %s\n", semin.intro);
  29. free(semin.intro);
  30.  
  31. return 0;
  32. }

7행에서 profile 구조체는 이름을 저장하는 멤버로 배열을 사용한다.
즉, 구조체 변수를 선언하면 베열 멤버도 그 크기만큼 저장 공간이 할당된다.

구조체의 멤버로 포인터를 쓰면 포인터 멤버에 대입 연산으로 간단히 문자열을 연결할 수 있다.

이 경우 문자열 상수 대신 문자열을 바로 입력해서는 안된다.
intro 멤버는 포인터이므로 문자열 자체를 저장할 공간은 없다.

따라서 입력을 위해서는
동적 할당을 통해 저장 공간을 먼저 확보해야 한다.


이번에는 다른 구조체를 멤버로 갖는 구조체를 사용해보자.

  1. #include <stdio.h>
  2.  
  3. struct profile
  4. {
  5. int age;
  6. double height;
  7. };
  8.  
  9. struct student
  10. {
  11. struct profile pf;
  12. int id;
  13. double grade;
  14. };
  15.  
  16. int main(void)
  17. {
  18. struct student semin;
  19.  
  20. semin.pf.age = 22;
  21. semin.pf.height = 185.0;
  22. semin.id = 123;
  23. semin.grade = 4.5;
  24.  
  25. printf("나이: %d\n", semin.pf.age);
  26. printf("키: %.1lf\n", semin.pf.height);
  27. printf("학번: %d\n", semin.id);
  28. printf("학점: %.1lf\n", semin.grade);
  29.  
  30. return 0;
  31. }

11행에서 student 구조체는 profile 구조체를 멤버의 자료형으로 사용한다.
student 구조체는 profile 구조체의 멤버를 모두 자신의 데이터로 가질 수 있다.

다만 포함된 profile 구조체의 멤버를 사용하기 위해서는
멤버 접근 연산자.를 2번 사용해야 한다.



📖 구조체 변수의 초기화와 대입 연산

구조체 변수도 일반 변수와 같이 선언과 동시에 초기화가 가능하다.
초깃값을 중괄호로 묶고 각 멤버의 형태에 맞는 값으로 초기화한다.

초기화와 대입을 활용하여 최고 학점의 학생 데이터를 출력해보자.

  1. #include <stdio.h>
  2.  
  3. struct student
  4. {
  5. int id;
  6. char name[20];
  7. double grade;
  8. };
  9.  
  10. int main(void)
  11. {
  12. struct student s1 = {123, "장세민", 4.5},
  13. s2 = {456, "홍길동", 3.5},
  14. s3 = {789, "멍청이", 2.3};
  15.  
  16. struct student max;
  17.  
  18. max = s1;
  19. if (s2.grade > max.grade) max = s2;
  20. if (s3.grade > max.grade) max = s3;
  21.  
  22. printf("학번: %d\n", max.id);
  23. printf("이름: %s\n", max.name);
  24. printf("학점: %.1lf\n", max.grade);
  25.  
  26. return 0;
  27. }

구조체는 보통 형 선언을 먼저 한 후에 구조체 변수 선언과 초기화를 하지만

struct student
{
    int id;
    char name[20];
    double grade;
} s1 = {123, "장세민", 4.5};

다음과 같이 3가지를 동시에 하는 것도 가능하다.

이때 구조체 선언을 함수 밖에서 하면 함께 선언되는 변수가 전역 변수가 되므로
초기화하지 않을 경우 모든 멤버는 0으로 자동 초기화된다.



함수 매개변수에 사용

구조체 변수는 대입 연산이 가능하므로 함수의 인수로 주거나
함수에서 여러 개의 값을 구조체로 묶어 동시에 반환하는 것이 가능하다.

예를 들어, 두 변수의 값을 바꾸는 함수는 포인터가 필요하지만,
구조체 변수를 사용해 값을 주고받으면 포인터 없이 값을 바꾸는 함수를 만들 수 있다.

레쓰고

  1. #include <stdio.h>
  2.  
  3. struct vision
  4. {
  5. double left;
  6. double right;
  7. };
  8.  
  9. struct vision exchange(struct vision robot);
  10.  
  11. int main(void)
  12. {
  13. struct vision robot;
  14.  
  15. printf("시력 입력: ");
  16. scanf("%lf%lf", &(robot.left), &(robot.right));
  17. robot = exchange(robot);
  18. printf("바뀐 시력: %.1lf %.1lf\n", robot.left, robot.right);
  19.  
  20. return 0;
  21. }
  22.  
  23. struct vision exchange(struct vision robot)
  24. {
  25. double temp;
  26.  
  27. temp = robot.left;
  28. robot.left = robot.right;
  29. robot.right = temp;
  30.  
  31. return robot;
  32. }

함수를 호출할 때 인수로 구조체 변수를 사용하면 멤버들의 값을 한꺼번에 함수에 줄 수 있다.
심지어 멤버가 배열이라도 모든 배열 요소의 값이 함수에 복사된다.

이러한 전달 방식은 구조체 변수를 반환할 때도 똑같이 적용되므로
함수가 여러 개의 값을 한 번에 반환할 수 있다.

profile
분석하는 남자 💻

0개의 댓글