C & C++/디딤돌 C언어

Part 30. 도서 관리 프로그램 II - 장르 추가, 전체 장르 보기

언휴 2024. 1. 23. 09:11

Part 30. 도서 관리 프로그램 II - 장르 추가, 전체 장르 보기

C언어, 도서 관리 프로그램

104.도서관리프로그램 – 클래스다이어그램

도서 관리 프로그램에서는 장르별로 도서를 관리하게 할거예요.
따라서 사용자 정의 형식으로 프로그램, 장르, 도서로 역할을 구분하여 정의하기로 해요.

클래스 다이어그램에서는 프로그램에 정의할 형식을 클래스로 표시하고 클래스 간의 관계를 추가로 표시해요.
프로그램은 장르들의 집합체이고 장르는 도서의 집합체죠.
이와 같은 관계를 집합 관계(실선과 빈 다이아몬드)라 불러요.

도서 관리 프로그램 클래스 다이어그램
도서 관리 프로그램 클래스 다이어그램

이 외에도 클래스 간의 관계는 직접 연관 관계, 연관 관계, 집합 관계, 일반화 관계, 의존 관계, 실현 관계가 있어요.
C++, Java, C# 처럼 OOP 프로그램에서는 이와 같은 관계에 어울리는 문법이 있어요.
여기에서는 이들에 관한 설명은 하지 않을게요.

도서는 Book 구조체로 정의할게요.
도서 데이터에는 제목, 저자, 도서 번호를 멤버가 있어야겠죠.

◈ Book.h

#pragma once
typedef struct _Book Book;
#define MAX_TNAME_LEN     100
#define MAX_ANAME_LEN 20
struct _Book
{
    char title[MAX_TNAME_LEN+1];
    char author[MAX_ANAME_LEN+1];
    int bnum;
};

장르에는 장르명, 장르번호, 최근에 부여한 도서 번호, 도서를 보관할 EHArray 개체가 있어야겠죠.

◈ Gerne.h

#pragma once
#include "Book.h"
#include "EHArray.h"
typedef struct _Genre Genre;
#define MAX_GNAME_LEN 20
struct _Genre
{
    char name[MAX_GNAME_LEN+1];
    int gnum;
    int last_bnum;
    EHArray *books;
};

App에는 최근에 생성한 장르 번호와 장르를 보관하는 Array 개체가 필요하겠죠.

◈ App.h

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

EHArray.h 는 사용자 정의 배열 게시글을 참고하세요.

105. 도서 관리 프로그램 – 장르 추가

C 언어 - 도서 관리 프로그램  - 장르 추가

이제 유즈케이스별로 각 기능을 어떠한 흐름으로 수행해야 하는지 생각해 보기로 해요.
여기서는 최종 사용자, App, Genre, Book사이에 주고 받을 흐름만 정의할게요.

먼저 장르 추가 기능의 시퀀스를 정의해 보세요.

장르 추가 기능에서는 Program에서 최종 사용자에게 추가할 장르 이름을 입력할 것을 요청하게 하세요.
최종 사용자가 장르 이름을 입력하면 Program에서는 같은 이름의 장르가 있는지 확인하겠죠.
그리고 없을 때 장르를 생성하게 하세요.

도서 관리 프로그램 - 장르 추가 시퀀스 다이어그램
도서 관리 프로그램 - 장르 추가 시퀀스 다이어그램

App에서는 Genre에서 제공하는 GetName 기능과 장르를 생성하는 NewGenre 기능을 사용하고 있어요.
따라서 이 두 함수는 Genre에서 제공하세요.
New 함수를 제외한 나머지 함수 이름은 형식명으로 시작하고 첫 번째 인자는 형식 포인터 변수예요.
이 둘은 시퀀스 다이어그램에서는 생략하기로 해요.
Genre.h에는 App에서 사용하는 함수 선언문을 추가하세요.

동적으로 장르를 생성하는 함수가 있으니 동적으로 소멸하는 함수를 선언하세요.

Genre *NewGenre(int gnum,const char *gname);
void DeleteGenre(Genre *genre);
const char *GenreGetName(Genre *genre);

Genre.c에서 이들 함수를 구현해야겠죠.
NewGenre에서는 Genre형식 크기의 메모리를 할당한 후에 초기화 작업을 수행하고 할당한 메모리를 반환하세요.

void GenreGenre(Genre *genre,int gnum,const char *gname);
Genre *NewGenre(int gnum,const char *gname)
{
    Genre *genre = 0;
    genre = (Genre *)malloc(sizeof(Genre));
    GenreGenre(genre,gnum,gname);
    return genre;
}

초기화 작업에서는 장르 번호와 장르 이름을 입력 인자로 전달받은 값으로 설정하세요.
도서를 보관하기 위한 동적 배열을 생성하고 장르 내 도서 번호를 부여하기 위한 최근에 부여한 도서 번호를 0으로 설정하세요.

void GenreGenre(Genre *genre,int gnum,const char *gname)
{
    genre->gnum = gnum;
    memset(genre->name,0,sizeof(genre->name));
    strncpy(genre->name,gname,MAX_GNAME_LEN);
    genre->books = NewArray(0,0);
    genre->last_bnum = 0;
}

Genre 개체를 소멸하는 함수에서는 해제화 작업 후 자신의 메모리를 해제해야겠죠.

void DeleteGenre(Genre *genre)
{
    GenreTGenre(genre);
    free(genre);
}

해제화 작업에서는 도서를 보관하기 위해 동적으로 생성한 배열 개체를 소멸하세요.

void GenreTGenre(Genre *genre)
{
    DeleteEHArray(genre->books);
}

장르 이름 가져오기 함수에서는 자신의 장르 이름을 반환하세요.

const char *GenreGetName(Genre *genre)
{
    return genre->name;
}

장르 추가 기능에 앞서 App 초기화 함수에서 장르를 보관하기 위한 배열을 동적으로 생성하는 부분을 추가하세요.
해제화 함수에서 이를 소멸하는 부분을 추가하세요.

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

장르 추가 기능에서는 장르명을 입력 받게 하세요.
그리고 App에 보관한 장르에서 같은 이름의 장르가 있는지 찾아요.
만약에 없다면 장르 개체를 생성하여 배열에 보관하세요.

void AppAddGenre(App *app)
{
    char gname[MAX_GNAME_LEN+1]="";
    Iterator seek = 0;
    Genre *genre = 0;

    printf("%d번째 추가할 장르명을 입력하세요.\n",app->last_gnum+1);
    gets_s(gname,MAX_GNAME_LEN);

    seek = AppFindGenre(app,gname);
    if(seek != EHArrayEnd(app->genres))
    {
        printf("이미 존재하는 장르입니다.\n");
        return;
    }
    genre = NewGenre(app->last_gnum+1,gname);
    app->last_gnum++;
    EHArrayPushBack(app->genres,genre);
}

특정 이름의 장르를 보관한 위치를 찾는 함수를 구현하기로 해요.
배열에서는 자료를 보관한 시작 위치와 끝 위치를 함수로 제공하고 있어요.
끝 위치란 마지막 자료를 보관한 위치가 아니라 이번에 자료를 보관할 위치임을 주의하세요.
즉 마지막 자료를 보관한 다음 위치예요.
그리고 Iterator 형식 변수에 간접 연산을 취하면 보관한 요소를 얻을 수 있어요.
여러분은 자신이 보관한 형식으로 명시적 형변환하세요.

Iterator AppFindGenre(App *app,const char *gname)
{
    Iterator seek;
    Iterator end;
    Genre *sgenre=0;
    const char *sgname=0;

    seek = EHArrayBegin(app->genres);
    end = EHArrayEnd(app->genres);

    for(  ;seek != end; ++seek)
    {
        sgenre = (Genre *)(*seek);
        sgname = GenreGetName(sgenre);
        if(strcmp(sgname,gname)==0)
        {
            break;
        }
    }
    return seek;
}

106. 도서 관리 프로그램 – 전체 장르 보기

C언어 - 도서 관리 프로그램 - 전체 장르 보기

이번에는 전체 장르 보기에 관한 시퀀스 다이어그램을 작성하기로 해요.
전체 장르 보기에서는 App에 보관한 모든 장르를 순차적으로 탐색하여 각 장르의 정보를 출력하면 되겠죠.

도서 관리 프로그램 - 전체 장르 보기 시퀀스 다이어그램
도서 관리 프로그램 - 전체 장르 보기 시퀀스 다이어그램

먼저 Genre에 View 함수를 제공하기로 해요.
View 함수에서는 장르 번호와 이름을 출력하게 하세요.

void GenreView(Genre *genre)
{
    printf("장르 번호:%d 장르 이름:%s\n",genre->gnum,genre->name);
}

App 소스에서는 Iterator 이용하여 배열에 보관한 장르를 하나씩 얻어와서 Genre의 View함수를 호출하세요.

void AppListGenre(App *app)
{
    Iterator seek= EHArrayBegin(app->genres);
    Iterator end= EHArrayEnd(app->genres);
    Genre *sgenre=0;
    for(  ;seek != end; ++seek)
    {
        sgenre = (Genre *)(*seek);
        GenreView(sgenre);
    }
}