3단원 — 구조체
서로 관련된 여러 값을 **하나의 자료형**으로 묶는 도구가 구조체입니다. "이름은 string, 나이는 int, 점수는 double" 같은 묶음을 한 변수로 다룹니다.
이 강의에서 배우는 것
- 1`struct`로 새 자료형을 정의한다.
- 2`typedef`로 별칭을 부여해 사용성을 높인다.
- 3구조체 포인터에서 `->` 연산자를 쓴다.
- 4구조체 배열로 레코드 모음을 관리한다.
왜 구조체가 필요한가? — 변수가 흩어지는 문제
학생 정보를 변수 따로따로 만들면:
char s1_name[32]; int s1_age; double s1_score;
char s2_name[32]; int s2_age; double s2_score;
/* ... 학생 100명이면? */배열로 묶어도 자료형이 다르면 한 배열에 못 담습니다. 구조체는 **자료형이 다른 멤버**들을 한 묶음으로 만듭니다.
typedef struct {
char name[32];
int age;
double score;
} Student;
Student arr[100]; // 학생 100명을 한 배열에핵심 개념
1) 정의와 초기화
struct Point {
int x;
int y;
};
struct Point p1 = {3, 4};
struct Point p2 = {.x = 1, .y = 2}; // 지정 초기화 (C99)2) `typedef`로 깔끔하게
typedef struct {
double x;
double y;
} Point;
Point p = {10, 20}; // struct 키워드 없이 사용3) 멤버 접근 — `.` vs `->`
Point p = {3, 4};
Point *ptr = &p;
p.x; // 점(.) 연산자: 구조체 변수에서 멤버 접근
ptr->x; // 화살표(->): 포인터에서 멤버 접근. (*ptr).x 의 단축 p (값) ptr (포인터)
┌──────┐ ┌──────┐
│ x: 3 │ ◄─────────│ &p │
│ y: 4 │ └──────┘
└──────┘
p.x = 3 ptr->x = 34) 구조체 배열
typedef struct { char name[32]; int score; } Student;
Student class_[3] = {
{"Kim", 90}, {"Lee", 85}, {"Park", 78},
};
class_[1].score; // 85각 원소는 독립된 구조체. 멤버 접근은 `arr[i].멤버`.
5) 구조체와 함수
큰 구조체는 값으로 넘기면 통째로 복사되어 비효율적입니다. **포인터로 전달**이 관용:
void birthday(Person *p) { p->age++; }
birthday(&someone);예제로 보기
예제 1 — `ex01_basic.c` : Point 구조체
struct Point p1 = {3, 4};
struct Point p2 = {.x = 1, .y = 2};
p1.x = 10;**실행 결과**
p1 = (3, 4)
p2 = (1, 2)
p1.x 변경 후 = (10, 4)핵심: 멤버는 일반 변수처럼 읽고 쓸 수 있습니다.
예제 2 — `ex02_typedef.c` : 두 점 사이 거리
typedef struct { double x, y; } Point;
double distance(Point a, Point b) {
return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}**실행 결과**
거리: 5.00핵심: 구조체를 **함수 인자/반환값**으로 그대로 쓸 수 있습니다 (값 전달).
예제 3 — `ex03_struct_array.c` : 학생 명단 평균
Student class_[] = {
{"Kim", 90}, {"Lee", 85}, {"Park", 78}, {"Choi", 92},
};**실행 결과**
Kim : 90
Lee : 85
Park : 78
Choi : 92
평균: 86.25핵심: 구조체 배열 + 반복문은 작은 데이터베이스의 시작.
예제 4 — `ex04_pointer.c` : 포인터로 멤버 수정
typedef struct { char name[32]; int age; } Person;
void birthday(Person *p) { p->age += 1; }**실행 결과**
생일 전: 홍길동, 20세
생일 후: 홍길동, 21세핵심: 함수가 호출자의 구조체를 바꾸려면 포인터를 전달해야 합니다.
다른 시각으로 보기 — "이력서" 비유
┌──────── 이력서(struct Person) ────────┐
│ 이름: 홍길동 │
│ 나이: 21 │
│ 키: 175.5 │
│ 학생증 사진(...) │
└─────────────────────────────────────┘이력서 한 장이 한 사람(`Person` 구조체 한 개) 입니다. "이력서 100장 = `Person` 배열 100개"가 됩니다.
`.` vs `->`는 사실 같은 일을 합니다.
ptr->x ≡ (*ptr).x // 완전히 동치자주 하는 실수
- **`struct` 누락**: `Point p;` 만 쓰면 typedef 안 했을 때 컴파일 오류.
→ 정의에 `typedef`를 함께 쓰는 습관.
- **구조체 비교**: `if (p1 == p2)` 는 컴파일 오류. 멤버끼리 비교해야 함.
- **큰 구조체 값 전달**: 매 호출마다 통째로 복사 → 포인터 전달 권장.
- **포인터에 `.` 사용**: `ptr.x`는 오류. `ptr->x` 또는 `(*ptr).x`.
- **패딩(padding)**: `sizeof(Student)`가 멤버 합보다 클 수 있음 (메모리 정렬 때문).
정리
- 구조체는 자료형이 다른 멤버들을 한 자료형으로 묶는다.
- `typedef`로 별칭을 만들면 사용성이 좋아진다.
- 구조체 변수는 `.`, 포인터는 `->`로 멤버에 접근.
- 큰 구조체는 포인터로 함수에 전달해 복사를 피한다.
- 구조체 배열은 작은 데이터베이스의 자연스러운 표현.
직접 해 보기
cd src
gcc -std=c11 -Wall -o ex01 ex01_basic.c && ./ex01
gcc -std=c11 -Wall -o ex02 ex02_typedef.c -lm && ./ex02
gcc -std=c11 -Wall -o ex03 ex03_struct_array.c && ./ex03
gcc -std=c11 -Wall -o ex04 ex04_pointer.c && ./ex04응용:
- `ex03`에서 평균이 가장 높은 학생의 이름을 함께 출력해 보세요.
- `ex04`에서 `birthday`를 `void birthday(Person p)` (값 전달)로 바꾸면 결과가 어떻게 달라질까요?
💻 예제 (examples)
실제로 컴파일·실행해 결과를 확인할 수 있는 예제입니다.
#include <stdio.h>
struct Point {
int x;
int y;
};
int main(void) {
struct Point p1 = {3, 4};
struct Point p2 = {.x = 1, .y = 2};
printf("p1 = (%d, %d)\n", p1.x, p1.y);
printf("p2 = (%d, %d)\n", p2.x, p2.y);
p1.x = 10;
printf("p1.x 변경 후 = (%d, %d)\n", p1.x, p1.y);
return 0;
}
p1 = (3, 4)
p2 = (1, 2)
p1.x 변경 후 = (10, 4)#include <stdio.h>
#include <math.h>
typedef struct {
double x;
double y;
} Point;
double distance(Point a, Point b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
int main(void) {
Point p = {0, 0};
Point q = {3, 4};
printf("거리: %.2f\n", distance(p, q)); /* 5.00 */
return 0;
}
거리: 5.00#include <stdio.h>
typedef struct {
char name[32];
int score;
} Student;
int main(void) {
Student class_[] = {
{"Kim", 90},
{"Lee", 85},
{"Park", 78},
{"Choi", 92},
};
int n = (int)(sizeof(class_) / sizeof(class_[0]));
int sum = 0;
for (int i = 0; i < n; i++) {
printf("%-6s : %d\n", class_[i].name, class_[i].score);
sum += class_[i].score;
}
printf("평균: %.2f\n", (double)sum / n);
return 0;
}
Kim : 90
Lee : 85
Park : 78
Choi : 92
평균: 86.25#include <stdio.h>
typedef struct {
char name[32];
int age;
} Person;
void birthday(Person *p) {
p->age += 1; /* 화살표로 멤버 접근 */
}
int main(void) {
Person p = {"홍길동", 20};
printf("생일 전: %s, %d세\n", p.name, p.age);
birthday(&p);
printf("생일 후: %s, %d세\n", p.name, p.age);
return 0;
}
생일 전: 홍길동, 20세
생일 후: 홍길동, 21세📝 과제 (exercises)
직접 풀어보고, 막힐 때 정답을 펼쳐 비교해보세요.
문제 1 (hw01.c)
목표: 2D 벡터 구조체 `Vec2 { double x, y; }`를 정의하고 `Vec2 add(Vec2 a, Vec2 b)`, `double dot(Vec2 a, Vec2 b)` 함수를 구현해 사용 예를 출력하세요.
- 파일명: hw01.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
typedef struct {
double x;
double y;
} Vec2;
Vec2 add(Vec2 a, Vec2 b) {
Vec2 r = {a.x + b.x, a.y + b.y};
return r;
}
double dot(Vec2 a, Vec2 b) {
return a.x * b.x + a.y * b.y;
}
int main(void) {
Vec2 a = {1, 2}, b = {3, 4};
Vec2 c = add(a, b);
printf("a + b = (%.1f, %.1f)\n", c.x, c.y);
printf("a . b = %.1f\n", dot(a, b));
return 0;
}
문제 2 (hw02.c)
목표: 학생 명단(이름, 국어, 영어, 수학)을 5명 분량으로 만들고, 각 학생의 **평균**을 출력 후, **평균이 가장 높은 학생**의 이름을 출력하세요.
- 파일명: hw02.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
typedef struct {
char name[32];
int kor, eng, math;
} Student;
int main(void) {
Student arr[5] = {
{"Kim", 90, 85, 92},
{"Lee", 78, 88, 70},
{"Park", 95, 91, 89},
{"Choi", 60, 70, 65},
{"Han", 88, 76, 82},
};
int top_idx = 0;
double top_avg = 0;
for (int i = 0; i < 5; i++) {
double avg = (arr[i].kor + arr[i].eng + arr[i].math) / 3.0;
printf("%-6s 평균 %.2f\n", arr[i].name, avg);
if (avg > top_avg) { top_avg = avg; top_idx = i; }
}
printf("\n최고 평균: %s (%.2f)\n", arr[top_idx].name, top_avg);
return 0;
}
문제 3 (hw03.c)
목표: 직사각형 구조체 `Rect { int w, h; }`에 대해 `int area(const Rect *r)`, `int perimeter(const Rect *r)`를 구현하세요. **구조체 포인터** 사용을 연습합니다.
- 파일명: hw03.c
▶정답 코드 펼치기 / 접기
#include <stdio.h>
typedef struct {
int w;
int h;
} Rect;
int area(const Rect *r) { return r->w * r->h; }
int perimeter(const Rect *r) { return 2 * (r->w + r->h); }
int main(void) {
Rect r = {5, 3};
printf("넓이: %d\n", area(&r));
printf("둘레: %d\n", perimeter(&r));
return 0;
}