Chater 1. 배열을 사용해 영화 평점 관리 프로그램 만들기
카테고리: DataStructure2
태그: C Data Structure Algorithm
홍정모 교수님의 강의 홍정모의 따라하며 배우는 C언어(부록) 를 듣고 정리한 필기입니다. 😀
Chapter 1. 배열(+동적 할당 배열)
🚀 영화 평점 관리 텍스트 파일
6
Rear Window
4.0
Mank
4.5
Tenet
4.0
Manchester by the Sea
4.0
Only Lovers Left Alive
3.5
Vice
4.5
영화 평점 파일은 이런 포맷으로 저장된다. 맨 위에 저장된 영화의 총 개수 6
, 그리고 제목 그 다음 줄에 평점 이런식으로 나열되어 저장 되는 포맷.
🚀 정적 배열 사용
✈ 영화 구조체
#define TSIZE 45 // 최대 45 글자(영화 제목 최대 45글자)
#define LMAX 10 // 배열 크기(저장할 영화 최대 개수)
struct movie
{
char title[TSIZE];
float rating;
};
- 정적 배열 안에서 영화들을 관리할거라서 이렇게 저장할 영화의 최대 갯수를 미리 정해놓고 시작해야 한다.
LMAX 10
최대 10 개의 영화 저장 가능. - 영화는 구조체로 관리 된다. 제목(char 배열) + 평점(float)
✈ main
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
int main()
{
struct movie movie_list[LMAX];
size_t n_items = 0;
read_file(movie_list, &n_items);
while (1)
{
printf("\n");
int s = input_menu();
switch (s)
{
case 1:
print_all(movie_list, n_items);
break;
case 2:
print_an_item(movie_list, n_items);
break;
case 3:
edit_an_item(movie_list, n_items);
break;
case 4:
add_an_item(movie_list, &n_items);
break;
case 5:
insert_an_item(movie_list, &n_items);
break;
case 6:
delete_an_item(movie_list, &n_items);
break;
case 7:
n_items = 0;
break;
case 8:
write_file(movie_list, n_items);
break;
case 9:
search_by_name(movie_list, n_items);
break;
case 10:
printf("Good bye\n");
exit(0);
default:
printf("%d is not implenmented. \n", s);
}
}
}
- 영화들을 담을 배열
movie_list
- 최대 크기인
LMAX
로 만들어둔다.
- 최대 크기인
- 실제 평점 관리 파일에 저장된 영화 갯수
n_items
✈ 파일 읽기
void read_file(struct movie movie_list[], size_t* ptr_n_items)
{
char filename[TSIZE] = { 0, };
printf("Please input filename to read and press Enter.\n");
printf(">> ");
if (scanf("%[^\n]%*c", filename) != 1)
{
printf("Wrong input. Terminating.\n");
exit(1);
}
FILE* file = fopen(filename, "r");
if (file == NULL)
{
printf("ERROR: Cannot open file. \n");
exit(1);
}
int num;
if (fscanf(file, "%d%*c", &num) != 1)
{
printf("ERROR: Wrong file format. \n");
exit(1);
}
for (int n = 0; n < num; n++)
{
if (fscanf(file, "%[^\n]%*c", movie_list[*ptr_n_items].title) != 1 ||
fscanf(file, "%f%*c", &movie_list[*ptr_n_items].rating) != 1) // rating 은 float 이니까 sacnf 에서 & 붙여야 함. 반면 title은 배열이니까 붙일 필요 X
{
printf("ERROR: Wrong file format. \n");
exit(1);
}
*ptr_n_items += 1;
}
assert(*ptr_n_items == num);
fclose(file);
printf("%d items have been read from the file.\n", *ptr_n_items);
}
- 읽을 파일의 이름 입력
scanf("%[^\n]%*c"
- ex)
%[12]
: 1 과 2 가 아닌 문자가 나오면 입력을 종료한다. 즉, 1과 2 만 입력을 받는다. 123456 을 입력했다면 12 만 최종적으로 입력 된다. %[^\n]
:^\n
은\n
을 제외한다는 뜻이다. 즉,\n
을 제외하고 모든 입력을 받겠다는 뜻- scanf 는 이 방법을 통해 공백 문자까지 포함하여 개행 문자 나오기 전까지의 한 줄의 문자열을 받을 수 있게 된다. 원래 scanf 는 공백 문자는 입력 받지 않지만 이런 꼼수를 쓸 수 있다!
%*c
:\n
이후에 쓴 문자들은 무시. 즉, 개행 문자 이후에 입력된 문자들도 버퍼에 입력이 되어 있을텐데 그 이후 문자들은 버퍼로부터 비우고 무시한다는 뜻이다.*
는 버퍼로부터 비우되 무시!- scanf 는 변수에 입력이 성공된 갯수를 리턴하기 때문에 입력한게
filename
에 제대로 저장이 안됐으면 1 이 리턴되지 않고 0 이 리턴될 것이다.
- ex)
- 파일 읽어오기
num
에 영화 갯수 받아오기"%d%*c"
: 정수만 입력 받고 그외 문자는 무시
movie_list
배열 원소들에 차곡차곡 영화들 저장- Call by Reference 로
n_items
원본을 변화시키기 위해 이를 참조하는ptr_n_items
으로 값을 조작하고 있다.
- Call by Reference 로
✈ 파일 쓰기
void write_file(struct movie movie_list[], size_t n_items)
{
char filename[TSIZE] = { 0, };
printf("Please input filename to read and press Enter.\n");
printf(">> ");
if (scanf("%[^\n]%*c", filename) != 1)
{
printf("Wrong input. Terminating.\n");
exit(1);
}
FILE* file = fopen(filename, "w");
if (file == NULL)
{
printf("ERROR: Cannot open file. \n");
exit(1);
}
fprintf(file, "%d\n", n_items);
for (int n = 0; n < n_items; n++)
{
fprintf(file, "%s\n", movie_list[n].title);
fprintf(file, "%f\n", movie_list[n].rating);
}
fclose(file);
printf("%d items have been saved to the file.\n", n_items);
}
✈ 메뉴 입력
int input_int()
{
int input;
while (1)
{
printf(">> ");
if (scanf("%d%*c", &input) == 1) return input; // 정수 입력
else
{
printf("Please input an integer and press enter. Try again.\n");
while (getchar() != '\n') continue; // 버퍼 비움
}
}
}
int input_menu()
{
while (1)
{
printf("Please select an option and press enter.\n");
printf("1. Print all items 2. Print an item\n");
printf("3. Edit an items 4. Add an item\n");
printf("5. Insert an items 6. Delete an item\n");
printf("7. Delete all items 8. Save file\n");
printf("9. Search by name 10. Quit\n");
int input = input_int();
if (input >= 1 && input <= 10) return input;
else printf("%d is invlid. Please try again.\n", input); // input_int()에서 정수 입력하긴 했는데 1~10 값을 벗어난 경우
}
}
- 제대로 입력 받았으면
return input
- 아니라면 while 문 다시 돌 것
✈ 출력
// 모든 영화 다 출력
void print_all(struct movie movie_list[], size_t n_items)
{
for (size_t n = 0; n < n_items; ++n)
printf("%d : \"%s\", %.1f\n", n, movie_list[n].title, movie_list[n].rating);
}
// 인덱스에 해당하는 영화 출력
void print_an_item(struct movie movie_list[], size_t n_items)
{
printf("Input the index of item to print.\n");
int index = input_int();
if (index < n_items)
printf("%d : \"%s\", %.1f\n", index, movie_list[index].title, movie_list[index].rating);
else
printf("Invalid item.\n");
}
✈ 수정
// 인덱스에 해당하는 영화 수정
void edit_an_item(struct movie movie_list[], size_t n_items)
{
printf("Input the index of item to edit.\n");
int index = input_int();
if (index < n_items)
{
printf("%d : \"%s\", %.1f\n", index, movie_list[index].title, movie_list[index].rating);
printf("Input new title and press enter.\n");
printf(">> ");
int f = scanf("%[^\n]%*c", movie_list[index].title);
printf("Input new rating and press enter.\n");
printf(">> ");
f = scanf("%f%*c", &movie_list[index].rating);
printf("%d : \"%s\", %.1f\n", index, movie_list[index].title, movie_list[index].rating);
}
else
printf("Invalid item.\n");
}
✈ 추가
// 뒤에 추가
void add_an_item(struct movie movie_list[], size_t* ptr_n_items)
{
// LMAX 를 넘으면 추가할 수 없음(정적 배열은 크기 변경 및 이사 불가능)
if (*ptr_n_items == LMAX)
{
printf("No more space.\n");
return;
}
const int index = *ptr_n_items; // 맨 뒤에 추가
printf("Input title and press enter.\n");
printf(">> ");
int f = scanf("%[^\n]%*c", movie_list[index].title);
printf("Input rating and press enter.\n");
printf(">> ");
f = scanf("%f%*c", &movie_list[index].rating);
printf("%d : \"%s\", %.1f\n", index, movie_list[index].title, movie_list[index].rating);
*ptr_n_items += 1;
}
// 해당 인덱스 자리에 추가
void insert_an_item(struct movie movie_list[], size_t* ptr_n_items)
{
// LMAX 를 넘으면 추가할 수 없음(정적 배열은 크기 변경 및 이사 불가능)
if (*ptr_n_items == LMAX)
{
printf("No more space.\n");
return;
}
printf("Input the index of item to insert.\n");
int index = input_int();
// 한칸씩 다 뒤로 밀려나야 함
for (size_t i = *ptr_n_items - 1; i >= index; i--)
{
strcpy(movie_list[i + 1].title, movie_list[i].title);
movie_list[i + 1].rating = movie_list[i].rating;
}
printf("Input title and press enter.\n");
printf(">> ");
int f = scanf("%[^\n]%*c", movie_list[index].title);
printf("Input rating and press enter.\n");
printf(">> ");
f = scanf("%f%*c", &movie_list[index].rating);
printf("%d : \"%s\", %.1f\n", index, movie_list[index].title, movie_list[index].rating);
*ptr_n_items += 1;
}
✈ 삭제
// 해당 인덱스 자리 삭제
void delete_an_item(struct movie movie_list[], size_t* ptr_n_items)
{
printf("Input the index of item to delete.\n");
int index = input_int();
// 한 칸씩 다 앞으로 땡겨와야 함
for (int i = index; i < *ptr_n_items; i++)
{
strcpy(movie_list[i].title, movie_list[i + 1].title);
movie_list[i].rating = movie_list[i + 1].rating;
}
*ptr_n_items -= 1;
}
✈ 검색
// 해당 인덱스 검색
void search_by_name(struct movie movie_list[], size_t n_items)
{
printf("Please input title to search and press Enter.\n");
printf(">> ");
char title[TSIZE] = { 0, };
if (scanf("%[^\n]%*c", title) != 1)
{
printf("Wrong input.\n");
return;
}
int index = 0;
for (; index < n_items; ++index)
{
if (strcmp(movie_list[index].title, title) == 0)
break;
}
if (index == n_items)
printf("No movie found : %s\n", title);
else
printf("%d : \"%s\", %.1f\n", index, movie_list[index].title, movie_list[index].rating);
}
🚀 동적 할당 배열 사용
#define TSIZE 45 // 최대 45 글자(영화 제목 최대 45글자)
// #define LMAX 10
struct movie
{
char title[TSIZE];
float rating;
};
영화 구조체 배열을 동적으로 만들 것이기 때문에 최대 크기 LMAX
는 필요 없어졌다. 재할당 받으면 되기 때문이다.
int main()
{
struct movie* movie_list = NULL; // ☆☆
size_t n_items = 0;
read_file(&movie_list, &n_items); // 이중 포인터 사용 ☆
while (1)
{
printf("\n");
int s = input_menu();
switch (s)
{
case 1:
print_all(movie_list, n_items);
break;
case 2:
print_an_item(movie_list, n_items);
break;
case 3:
edit_an_item(movie_list, n_items);
break;
case 4:
add_an_item(&movie_list, &n_items);
break;
case 5:
insert_an_item(&movie_list, &n_items);
break;
case 6:
delete_an_item(movie_list, &n_items);
break;
case 7:
n_items = 0;
break;
case 8:
write_file(movie_list, n_items);
break;
case 9:
search_by_name(movie_list, n_items);
break;
case 10:
printf("Good bye\n");
free(movie_list); // ☆☆
n_items = 0; // ☆☆
exit(0);
default:
printf("%d is not implenmented. \n", s);
}
}
// ☆☆
free(movie_list);
n_items = 0;
}
movie_list
👉 영화 구조체 동적 할당 배열을 가리킬 포인터&movie_list
👉 포인터의 주소. 재할당 받으려면movie_list
포인터 값을 Call by Reference로 수정해야 하기 때문에 필요하다. 새롭게 할당받은 다른 사이즈의 동적 배열을 가리켜야 하기 때문에movie_list
값을 수정해야 하기 때문이다. 새로운 주소 담아야함.free(movie_list)
동적 메모리 해제 잊지 말기..
void read_file(struct movie** ptr_movie_list, size_t* ptr_n_items)
{
char filename[TSIZE] = { 0, };
printf("Please input filename to read and press Enter.\n");
printf(">> ");
if (scanf("%[^\n]%*c", filename) != 1)
{
printf("Wrong input. Terminating.\n");
exit(1);
}
FILE* file = fopen(filename, "r");
if (file == NULL)
{
printf("ERROR: Cannot open file. \n");
exit(1);
}
int num;
if (fscanf(file, "%d%*c", &num) != 1)
{
printf("ERROR: Wrong file format. \n");
exit(1);
}
// 이중 포인터 ☆☆
*ptr_movie_list = (struct movie*)malloc(sizeof(struct movie) * num);
if (*ptr_movie_list == NULL)
{
printf("Malloc failed.\n");
exit(1);
}
for (int n = 0; n < num; n++)
{
// 이중 포인터 ☆☆
if (fscanf(file, "%[^\n]%*c", (*ptr_movie_list)[*ptr_n_items].title) != 1 ||
fscanf(file, "%f%*c", &(*ptr_movie_list)[*ptr_n_items].rating) != 1)
{
printf("ERROR: Wrong file format. \n");
exit(1);
}
*ptr_n_items += 1;
}
assert(*ptr_n_items == num);
fclose(file);
printf("%d items have been read from the file.\n", *ptr_n_items);
}
void add_an_item(struct movie** ptr_movie_list, size_t* ptr_n_items)
{
// 이중 포인터 ☆☆
// 이사가기 위해 재할당 (배열 크기 꽉찼다고 추가 못한다는 그런거 없어짐)
struct movie* temp = *ptr_movie_list;
*ptr_movie_list = (struct movie*)malloc(sizeof(struct movie) * (*ptr_n_items + 1));
if (*ptr_n_items == NULL)
{
printf("No more space.\n");
return;
}
memcpy(*ptr_movie_list, temp, sizeof(struct movie) * *ptr_n_items);
free(temp);
const int index = *ptr_n_items;
printf("Input title and press enter.\n");
printf(">> ");
int f = scanf("%[^\n]%*c", (*ptr_movie_list)[index].title);
printf("Input rating and press enter.\n");
printf(">> ");
f = scanf("%f%*c", &(*ptr_movie_list)[index].rating);
printf("%d : \"%s\", %.1f\n", index, (*ptr_movie_list)[index].title, (*ptr_movie_list)[index].rating);
*ptr_n_items += 1;
}
*ptr_movie_list
👉movie_list
포인터를 Call by Reference 로 참조 中- 동적 배열을 사용하니까 추가, 삽입 과정 때도
LMAX
크기이면 NO SPACE 라 못한다는 처리를 해줄 필요가 없다. 재할당해서 더 큰 동적 배열을 할당 받으면 되기 때문이다.
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기