Skip to Content
💻 코리아IT아카데미 신촌 - 프로그래밍 학습 자료

Topic 2: 구조체 배열과 포인터 📚➡️

🎯 학습 목표

  • 구조체 배열을 생성하고 활용할 수 있다
  • 포인터를 사용해서 구조체에 접근하는 방법을 익힐 수 있다
  • 함수 매개변수로 구조체를 전달하는 방법을 이해할 수 있다
  • 동적 메모리 할당을 통한 구조체 관리를 할 수 있다

🤔 왜 구조체 배열이 필요할까요?

학급의 모든 학생 정보를 관리한다고 생각해보세요. 한 명씩 변수를 만든다면…

// ❌ 비효율적인 방법 Student student1 = {"김철수", 20, "2024001", 3.8}; Student student2 = {"이영희", 19, "2024002", 4.2}; Student student3 = {"박민수", 21, "2024003", 3.5}; // ... 30명이라면 30개의 변수? 😱

이런 문제를 구조체 배열로 해결할 수 있습니다! 🎯

📋 구조체 배열의 기초

구조체 배열 선언과 초기화

#include <iostream> #include <string> using namespace std; struct Student { string name; int age; string id; double grade; }; int main() { // 방법 1: 배열 선언 후 개별 초기화 Student students[3]; students[0] = {"김철수", 20, "2024001", 3.8}; students[1] = {"이영희", 19, "2024002", 4.2}; students[2] = {"박민수", 21, "2024003", 3.5}; // 방법 2: 선언과 동시에 초기화 Student class1[3] = { {"김철수", 20, "2024001", 3.8}, {"이영희", 19, "2024002", 4.2}, {"박민수", 21, "2024003", 3.5} }; // 배열 크기 자동 결정 Student class2[] = { {"최지우", 22, "2024004", 3.9}, {"한민정", 20, "2024005", 4.0} }; // 크기가 자동으로 2가 됨 return 0; }

🔄 구조체 배열 순회하기

기본적인 for 반복문

#include <iostream> #include <string> using namespace std; struct Product { string name; int price; int stock; string category; }; int main() { Product inventory[] = { {"노트북", 1200000, 5, "전자제품"}, {"마우스", 50000, 20, "전자제품"}, {"키보드", 80000, 15, "전자제품"}, {"의자", 300000, 8, "가구"}, {"책상", 500000, 3, "가구"} }; int arraySize = sizeof(inventory) / sizeof(inventory[0]); cout << "📦 === 재고 현황 === 📦" << endl; cout << "--------------------------------------" << endl; for (int i = 0; i < arraySize; i++) { cout << "상품명: " << inventory[i].name << endl; cout << "가격: " << inventory[i].price << "원" << endl; cout << "재고: " << inventory[i].stock << "개" << endl; cout << "카테고리: " << inventory[i].category << endl; cout << "--------------------------------------" << endl; } return 0; }

범위 기반 for 반복문 (C++11)

#include <iostream> #include <string> #include <vector> using namespace std; struct Book { string title; string author; int pages; double rating; }; int main() { vector<Book> library = { {"해리포터", "J.K. 롤링", 400, 4.8}, {"반지의 제왕", "톨킨", 500, 4.9}, {"1984", "조지 오웰", 300, 4.7}, {"위대한 개츠비", "피츠제럴드", 250, 4.5} }; cout << "📚 === 도서관 목록 === 📚" << endl << endl; // 범위 기반 for문 - 읽기 전용 for (const auto& book : library) { cout << "📖 " << book.title << endl; cout << " 저자: " << book.author << endl; cout << " 페이지: " << book.pages << "페이지" << endl; cout << " 평점: " << book.rating << "/5.0" << endl; // 평점에 따른 별표 표시 cout << " 별점: "; int stars = (int)(book.rating); for (int i = 0; i < stars; i++) { cout << "⭐"; } cout << endl << endl; } // 평점이 4.7 이상인 책만 필터링 cout << "🏆 === 추천 도서 (평점 4.7 이상) === 🏆" << endl; for (const auto& book : library) { if (book.rating >= 4.7) { cout << "- " << book.title << " (평점: " << book.rating << ")" << endl; } } return 0; }

🎯 포인터를 사용한 구조체 접근

구조체 포인터 기본 사용법

#include <iostream> #include <string> using namespace std; struct Car { string brand; string model; int year; double price; }; // 구조체를 출력하는 함수 (포인터 매개변수) void printCarInfo(const Car* carPtr) { // 포인터를 통한 멤버 접근: -> 연산자 사용 cout << "🚗 === 자동차 정보 === 🚗" << endl; cout << "브랜드: " << carPtr->brand << endl; cout << "모델: " << carPtr->model << endl; cout << "연식: " << carPtr->year << "년" << endl; cout << "가격: " << carPtr->price << "만원" << endl; cout << "========================" << endl; } // 구조체를 수정하는 함수 (포인터 매개변수) void updateCarPrice(Car* carPtr, double newPrice) { carPtr->price = newPrice; cout << "💰 " << carPtr->brand << " " << carPtr->model << "의 가격이 " << newPrice << "만원으로 변경되었습니다!" << endl; } int main() { Car myCar = {"현대", "아반떼", 2023, 2500.0}; // 구조체 포인터 선언 Car* carPtr = &myCar; // 포인터를 통한 접근 방법 비교 cout << "일반 접근: " << myCar.brand << endl; // 점(.) 연산자 cout << "포인터 접근: " << carPtr->brand << endl; // 화살표(->) 연산자 cout << "포인터 접근 (다른 방식): " << (*carPtr).brand << endl; // 역참조 후 점 연산자 cout << endl; // 함수 호출 printCarInfo(carPtr); updateCarPrice(carPtr, 2800.0); printCarInfo(carPtr); return 0; }

🎯 포인터로 구조체 다루기: 핵심 개념

왜 포인터를 사용하는가?

구조체를 함수에 전달할 때 두 가지 방법이 있습니다:

struct Player { string name; int level; int experience; }; // 방법 1: 값으로 전달 (복사) void printPlayer1(Player p) { // 전체 구조체를 복사! cout << p.name << ": 레벨 " << p.level << endl; } // 방법 2: 포인터로 전달 (주소) void printPlayer2(Player* p) { // 주소만 전달! cout << p->name << ": 레벨 " << p->level << endl; }

포인터의 장점

메모리 효율성: 큰 구조체도 주소(8바이트)만 전달
🔧 수정 가능: 원본 데이터를 직접 수정할 수 있음
📈 성능 향상: 복사 시간이 없어서 빠름

화살표 연산자 (->)

포인터로 구조체 멤버에 접근할 때 사용하는 특별한 연산자입니다:

Player player = {"김철수", 5, 100}; Player* ptr = &player; // 방법 1: 화살표 연산자 (권장) ptr->name = "이영희"; ptr->level = 10; // 방법 2: 역참조 + 점 연산자 (복잡함) (*ptr).name = "이영희"; (*ptr).level = 10;

실습: 레벨업 시스템

#include <iostream> using namespace std; struct Character { string name; int level; int exp; }; // 여기에 코드를 작성하세요 // 1. levelUp 함수를 만들어보세요 (포인터 매개변수 사용) // 2. 레벨을 1 증가시키고 경험치를 0으로 초기화 void levelUp(Character* c) { // 코드를 작성하세요 } int main() { Character hero = {"용사", 1, 100}; cout << "레벨업 전: " << hero.name << " 레벨 " << hero.level << endl; levelUp(&hero); // 주소를 전달 cout << "레벨업 후: " << hero.name << " 레벨 " << hero.level << endl; return 0; }

구조체 배열과 포인터

Player team[3] = { {"전사", 5, 200}, {"마법사", 3, 150}, {"궁수", 4, 180} }; // 배열의 각 원소에 접근 for (int i = 0; i < 3; i++) { cout << team[i].name << endl; // 직접 접근 cout << (team + i)->name << endl; // 포인터 연산 } // 포인터로 함수에 전달 void processTeam(Player* players, int size) { for (int i = 0; i < size; i++) { players[i].level++; // 모든 팀원 레벨업! } }

🤔 흔한 실수와 해결방법

null 포인터 접근

Player* p = nullptr; cout << p->name; // 💥 프로그램 크래시!

안전한 접근

if (p != nullptr) { cout << p->name; // 안전함 }

점 연산자와 화살표 연산자 혼동

Player player; Player* ptr = &player; player->name; // ❌ 변수에 화살표 사용 ptr.name; // ❌ 포인터에 점 사용

올바른 사용

player.name; // ✅ 변수에는 점 ptr->name; // ✅ 포인터에는 화살표

💾 동적 메모리와 구조체

동적 할당의 필요성

정적 배열의 한계:

struct Student { string name; int score; }; Student class1[30]; // 고정 크기: 30명만 가능 Student class2[50]; // 또 다른 고정 크기

동적 할당의 해결책:

int studentCount; cout << "학생 수를 입력하세요: "; cin >> studentCount; // 실행 중에 크기 결정! Student* students = new Student[studentCount];

new와 delete 사용법

// 1. 단일 구조체 동적 할당 Student* student = new Student; student->name = "김철수"; student->score = 95; delete student; // 메모리 해제 // 2. 구조체 배열 동적 할당 Student* students = new Student[count]; students[0].name = "이영희"; students[0].score = 88; delete[] students; // 배열 메모리 해제

🚨 메모리 관리 원칙

1. 짝을 맞춰라!

newdelete // 단일 객체 new[] ↔ delete[] // 배열

2. 해제 후 사용 금지

Student* s = new Student; delete s; s->name = "금지!"; // ❌ 이미 해제된 메모리 접근 s = nullptr; // ✅ 안전을 위해 nullptr 설정

3. 중복 해제 금지

delete s; delete s; // ❌ 두 번 해제하면 프로그램 크래시!

실습: 동적 점수 관리

#include <iostream> using namespace std; struct Score { string subject; int points; }; int main() { int subjectCount; cout << "과목 수를 입력하세요: "; cin >> subjectCount; // 여기에 코드를 작성하세요 // 1. Score 배열을 동적 할당하세요 // 2. 각 과목 정보를 입력받으세요 // 3. 모든 과목을 출력하세요 // 4. 메모리를 해제하세요 return 0; }

스마트 포인터 미리보기

C++에서는 자동 메모리 관리를 위한 스마트 포인터도 제공합니다:

#include <memory> // 자동으로 메모리 해제됨! unique_ptr<Student> student = make_unique<Student>(); student->name = "자동 관리"; // delete 불필요 - 자동으로 해제됨

하지만 이건 고급 내용이니 나중에 배우게 됩니다! 지금은 new/delete의 원리를 확실히 이해하는 것이 중요해요.

🔧 함수와 구조체 배열

다양한 전달 방법

#include <iostream> #include <string> using namespace std; struct Score { string subject; int points; double weight; // 가중치 }; // 1. 값으로 전달 (비효율적) void printScoreByValue(Score score) { cout << "과목: " << score.subject << ", 점수: " << score.points << endl; } // 2. 참조로 전달 (효율적, 수정 가능) void updateScore(Score& score, int newPoints) { score.points = newPoints; cout << score.subject << " 점수가 " << newPoints << "점으로 업데이트되었습니다!" << endl; } // 3. 상수 참조로 전달 (효율적, 읽기 전용) void printScoreByConstRef(const Score& score) { cout << "📚 " << score.subject << ": " << score.points << "점 (가중치: " << score.weight << ")" << endl; } // 4. 배열 전달 (포인터로 전달됨) double calculateWeightedAverage(const Score scores[], int count) { double totalWeightedScore = 0; double totalWeight = 0; for (int i = 0; i < count; i++) { totalWeightedScore += scores[i].points * scores[i].weight; totalWeight += scores[i].weight; } return totalWeightedScore / totalWeight; } // 5. vector 참조로 전달 void sortScoresByPoints(vector<Score>& scores) { // 간단한 버블 정렬 for (int i = 0; i < scores.size() - 1; i++) { for (int j = 0; j < scores.size() - 1 - i; j++) { if (scores[j].points < scores[j + 1].points) { Score temp = scores[j]; scores[j] = scores[j + 1]; scores[j + 1] = temp; } } } } int main() { // 성적 배열 생성 Score myScores[] = { {"수학", 85, 1.2}, {"영어", 92, 1.0}, {"과학", 78, 1.1}, {"국어", 88, 1.0}, {"사회", 95, 0.8} }; int scoreCount = sizeof(myScores) / sizeof(myScores[0]); cout << "📊 === 성적 관리 시스템 === 📊" << endl << endl; // 모든 성적 출력 cout << "현재 성적:" << endl; for (int i = 0; i < scoreCount; i++) { printScoreByConstRef(myScores[i]); } // 가중 평균 계산 double average = calculateWeightedAverage(myScores, scoreCount); cout << "\n📈 가중 평균: " << average << "점" << endl; // 점수 수정 cout << "\n=== 점수 수정 ===" << endl; updateScore(myScores[0], 90); // 수학 점수 수정 // vector로 복사해서 정렬 vector<Score> scoreVector(myScores, myScores + scoreCount); sortScoresByPoints(scoreVector); cout << "\n🏆 === 점수 순위 === 🏆" << endl; for (int i = 0; i < scoreVector.size(); i++) { cout << (i + 1) << "등: "; printScoreByConstRef(scoreVector[i]); } return 0; }

💡 성능과 메모리 최적화 팁

구조체 크기 최적화

#include <iostream> using namespace std; // ❌ 비효율적인 구조체 (패딩 때문에 메모리 낭비) struct BadStruct { char a; // 1 byte int b; // 4 bytes (3 bytes 패딩 발생) char c; // 1 byte (3 bytes 패딩 발생) }; // ✅ 효율적인 구조체 (패딩 최소화) struct GoodStruct { int b; // 4 bytes char a; // 1 byte char c; // 1 byte (2 bytes 패딩) }; // 더 나은 방법: 관련 데이터끼리 묶기 struct OptimalStruct { int numbers[10]; // 40 bytes char flags[4]; // 4 bytes // 총 44 bytes, 패딩 최소화 }; int main() { cout << "구조체 크기 비교:" << endl; cout << "BadStruct: " << sizeof(BadStruct) << " bytes" << endl; cout << "GoodStruct: " << sizeof(GoodStruct) << " bytes" << endl; cout << "OptimalStruct: " << sizeof(OptimalStruct) << " bytes" << endl; return 0; }

🧪 퀴즈 타임!

🤔 퀴즈 1: 구조체 배열에서 특정 조건의 요소 개수는?

struct Item { string name; int price; bool available; }; Item items[] = { {"사과", 1000, true}, {"바나나", 2000, false}, {"오렌지", 1500, true}, {"포도", 3000, true} }; // 가격이 2000원 미만이고 구매 가능한 아이템은 몇 개?

정답: 2개 (사과, 오렌지)

조건을 만족하는 아이템: 사과(1000원, available), 오렌지(1500원, available)

🤔 퀴즈 2: 포인터를 통한 구조체 접근에서 올바른 문법은?

struct Point { int x, y; }; Point p = {10, 20}; Point* ptr = &p;

다음 중 올바른 접근 방법들은?

  1. ptr->x
  2. (*ptr).x
  3. ptr.x
  4. &ptr->x

정답: 1번과 2번이 올바릅니다.

  • ptr->x: 화살표 연산자 (가장 일반적)
  • (*ptr).x: 역참조 후 점 연산자
  • ptr.x: ❌ 잘못된 문법
  • &ptr->x: x의 주소를 가져오는 것 (값 접근과는 다름)

🤔 퀴즈 3: 다음 코드의 출력 결과는?

struct Data { int value; }; void modify1(Data d) { d.value = 100; } void modify2(Data* d) { d->value = 200; } int main() { Data data = {10}; modify1(data); cout << data.value << " "; modify2(&data); cout << data.value << endl; return 0; }

정답: 10 200

  • modify1은 값으로 전달받으므로 원본이 변경되지 않음 (10 유지)
  • modify2는 포인터로 전달받으므로 원본이 변경됨 (200으로 변경)

🎯 연습 문제

연습 1: 영화 평점 시스템

영화 정보(제목, 감독, 연도, 평점)를 담는 구조체 배열을 만들고, 평점 순으로 정렬하여 상위 3개 영화를 출력하는 프로그램을 작성하세요.

💡 힌트

  • Movie 구조체를 정의하세요
  • 평점을 기준으로 내림차순 정렬하세요
  • 상위 3개만 출력하는 반복문을 작성하세요

연습 2: 학생 성적 분석기

학생들의 성적 정보를 동적으로 입력받고, 각 과목별 평균과 학생별 총점을 계산하는 프로그램을 포인터를 사용하여 작성하세요.

💡 힌트

  • 동적 메모리 할당을 사용하세요
  • 함수 매개변수로 포인터를 활용하세요
  • 과목별/학생별 계산 함수를 분리하세요

연습 3: 온라인 쇼핑몰 장바구니

상품 정보와 장바구니 기능을 구현하여, 상품 추가/삭제, 총 금액 계산, 할인 적용 등의 기능을 포인터와 구조체 배열로 구현하세요.

💡 힌트

  • Product와 CartItem 구조체를 정의하세요
  • vector를 사용해서 동적 장바구니를 구현하세요
  • 포인터를 사용해서 상품 정보에 효율적으로 접근하세요

🎉 마무리

구조체 배열과 포인터는 효율적인 데이터 관리의 핵심입니다!

오늘 배운 내용:

  • 📚 구조체 배열의 생성과 초기화
  • 🔄 다양한 반복문을 통한 배열 순회
  • 🎯 포인터를 통한 구조체 접근 (-> 연산자)
  • 💾 동적 메모리 할당과 구조체
  • 🔧 함수 매개변수로 구조체 전달하기
  • ⚡ 성능 최적화 방법

다음 단계:

  • Topic 3에서는 열거형(enum)과 공용체(union)를 학습합니다
  • 더 고급 데이터 타입들을 다루게 됩니다
  • 복합적인 자료구조 설계를 배우게 됩니다

이제 구조체를 배열로 관리하고 포인터로 효율적으로 접근하는 방법을 익혔습니다! 💪

Last updated on