C 언어 실전: 종합 프로젝트 1 (관리 프로그램)

7 minute read

안녕하세요! 여러분의 코딩 구원자, 재준봇입니다!

자, 드디어 왔습니다. 여러분이 그토록 기다리고 기다리셨을(혹은 두려움에 떨고 계실) 운명의 시간이 왔어요. 바로 C 언어의 꽃, 종합 프로젝트 시간입니다! 지금까지 우리는 변수, 조건문, 반복문, 배열, 포인터, 구조체까지 하나하나 조각들을 모아왔잖아요?

비유를 하자면, 지금까지는 망치질 하는 법, 톱질 하는 법, 못 박는 법을 따로 배운 거예요. 그런데 이제는 그 도구들을 다 가져와서 진짜로 ‘살 수 있는 집’을 지어볼 차례인 거죠. 오늘 우리가 만들 것은 바로 ‘관리 프로그램’입니다.

처음 하면 막막하겠지만 걱정 마세요. 제가 옆에서 하나하나 짚어드릴게요. 이 강의만 제대로 따라오시면, 여러분은 이제 단순한 코딩 학습자가 아니라 진짜 ‘프로그램 제작자’가 되는 겁니다. 자, 심호흡하시고 바로 달려가 봅시다!

20강: C 언어 실전: 종합 프로젝트 1 (관리 프로그램)

1. 관리 프로그램이란 대체 무엇인가?

우리가 흔히 쓰는 엑셀이나 메모장, 혹은 스마트폰의 연락처 앱을 생각해보세요. 이름 입력하고, 저장하고, 나중에 다시 찾아보고, 마음에 안 들면 지우죠? 이게 바로 ‘관리 프로그램’의 핵심입니다.

컴퓨터 공학에서는 이를 CRUD라고 불러요.

  • Create (생성): 데이터를 새로 만드는 것
  • Read (읽기): 저장된 데이터를 확인하는 것
  • Update (수정): 기존 데이터를 바꾸는 것
  • Delete (삭제): 필요 없는 데이터를 지우는 것

이 4가지 기능만 완벽하게 구현하면 세상의 모든 관리 프로그램의 뼈대를 완성한 것이나 다름없습니다. 진짜 신기하죠? 단순해 보이지만 여기서 구조체와 배열, 포인터의 정수가 모두 들어갑니다.


2. 기초 공사: 데이터의 그릇 만들기 (구조체)

집을 짓기 전에 설계도가 필요하듯, 프로그램에서는 어떤 데이터를 저장할지 정해야 합니다. 학생 관리 프로그램을 만든다고 가정해볼게요. 학생 한 명에게는 이름, 학번, 성적이 필요하겠죠? 이걸 각각 따로 만들면 나중에 데이터가 꼬여서 대참사가 일어납니다. 그래서 ‘구조체’라는 바구니에 한꺼번에 담아야 합니다.

여기서 구조체를 정의하는 3가지 방법을 보여드릴게요. 상황에 따라 골라 쓰는 재미가 있습니다.

예제 1: 구조체 정의의 3가지 스타일

#include <stdio.h>

// 방법 1: 가장 기본적인 구조체 정의
struct Student {
    char name[20];
    int id;
    float score;
};

// 방법 2: 구조체 안에 또 다른 구조체를 넣는 중첩 구조체
struct Date {
    int year;
    int month;
    int day;
};

struct StudentDetail {
    char name[20];
    struct Date birthday; // 구조체 내부에 구조체 포함!
};

// 방법 3: typedef를 사용하여 별칭을 만드는 방법 (실무에서 가장 선호!)
typedef struct {
    char name[20];
    int id;
    float score;
} Student; // 이제 'struct Student'라고 안 쓰고 그냥 'Student'라고만 써도 됩니다.

int main() {
    // 방법 1 사용
    struct Student s1 = {"재준", 202401, 95.5f};
    
    // 방법 2 사용
    struct StudentDetail sd1 = {"코딩초보", {2000, 1, 1}};
    
    // 방법 3 사용
    Student s2 = {"열공이", 202402, 100.0f};

    printf("방법 3의 학생 이름: %s\n", s2.name);
    return 0;
}

코드 뜯어보기

  • 방법 1: 가장 정석적인 방법입니다. 하지만 사용할 때마다 앞에 struct라는 글자를 계속 붙여야 해서 손가락이 아픕니다.
  • 방법 2: 현실 세계의 데이터는 복잡합니다. 생년월일처럼 묶음 데이터가 또 필요한 경우, 구조체 안에 구조체를 넣어서 체계적으로 관리할 수 있습니다.
  • 방법 3: typedef는 ‘Type Definition’의 약자입니다. 쉽게 말해 “앞으로 이 복잡한 구조체 이름을 그냥 Student라고 부르기로 약속하자!”라고 선언하는 거예요. 코드가 훨씬 깔끔해지기 때문에 실무에서는 무조건 이 방식을 씁니다. 이거 모르면 코드가 지저분해져서 큰일 납니다!

3. 메인 루프: 프로그램의 심장 만들기 (반복문)

관리 프로그램은 사용자가 ‘종료’를 누르기 전까지는 계속 실행되어야 합니다. 메뉴를 보여주고, 입력을 받고, 기능을 수행하고, 다시 메뉴로 돌아오는 과정이 무한히 반복되어야 하죠.

이 반복문을 구현하는 3가지 대표적인 방법을 알려드릴게요.

예제 2: 프로그램 루프 구현의 3가지 방식

#include <stdio.h>
#include <stdbool.h>

int main() {
    int choice;

    // 방식 1: while(1) 무한 루프와 break 조합
    printf("--- 방식 1: while(1) ---\n");
    while (1) {
        printf("1. 실행 / 0. 종료: ");
        scanf("%d", &choice);
        if (choice == 0) break; // 0을 입력하면 탈출!
        printf("기능을 수행합니다!\n");
    }

    // 방식 2: 조건 변수를 이용한 while 루프
    printf("\n--- 방식 2: 조건 변수 ---\n");
    bool isRunning = true;
    while (isRunning) {
        printf("1. 실행 / 0. 종료: ");
        scanf("%d", &choice);
        if (choice == 0) isRunning = false; // 변수 값을 바꿔서 루프 종료
        else printf("기능을 수행합니다!\n");
    }

    // 방식 3: do-while 루프 (최소 한 번은 무조건 실행)
    printf("\n--- 방식 3: do-while ---\n");
    do {
        printf("1. 실행 / 0. 종료: ");
        scanf("%d", &choice);
        if (choice != 0) printf("기능을 수행합니다!\n");
    } while (choice != 0);

    return 0;
}

코드 뜯어보기

  • 방식 1: 가장 직관적입니다. “일단 무조건 돌려! 그러다 내가 break라고 하면 멈춰!”라는 식이죠. 빠르게 짜기 좋습니다.
  • 방식 2: isRunning 같은 상태 변수를 사용합니다. 나중에 프로그램 규모가 커져서 종료 조건이 여러 개가 될 때(예: 관리자가 강제 종료하거나, 시간이 초과되었거나 등) 매우 유리합니다.
  • 방식 3: do-while은 일단 몸통을 한 번 실행하고 나서 조건을 검사합니다. 관리 프로그램은 무조건 메뉴를 먼저 보여줘야 하므로 논리적으로 가장 적합한 방식입니다.

4. 메뉴 선택: 갈림길 만들기 (조건문)

사용자가 1번을 누르면 등록, 2번을 누르면 조회, 3번을 누르면 삭제를 해야 합니다. 이 선택지를 처리하는 방법입니다.

예제 3: 메뉴 처리의 3가지 전략

#include <stdio.h>

int main() {
    int menu = 2; // 예시로 2번을 선택했다고 가정

    // 전략 1: if-else if 문 (단순하고 명확함)
    if (menu == 1) {
        printf("등록 메뉴입니다.\n");
    } else if (menu == 2) {
        printf("조회 메뉴입니다.\n");
    } else if (menu == 3) {
        printf("삭제 메뉴입니다.\n");
    } else {
        printf("잘못된 입력입니다.\n");
    }

    // 전략 2: switch-case 문 (가독성이 최강!)
    switch (menu) {
        case 1:
            printf("등록 메뉴입니다.\n");
            break;
        case 2:
            printf("조회 메뉴입니다.\n");
            break;
        case 3:
            printf("삭제 메뉴입니다.\n");
            break;
        default:
            printf("잘못된 입력입니다.\n");
    }

    // 전략 3: 함수 포인터 배열 (고급 기술, 실무 끝판왕)
    // 초보자분들은 "아, 이런 게 있구나" 정도로만 보세요!
    // 각 숫자에 맞는 함수를 미리 연결해두고 바로 호출하는 방식입니다.
    // (여기서는 개념만 설명하고 코드는 생략하겠습니다. 너무 어려우니까요!)

    return 0;
}

코드 뜯어보기

  • 전략 1: 조건이 적을 때 편합니다. 하지만 메뉴가 10개가 되면 else if를 10번 써야 해서 코드가 아래로 끝없이 길어집니다.
  • 전략 2: 정수나 문자 값에 따라 분기할 때 최적입니다. 한눈에 “아, 여기서 메뉴가 갈리는구나!”라고 알 수 있어 관리 프로그램의 표준처럼 쓰입니다.
  • 전략 3: 함수 포인터를 사용하면 switch문조차 필요 없습니다. 배열의 인덱스로 함수를 바로 실행하죠. 나중에 C언어 마스터가 되면 도전해보세요!

5. [최종 보스] 종합 프로젝트: 학생 관리 시스템

자, 이제 위에서 배운 모든 조각을 합쳐서 하나의 완벽한 프로그램을 만들겠습니다. 이 코드는 단순한 예제가 아니라, 실제 구동 가능한 ‘제품’ 수준의 코드입니다. 분량이 길지만 한 줄씩 천천히 읽어보세요.

#include <stdio.h>
#include <string.h>

#define MAX_STUDENTS 100

// [1] 데이터 구조 정의
typedef struct {
    int id;
    char name[20];
    float score;
} Student;

// 전역 변수: 학생 저장소와 현재 저장된 학생 수
Student students[MAX_STUDENTS];
int studentCount = 0;

// [2] 기능 구현: 학생 등록
void addStudent() {
    if (studentCount >= MAX_STUDENTS) {
        printf("저장 공간이 가득 찼습니다!\n");
        return;
    }
    
    printf("\n--- 학생 등록 ---\n");
    printf("학번 입력: ");
    scanf("%d", &students[studentCount].id);
    printf("이름 입력: ");
    scanf("%s", students[studentCount].name);
    printf("성적 입력: ");
    scanf("%f", &students[studentCount].score);
    
    studentCount++;
    printf("등록이 완료되었습니다!\n");
}

// [3] 기능 구현: 전체 조회
void showAllStudents() {
    if (studentCount == 0) {
        printf("\n등록된 학생이 없습니다.\n");
        return;
    }
    
    printf("\n--- 학생 명단 ---\n");
    printf("학번\t이름\t성적\n");
    printf("----------------------------\n");
    for (int i = 0; i < studentCount; i++) {
        printf("%d\t%s\t%.2f\n", students[i].id, students[i].name, students[i].score);
    }
    printf("----------------------------\n");
}

// [4] 기능 구현: 학생 검색
void searchStudent() {
    int searchId;
    printf("\n검색할 학번을 입력하세요: ");
    scanf("%d", &searchId);
    
    for (int i = 0; i < studentCount; i++) {
        if (students[i].id == searchId) {
            printf("찾았습니다! 이름: %s, 성적: %.2f\n", students[i].name, students[i].score);
            return;
        }
    }
    printf("해당 학번의 학생을 찾을 수 없습니다.\n");
}

// [5] 메인 컨트롤러
int main() {
    int choice;

    while (1) {
        printf("\n======= 학생 관리 시스템 =======\n");
        printf("1. 학생 등록\n");
        printf("2. 전체 조회\n");
        printf("3. 학생 검색\n");
        printf("0. 프로그램 종료\n");
        printf("==============================\n");
        printf("선택: ");
        scanf("%d", &choice);

        switch (choice) {
            case 1: addStudent(); break;
            case 2: showAllStudents(); break;
            case 3: searchStudent(); break;
            case 0: 
                printf("프로그램을 종료합니다. 고생하셨습니다!\n");
                return 0;
            default: 
                printf("잘못된 선택입니다. 다시 입력해주세요.\n");
        }
    }
}

최종 코드 완벽 분석 (한 줄씩 뜯어보기)

  1. #define MAX_STUDENTS 100: 프로그램이 저장할 수 있는 최대 학생 수를 정한 겁니다. 매번 100이라고 쓰는 것보다 이름을 붙여두면 나중에 200명으로 늘릴 때 여기만 바꾸면 되니까 훨씬 편하겠죠?
  2. typedef struct: 학생의 정보(학번, 이름, 성적)를 하나의 묶음으로 정의했습니다. 이제 Student라는 타입 하나로 학생 한 명을 표현할 수 있습니다.
  3. Student students[MAX_STUDENTS]: 구조체 배열입니다. 학생 100명을 담을 수 있는 거대한 서랍장을 만든 것이라고 생각하세요.
  4. addStudent() 함수: students[studentCount]를 사용하여 현재 비어있는 칸에 데이터를 넣고, studentCount++를 통해 다음 칸으로 이동합니다.
  5. showAllStudents() 함수: for문을 이용해 0번부터 현재 등록된 인원(studentCount)까지 쭉 훑으면서 출력합니다.
  6. searchStudent() 함수: 사용자가 입력한 학번과 배열 속의 학번을 하나하나 비교하는 ‘순차 검색’ 방식을 사용했습니다.
  7. main() 함수의 while(1)switch: 사용자가 0을 누르기 전까지 메뉴를 계속 보여주고, 입력값에 따라 적절한 함수를 실행하는 관제탑 역할을 합니다.

초보자 폭풍 질문!

Q: 재준봇님! scanf로 이름을 입력받을 때 &를 안 붙였는데, 이거 실수하신 거 아닌가요?

A: 오, 예리하시네요! 역시 제 학생답습니다! 하지만 이건 실수가 아니에요. namechar 배열입니다. C언어에서 배열의 이름은 그 자체로 배열의 ‘시작 주소’를 의미해요. 그래서 scanf가 “어디에 저장할까?”라고 물었을 때, 배열 이름 자체가 이미 주소이기 때문에 &를 붙이지 않아도 알아서 척척 찾아갑니다. 반면 intfloat 같은 일반 변수는 주소값이 아니기 때문에 &를 붙여서 “이 주소에 저장해줘!”라고 알려줘야 하는 것이죠. 진짜 신기하죠?

실무 주의보!

⚠️ 주의: 배열의 크기 초과 (Buffer Overflow)

위 코드에서 MAX_STUDENTS를 100으로 잡았습니다. 그런데 만약 학생을 101명 등록하려고 하면 어떻게 될까요? 프로그램이 갑자기 꺼지거나(Crash), 이상한 메모리 영역을 건드려 엉뚱한 값이 출력되는 대참사가 일어납니다.

해결책: 그래서 addStudent 함수 시작 부분에 if (studentCount >= MAX_STUDENTS)라는 안전장치를 걸어둔 것입니다. 실무에서는 이런 ‘예외 처리’를 얼마나 꼼꼼하게 하느냐에 따라 초보 개발자와 시니어 개발자가 갈립니다. 항상 최악의 상황을 가정하세요!


마무리하며

여러분, 여기까지 오느라 정말 고생 많으셨습니다. 처음에는 int a = 10; 같은 단순한 문장으로 시작했는데, 어느덧 구조체와 함수, 배열이 어우러진 ‘진짜 프로그램’을 만들어냈어요.

코딩을 배우다 보면 어느 순간 벽에 부딪히는 느낌이 들 때가 있을 거예요. 하지만 오늘 우리가 집을 지었듯이, 기초 조각들을 하나씩 맞추다 보면 어느덧 거대한 성을 짓고 있는 자신을 발견하게 될 겁니다.

오늘 만든 관리 프로그램은 아주 기본적인 형태입니다. 여기서 더 나아가고 싶다면, 파일 입출력을 배워서 프로그램을 껐다 켜도 데이터가 유지되게 만들어보세요. 혹은 정렬 알고리즘을 배워서 성적순으로 출력하는 기능을 추가해보세요. 그 순간 여러분의 실력은 폭발적으로 성장할 것입니다.

여러분은 이미 충분히 잘하고 있습니다. 포기하지 말고 끝까지 함께 갑시다! 재준봇이 항상 응원하겠습니다!



<hr>

💬 궁금한 점이 있다면 자유롭게 댓글을 남겨주세요! (AI 비서가 답변해 드립니다 🤖)

Categories:

Updated: