103. 도서관리프로그램-분석결과를 코드로

실습 각 단계에서 다이어그램을 작성할 때마다 약속한 부분을 코드로 구현할게요.
먼저 Win32 콘솔 응용 프로그램 프로젝트를 생성한 후에 Program.h 파일과 Program.c 파일을 추가하세요.
그리고 앞에서 작성한 동적 배열(88. 사용자 정의 배열 게시글 부터)을 추가하세요.

요구 분석한 부분을 코드로 옮기기 전에 콘솔 응용 프로그램 종류에 관계없이 사용할  두 개의 함수를 작성하고 출발합시다.
하나는 콘솔 화면을 지우는 함수예요.

void clrscr();

그리고 나머지 하나는 기능 키를 입력받는 함수예요.

int getkey();

콘솔 화면을 지우는 함수는 단순히 system(“cls”)를 래핑한 함수예요.

void clrscr()
{
    system("cls");
}

기능 키를 입력받는 함수는 키보드의 F1, F2, F3 등의 키를 입력받는 함수예요.
ASCII 코드에는 기능 키(F1, F2, …)를 정의하지 않았어요.
ASCII 코드를 정의할 당시에는 키보드에 기능 키가 없었거든요.
conio.h 파일을 추가하여 getch 함수나 getche 함수를 이용하면 기능 키를 입력받을 수 있어요.
getch 함수는 누른 키를 콘솔 화면에 표시하지 않고 getche 함수는 표시하는 점을 빼고는 동작 원리는 같아요.
여기서는 getch 함수를 사용할게요.

getchar 함수는 최종 사용자가 엔터를 눌러야 입력한 문자들 중에 맨 앞에 문자의 아스키 코드 값을 반환했죠.
하지만 getch 함수는 getchar 함수와 달리 키보드를 누르면 바로 반응해요.
기능 키를 눌렀을 때 어떤 키를 눌렀는지 확인하려면 getch 함수를 두 번 호출해야 확인할 수 있어요.
getch 함수를 호출하였을 때 최종 사용자가 F1~F10 사이의 기능 키를 누르면 0을 반환해요.
그리고 다시 getch 함수를 호출하면 어떤 기능 키를 눌렀는지 확인할 수 있어요.
59는 F1, 60은 F2, … 예요.
마지막으로 getch 함수를 호출하였을 때 ESC 키를 누르면 27을 반환해요.
먼저 기능 키를 열거형으로 정의하세요.
여기서는 다른 키를 눌렀을 때의 값을 NO_DEFINED로 정하고 ESC, F1, F2 순으로 열거하기로 해요.

typedef enum _key key;
enum _key
{
    NO_DEFINED, F1, F2, F3, F4, F5, F6, F7, F8, F9,F10, ESC
};

최종 사용자가 어떠한 기능 키를 입력했는지 확인하는 함수를 getkey라고 정할게요.

int getkey(void); //getkey: 최종 사용자가 입력한 기능 키를 반환하는 함수

◈ ehcommon.h

#pragma once
typedef enum _key key;
enum _key
{
    NO_DEFINED, F1, F2, F3, F4, F5, F6, F7, F8, F9,F10, ESC
};
void clrscr();
int getkey();

◈ ehcommon.c

#pragma warning(disable:4996)
#include "ehcommon.h"
#include <conio.h>
#include <stdio.h>
#include <process.h>
void clrscr()
{
    system("cls");
}
 
int getkey()
{
    int key = 0;
    key = getch();
    if(key == 27)
    {
        return ESC;
    }
    if(key == 0)
    {
        key = getch();
        switch(key)
        {
        case 59: return F1;case 60: return F2;case 61: return F3;case 62: return F4;
        case 63: return F5;case 64: return F6;case 65: return F7;case 66: return F8;
        case 67: return F9; case 68: return F10;
        }
    }
    return NO_DEFINED;
}  

프로그램은 시작하면서 App 개체를 생성하고 사용자와 상호작용을 수행한 후에 App 개체를 소멸하는 순서로 수행하게 하세요.

App 개체를 생성할 때는 데이터 파일이 있을 때 파일의 내용으로 데이터를 로딩하는 작업을 수행하세요.
이를 위해 Program.c 파일에 초기 파일 이름을 정의하세요.
여기서 정한 파일 이름과 확장자는 여러분께서 적당하게 정하세요.
#define DEF_FNAME    “member.ehd”

그리고 main 함수로 전달받은 인자가 2개일 때는 프로그램에 사용할 파일 이름으로 두 번째 인자를 사용할게요.
◈ Program.c

#include "App.h"
#define DEF_FNAME    "member.ehd"
int main(int argc, char **argv)
{
    App *app = 0;
    if(argc != 2)
    {
        app = NewApp(DEF_FNAME);
    }
    else
    {
        app = NewApp(argv[1]);
    }
    AppRun(app);
    DeleteApp(app);
    return 0;
}

설계 단계에서 프로그램에 사용할 형식을 정의할 거예요.
하지만 여기에서 기본적인 흐름을 정의하기 위해 간단하게 App 구조체를 가상으로 정의하세요.
그리고 App을 동적으로 생성하는 함수와 소멸하는 함수, 사용자와 상호 작용하는 함수 Run을 선언하세요.

◈ App.h

#pragma once
typedef struct _App    App;
#include <stdio.h>
struct _App
{
    char fname[FILENAME_MAX];
};
 
App *NewApp(const char *fname);
void AppRun(App *app);
void DeleteApp(App *app

이제 App.c 파일을 작성하기로 해요.

먼저 App 데이터를 동적으로 생성하는 함수를 작성하세요.
App 형식 크기의 메모리를 할당받아요.
그리고 할당받은 App 데이터의 생성 시 해야 할 초기 작업을 수행하고고 할당받은 메모리를 반환하세요.

App *NewApp(const char *fname)
{
   App *app = 0;
   app = (App *)malloc(sizeof(App));
   AppApp(app,fname);
   return app;
}

App 데이터를 생성한 후에 초기화하는 함수에서는 인자로 전달받은 파일 이름을 설정한 후에 파일에 보관한 데이터를 로딩하는 작업을 수행하세요.

void AppApp(App *app,const char *fname)
{
   memset(app->fname,0,sizeof(app->fname));
   strncpy(app->fname,fname,FILENAME_MAX);
   AppLoad(app);
   printf("아무 키나 누르세요.\n");
   getkey();
}

AppLoad 함수는 설계 및 구현 단계에서 해야 할 일을 고민하고 작성하기로 해요.
App 개체를 소멸하는 함수에서는 관리하는 데이터를 저장한 후에 소멸하기 전에 해제하고 자신의 메모리를 해제하게 하세요.

void DeleteApp(App *app)
{
   AppSave(app);
   AppTApp(app);
   free(app);
}

AppSave 함수와 AppTApp 함수는 설계 및 구현 단계에서 해야 할 일을 고민하고 구현하기로 해요.

최종 사용자와 상호작용하는 함수에서는 메뉴를 선택하면 선택한 기능을 반복할 거예요.

void AppRun(App *app)
{
    int key = 0;
    while((key = AppSelectMenu(app))!=ESC)
    {
        switch(key)
        {
            case F1: AppAddGenre(app); break;
            case F2: AppRemoveGenre(app); break;
            case F3: AppListGenre(app); break;
            case F4: AppListBookAtGenre(app); break;
            case F5: AppAddBook(app); break;
            case F6: AppFindBookByNum(app); break;
            case F7: AppFindBookByTitle(app); break;
            case F8: AppListAll(app); break;
            default: printf("잘못 선택하였습니다.\n"); break;
        }
        printf("아무 키나 누르세요.\n");
        getkey();
    }
}

◈ App.c

#pragma warning(disable:4996)
#include "App.h"
#include "ehcommon.h"
#include <malloc.h>
#include <stdio.h>
#include <memory.h>
#include <string.h>
 
void AppApp(App *app,const char *fname);
void AppTApp(App *app);
void AppLoad(App *app);
void AppSave(App *app);
 
int AppSelectMenu(App *app);
void AppAddGenre(App *app);
void AppRemoveGenre(App *app);
void AppListGenre(App *app);
void AppListBookAtGenre(App *app); 
void AppAddBook(App *app); 
void AppFindBookByNum(App *app); 
void AppFindBookByTitle(App *app); 
void AppListAll(App *app); 
 
App *NewApp(const char *fname)
{
    App *app = 0;
    app = (App *)malloc(sizeof(App));
    AppApp(app,fname);
    return app;
}
 
void AppApp(App *app,const char *fname)
{
    memset(app->fname,0,sizeof(app->fname));
    strncpy(app->fname,fname,FILENAME_MAX);
    AppLoad(app);
    printf("아무 키나 누르세요.\n");
    getkey();
}
void AppLoad(App *app)
{
    printf("Load\n");    
}
void AppRun(App *app)
{
    int key = 0;
    while((key = AppSelectMenu(app))!=ESC)
    {
        switch(key)
        {
        case F1: AppAddGenre(app); break;
        case F2: AppRemoveGenre(app); break;
        case F3: AppListGenre(app); break;
        case F4: AppListBookAtGenre(app); break;
        case F5: AppAddBook(app); break;
        case F6: AppFindBookByNum(app); break;
        case F7: AppFindBookByTitle(app); break;
        case F8: AppListAll(app); break;
        default: printf("잘못 선택하였습니다.\n"); break;
        }
        printf("아무 키나 누르세요.\n");
        getkey();
    }
} 
int AppSelectMenu(App *app)
{
    clrscr();
    printf("장르별 도서관리 프로그램 \n");
    printf("F1:장르 추가 F2: 장르 삭제 F3: 전체 장르 보기\n");
    printf("F4: 특정 장르의 도서 목록 보기\n");
    printf("F5:도서 추가 F6: 도서 검색(일련번호) F7: 도서검색(제목)\n");
    printf("F8: 전체 도서 보기 ESC: 종료\n");
    return getkey();
}
void AppAddGenre(App *app)
{
    printf("장르 추가\n");    
}
void AppRemoveGenre(App *app)
{
    printf("장르 삭제\n");    
}
void AppListGenre(App *app)
{
    printf("전체 장르 보기\n");    
}
void AppListBookAtGenre(App *app)
{
    printf("특정 장르 내 도서 목록 보기\n");    
}
void AppAddBook(App *app)
{
    printf("도서 추가\n");    
}
void AppFindBookByNum(App *app)
{
    printf("도서 번호로 도서 검색\n");    
}
void AppFindBookByTitle(App *app)
{
    printf("도서 제목으로 도서 검색\n");    
}
void AppListAll(App *app)
{
    printf("전체 보기\n");    
}
void DeleteApp(App *app)
{
    AppSave(app);
    AppTApp(app);
    free(app);
}
void AppTApp(App *app)
{
    printf("App 해제화\n");     
}
void AppSave(App *app)
{
    printf("Save\n");    
}

여러분은 주기적으로 프로젝트를 빌드해서 오류가 있는지 확인하고 오류를 잡으면서 작업을 진행하세요.