본문 바로가기
프로그래밍/C

[ C언어 ] 프로젝트(삽질일기) : 도서 관리 프로그램 - (1) - 구조체 배열

by eteo 2022. 4. 12.

C언어 도서관리 프로그램
구조체배열을 사용한 간단한 도서관리 프로그램

 

 

book.h

#ifndef __BOOK_H__
#define __BOOK_H__

#define SIZE 20

typedef struct {
	char year;
	char month;
	char day;
}_stDate;

typedef struct {
	char title[30];
	int price;
	char author[10];
	int page;
	_stDate date;
} _stBook;

char DispMenu(char num);
char CountData(_stBook list[]);
char AddBook(_stBook list[], char num);
void PrintBook(_stBook list[], char num);
char RmBook(_stBook list[], char num);
void SortMenu(_stBook list[], char num);
void SelectionSort(_stBook list[], char num, char select);
char* SearchBook(_stBook list[], char num);
void PrintSearch(_stBook list[], char i);

#endif

헤더파일 내 구조체 선언 및 함수의 원형 선언

  • typedef 사용 시 굳이 구조체 변수명을 쓸 필요 없이 재정의할 별칭만 적어주면 된다.

 

 

main.c

#include <stdio.h>
#include <Windows.h>
#include <stdlib.h>
#include <string.h>
#include "book.h"


int main(void) {

	_stBook booklist[SIZE] = {
	{"모두의 딥러닝", 20000, "조태호", 300, {22,1,1}},
	{"혼자 공부하는 자바", 24000, "신용권", 708, {19,6,10}},
	{"윈도우 11 가이드북", 17100, "권순만", 432, {22,3,25}},
	{"파이썬 정복", 19800, "김상형", 508, {18,4,2}},
	{"코딩테스트 연습", 30600, "나동빈", 604, {20,8,5}},
	{"안드로이드 프로그래밍", 33000, "우재남", 580, {22,1,22}},
	{"가장 쉬운 유니티", 54000, "이제민", 1102, {22,2,1}},
	{"SQL 정복", 35100, "김상형", 708, {21,5,1}},
	{"자바스크립트 입문", 16200, "고경희", 352, {21,11,1}},
	{"Node.js 교과서", 32400, "조현영", 756, {20,7,25}},
	};

	char datanum = CountData(booklist);

	while (1) {		

		char menu;
		system("cls");
		menu = DispMenu(datanum);
		system("cls");
		if (menu == 6) break;
		else if (menu == 1) datanum = AddBook(booklist, datanum);
		else if (menu == 2) break; /*datanum = RmBook(booklist, datanum);*/
		else if (menu == 3) {
			SortMenu(booklist, datanum);
			system("pause");
		}
		else if (menu == 4) {
			SearchBook(booklist, datanum);
			system("pause");
		}
		else if (menu == 5) {
			PrintBook(booklist, datanum);
			system("pause");
		}
		else {
			rewind(stdin);
			continue;
		}
	}
	return 0;
}

main에는 booklist의 최초 데이터 개수를 세서 담는 변수와 메뉴를 화면에 표시한 뒤 키보드로 입력받은 값을 저장할 변수만 선언하였다.

  • rewind(stdin)는 원하지 않는 문자나 문자열이 입력되었을 때 무한루프에 빠지는 경우를 방지하기 위해 추가하였다.

 

 

 

프로그램 작성시 if else 문을 사용하였지만 switch case문을 사용해도 될 것 같아서 아래와 같이 한번 바꿔봤다.

	switch(menu) {
		case 1 :
			datanum = AddBook(booklist, datanum);
			break;
		case 2 :
			datanum = RmBook(booklist, datanum);
			break;
		case 3 :
			SortMenu(booklist, datanum);
			system("pause");
			break;
		case 4:
			SearchBook(booklist, datanum);
			system("pause");
			break;
		case 5:
			PrintBook(booklist, datanum);
			system("pause");
			break;
		case 6:
			return 0;
		default :
			continue;
		}

 

 

function.c

char DispMenu(char num) {
	char menu;
	
	printf("------------------------------------------------------------------------------\n");
	printf("|                                                                            |\n");
	printf("|                           도서 검색 프로그램                               |\n");
	printf("|                                              (현재 데이터 개수 : %2d)       |\n", num);
	printf("|----------------------------------MENU--------------------------------------|\n");
	printf("|                                                                            |\n");
	printf("|  1. 도서 추가                                                              |\n");
	printf("|                                                                            |\n");
	printf("|  2. 도서 삭제                                                              |\n");
	printf("|                                                                            |\n");
	printf("|  3. 도서 정렬                                                              |\n");
	printf("|                                                                            |\n");
	printf("|  4. 도서 검색                                                              |\n");
	printf("|                                                                            |\n");
	printf("|  5. 도서 확인                                                              |\n");
	printf("|                                                                            |\n");
	printf("|  6. 종료                                                                   |\n");
	printf("|                                                                            |\n");
	printf("------------------------------------------------------------------------------\n\n");
	printf("   원하는 메뉴를 입력하세요 [ ]\b\b");
	scanf_s("%hhd", &menu);
	return menu;
}

메뉴 표시하는 함수

 

 

 

char CountData(_stBook list[]) {
	char cnt = 0;
	for (int i = 0; i<SIZE ; i++) {
		if (list[i].title[0] != 0) cnt++;
	}
	return cnt;
}

초기실행 시 현재 데이터 개수를 반환하는 함수. 구조체 선언 후 초기화를 했기 때문에 제목배열이 null으로 시작하면 데이터값이 없는것으로 보았다.

 

 

 

char AddBook(_stBook list[], char num) {
	int i = num;
	char temp[100] = { 0, };
	for (; i < SIZE; i++) {
		char chk;
		printf("------------------------------------------------------------------------------\n\n");
		printf("   도서 제목을 입력하세요: ");
		scanf_s(" %[^\n]s", temp, sizeof(temp));	// 공백포함해서 입력받음
		if (strlen(temp) > sizeof(list[i].title) - 1) {	// 입력받은문자열의 길이+\0이 배열의 길이를 초과하는 경우 다시 입력받음
			printf("   너무 긴 문자열을 입력하셨습니다. 처음부터 다시 입력해 주세요.\n");
			i--;
			continue;
		}
		else strcpy_s(list[i].title, sizeof(list[i].title), temp);
		printf("\n   도서 가격을 입력하세요: ");
		scanf_s("%d", &list[i].price);
		printf("\n   도서 저자를 입력하세요: ");
		scanf_s(" %[^\n]s", temp, sizeof(temp));
		if (strlen(temp) > sizeof(list[i].author) - 1) {
			printf("   너무 긴 문자열을 입력하셨습니다. 처음부터 다시 입력해 주세요.\n");
			i--;
			continue;
		}
		else strcpy_s(list[i].author, sizeof(list[i].author), temp);
		printf("\n   도서 페이지를 입력하세요: ");
		scanf_s("%d", &list[i].page);
		printf("\n   도서 발행연도를 입력하세요(yy-mm-dd) : __ __ __\b\b\b\b\b\b\b\b");
		scanf_s("%02hhd %02hhd %02hhd", &list[i].date.year, &list[i].date.month, &list[i].date.day); // 0포함 입력받음
		printf("\n------------------------------------------------------------------------------\n");
		printf("\n   계속 입력하려면[1] 메뉴로 돌아가려면[0]를 입력하세요 [ ]\b\b");
		scanf_s("%hhd", &chk);
		if (chk == 0) {
			i++;
			return i;
		}
		system("cls");
	}
	printf("   입력 가능한 데이터 개수를 초과하였습니다. ");
	system("pause");
	return i;
}

도서 추가 기능

현재 데이터 개수를 num 매개변수로 받아서 num부터 구조체 배열의 SIZE 크기 전까지 반복한다.

  • scanf에 정규표현식 %[^\n]s 를 사용하면 공백 포함하여 엔터가 쳐지기 전까지의 입력을 받을 수 있다.
  • 또한 배열의 길이를 넘는 입력이 들어오는 경우의 예외처리를 하기 위해 함수안에 임시배열 temp를 두고 길이를 검사한 후에 이상이 없으면 strcpy로 복사하도록 하였다.
  • 도서 발행연도 같은 경우 01~09 등을 문제없이 입력받기 위해 두자리로 입력받고 1자리숫자인 경우 앞에 0을 채워 입력 받을 수 있도록 scanf 안에 %02d 형식을 사용하였다. 

 

 

 

void GotoXY(int x, int y) {
	COORD Pos;
	Pos.X = x;
	Pos.Y = y;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), Pos);
}
char AddBook(_stBook list[], char num) {
	// GotoXY 버전
	int i = num;
	char temp[100] = { 0, };
	for (; i < SIZE; i++) {
		char chk;
		printf("------------------------------------------------------------------------------\n\n");
		printf("   도서 제목을 입력하세요: ");
		printf("\n\n   도서 가격을 입력하세요: ");
		printf("\n\n   도서 저자를 입력하세요: ");
		printf("\n\n   도서 페이지를 입력하세요: ");
		printf("\n\n   도서 발행연도를 입력하세요 : __ __ __ (yy-mm-dd)");
		printf("\n\n------------------------------------------------------------------------------\n");

		GotoXY(27, 2);
		scanf_s(" %[^\n]s", temp, sizeof(temp));	// 공백포함해서 입력받음
		if (strlen(temp) > sizeof(list[i].title) - 1) {	// 입력받은문자열의 길이+\0이 배열의 길이를 초과하는 경우 다시 입력받음
			printf("   너무 긴 문자열을 입력하셨습니다. 처음부터 다시 입력해 주세요.\n");
			i--;
			continue;
		}
		else strcpy_s(list[i].title, sizeof(list[i].title), temp);

		GotoXY(27, 4);
		scanf_s("%d", &list[i].price);

		GotoXY(27, 6);
		scanf_s(" %[^\n]s", temp, sizeof(temp));
		if (strlen(temp) > sizeof(list[i].author) - 1) {
			printf("   너무 긴 문자열을 입력하셨습니다. 처음부터 다시 입력해 주세요.\n");
			i--;
			continue;
		}
		else strcpy_s(list[i].author, sizeof(list[i].author), temp);

		GotoXY(29, 8);
		scanf_s("%d", &list[i].page);

		GotoXY(32, 10);
		scanf_s("%02hhd %02hhd %02hhd", &list[i].date.year, &list[i].date.month, &list[i].date.day); // 0포함 입력받음

		printf("\n\n   계속 입력하려면[1] 메뉴로 돌아가려면[0]를 입력하세요 [ ]\b\b");
		scanf_s("%hhd", &chk);
		if (chk == 0) {
			i++;
			return i;
		}
		system("cls");
	}
	printf("   입력 가능한 데이터 개수를 초과하였습니다. ");
	system("pause");
	return i;
}

도서 추가 기능을 GotoXY함수를 사용해서도 구현해 보았다.

 

디자인적으로 조금 더 정돈된 느낌을 주는 것 같긴 하지만 굳이 싶어 다시 주석처리 했다.

 

 

 

 

void PrintBook(_stBook list[], char num) {
	printf("------------------------------------------------------------------------------\n");
	printf("                제목                 가격      저자     페이지    발행연도   \n");
	printf("==============================================================================\n");
	for (int i = 0; i < num; i++) {
		printf("[%2d] %-30s %-9d %-10s %-8d %02d.%02d.%02d\n\n", i+1, list[i].title, list[i].price, list[i].author, list[i].page, list[i].date.year, list[i].date.month, list[i].date.day);
	}
	printf("------------------------------------------------------------------------------\n");
}

도서리스트 출력 기능

  • 출력타입 앞에 숫자를 붙이면 그만큼 칸을 확보하고 디폴트가 오른쪽정렬, -를 붙이면 왼쪽정렬이 된다. 앞에 0을 붙이면 빈공간에 0을 채운다.

 

 

 

void SortMenu(_stBook list[], char num) {
	char select;
	printf("|----------------------------------------------------------------------------|\n");
	printf("|                                                                            |\n");
	printf("|  무엇으로 정렬하시겠습니까?                                                |\n");
	printf("|                                                                            |\n");
	printf("|  1. 가격 순                                                                |\n");
	printf("|                                                                            |\n");
	printf("|  2. 발행연도 순                                                            |\n");
	printf("|                                                                            |\n");
	printf("|  3. 페이지 순                                                              |\n");
	printf("|                                                                            |\n");
	printf("|  4. 도서명 순                                                              |\n");
	printf("|                                                                            |\n");
	printf("------------------------------------------------------------------------------\n\n");
	printf("   원하는 메뉴를 입력하세요 [ ]\b\b");
	scanf_s("%hhd", &select);
	SelectionSort(list, num, select);
}

void SelectionSort(_stBook list[], char num, char select) {
	int i, j, min;
	_stBook temp;
	_stBook justforsort[SIZE];	// 원본값이 훼손되지 않도록 임시 구조체배열 생성

	memmove(justforsort, list, sizeof(_stBook[SIZE]));	// 구조체 복사

	for (i = 0; i < num-1 ; i++) {
		min = i;
		if (select == 1) {	// 가격 정렬
			for (j = i + 1 ; j < num ; j++) {
				if (justforsort[j].price < justforsort[min].price) min = j;
			}
		}
		else if (select == 2) {	// 발행연도 정렬
			for (j = i + 1; j < num; j++) {
				if (justforsort[j].date.year < justforsort[min].date.year) min = j;	//	년
				else if (justforsort[j].date.year == justforsort[min].date.year) {
					if (justforsort[j].date.month < justforsort[min].date.month) min = j;	// 월
					else if (justforsort[j].date.month == justforsort[min].date.month) {
						if (justforsort[j].date.day < justforsort[min].date.day) min = j;	// 일
					}
				}
			}
		}
		else if (select == 3) { // 페이지 정렬
			for (j = i + 1; j < num; j++) {
				if (justforsort[j].page < justforsort[min].page) min = j;
			}
		}
		else if (select == 4) { // 도서명 정렬
			for (j = i + 1; j < num; j++) {
				if (strcmp(justforsort[j].title, justforsort[min].title) < 0) min = j;
			}
		}
		temp = justforsort[i];
		justforsort[i] = justforsort[min];
		justforsort[min] = temp;
	}
	system("cls");
	PrintBook(justforsort, num);	// 출력함수 호출
}

도서 정렬 기능

정렬 기준을 선택하는 메뉴를 출력하는 함수와 실제 SelectionSort를 수행하는 함수를 따로 만들었다. 원본 배열에는 도서가 등록된 순서대로 등록되어있으므로 원본값이 훼손되지 않도록 임시 구조체 배열을 만들어 값을 복사한 뒤 정렬하여, 출력함수를 호출하고 그 임시 구조체 배열을 인수로 넘겨 출력을 하도록 했다.

 

 

 

char* SearchBook(_stBook list[], char num) {
	char keyword[100] = { 0, };
	char chk = 0;
	static char array[SIZE] = {0, };

	for (int i = 0; i < num; i++) {
		array[i] = 0;
	}

	printf("------------------------------------------------------------------------------\n");
	printf("   검색어를 입력하세요.......................[                          ]\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
	scanf_s(" %[^\n]s", keyword, sizeof(keyword));
	printf("\n------------------------------------------------------------------------------\n");
	printf("                제목                 가격      저자     페이지    발행연도   \n");
	printf("==============================================================================\n");
	for (int i = 0; i < num; i++) {
		if (strstr(list[i].title, keyword) != NULL) {	// 키워드로 검색하여 널이 아닌 경우 출력
			PrintSearch(list, i);
			array[chk] = i+1;
			chk++;	// 검색결과의 갯수와 열번호 저장
		}
	}
	if (chk == 0) printf("   검색 결과가 없습니다.\n");
	printf("------------------------------------------------------------------------------\n");
	return array;
}

void PrintSearch(_stBook list[], char i) {
	printf("[%2d] %-30s %-9d %-10s %-8d %02d.%02d.%02d\n\n", i + 1, list[i].title, list[i].price, list[i].author, list[i].page, list[i].date.year, list[i].date.month, list[i].date.day);
}

도서 검색 기능

키워드를 입력 받아서 strstr 함수로 검색한다.

strcmp 대신 strstr을 사용한 이유는 키워드 중 일부만 일치해도 검색 목록에 뜨게끔 하기 위함이다.

  • 문자열 탐색 함수인 strstr 의 원형은 char * strstr ( const char *, const char * ); 으로 string1에 string2와 일치하는 문자열이 있으면 해당 문자열이 시작하는 주소를 반환하고 없으면 NULL을 반환한다.

NULL이 아닌 경우 검색된 결과를 출력하는 함수를 별개로 만들었는데 사실 도서 출력함수랑 내용은 같지만 제목, 가격, 저자, 페이지, 발행연도가 써있는 첫번째 행이 여러번 나오면 곤란하기에 따로 만들었다.

또한 NULL이 아닌경우 chk++하여 chk==0 인 경우 검색결과가 없다고 출력하게 하였으며 이후 나오는 삭제기능을 구현하기 위해 NULL이 아닐 때의 인덱스 i값을 +1하여 차례대로 array[chk]에 넣은 뒤 array배열의 시작주소를 반환한다.

array배열은 return 값으로 사용했으므로 함수종료 후에도 살아있도록 static으로 선언하였고 함수 앞부분에 for문을 돌려 0으로 초기화해준다. 원래 static변수는 함수밖에서 접근이 불가하나 주소로 반환하였기에 가능해졌다.

 

 

 

 

char RmBook(_stBook list[], char num) {
	char chk;
	char* parray = 0;
	char rmnum = 0;
	printf("------------------------------------------------------------------------------\n");
	printf("   삭제하고자 하는 도서를 검색하세요\n");
	parray = SearchBook(list, num);	// 검색 결과가 담긴 배열을 포인터로 받음
	if (parray[0] == 0) {	// 검색 결과가 없을 때
		printf("\n   메뉴로 돌아갑니다......");
		system("pause");
	}
	else if (parray[1] == 0){	// 검색 결과가 1개일 때
		printf("\n   정말 삭제하시겠습니까? Yes [1] / No [0] ... [ ]\b\b");
		scanf_s("%hhd", &chk);
		if (chk == 1) {
			memset(&list[parray[0]-1], 0, sizeof(_stBook));	// 삭제
			for (int i = parray[0] - 1; i < num; i++) {
				memmove(&list[i], &list[i + 1], sizeof(_stBook));	// 한칸씩 땡겨서 정렬
			}
			num--;	// 데이터 개수 줄임
			printf("\n   삭제가 완료되었습니다...");
			system("pause");
		}
	}
	else {	// 검색 결과가 여러개 일 때
		printf("\n   삭제할 도서 번호를 입력해주세요 ... [ ]\b\b");
		scanf_s("%hhd", &rmnum);
		printf("\n   정말 삭제하시겠습니까? Yes [1] / No [0] ... [ ]\b\b");
		scanf_s("%hhd", &chk);
		if (chk == 1) {
			memset(&list[rmnum - 1], 0, sizeof(_stBook));	// 삭제
			for (int i = rmnum - 1; i < num; i++) {
				memmove(&list[i], &list[i + 1], sizeof(_stBook));	// 한칸씩 땡겨서 정렬
			}
			num--;	// 데이터 개수 줄임
			printf("\n   삭제가 완료되었습니다...");
			system("pause");
		}
	}
	return num;
}

도서 삭제기능

SearchBook 함수를 호출하고 반환된 값을 포인터에 넣는다.

parray[0]==0 인 경우를 검색결과가 없다고 보았는데 이것을 위해 앞의 SearchBook 함수에서 0번째 인덱스에서 키워드를 탐색을 했더라도 1이상의 값을 가질 수 있게 +1해주었던 것이다. parray[0]에만 값이 들어있고 parray[1]==0이라면 검색결과가 1개라는 뜻이고 else로 검색 결과가 여러 개인 경우 삭제할 도서번호를 직접 선택할 수 있게 구분했다. 

또 삭제한 경우 num-- 한뒤 메인에 반환해서 데이터 개수가 줄어들은 것을 알 수 있게 했다.

검색결과가 여러개 출력된 경우 직접 삭제할 도서번호를 선택하게끔 하였다.

 

 

다음편에는 삭제기능을 동적할당을 사용한 코드로 수정해보도록 하자

 

다음편 :

https://eteo.tistory.com/20

 

[C언어] 프로젝트(삽질일기) : 도서 관리 프로그램 - (2) - 동적 메모리 할당

지난편에 이어 도서 삭제기능을 동적할당을 사용한 버전으로 바꿔보자. 2022.04.12 - [Language/C] - [C언어] 프로젝트(삽질일기) : 도서 관리 프로그램 - (1) - 구조체 배열 처음으로 돌아가서 삭제기능은

eteo.tistory.com