Chater 1. 배열을 사용해 영화 평점 관리 프로그램 만들기

Date:     Updated:

카테고리:

태그:

홍정모 교수님의 강의 홍정모의 따라하며 배우는 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 이 리턴될 것이다.
  • 파일 읽어오기
  • num에 영화 갯수 받아오기
    • "%d%*c" : 정수만 입력 받고 그외 문자는 무시
  • movie_list 배열 원소들에 차곡차곡 영화들 저장
    • Call by Reference 로 n_items 원본을 변화시키기 위해 이를 참조하는 ptr_n_items으로 값을 조작하고 있다.


✈ 파일 쓰기

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 라 못한다는 처리를 해줄 필요가 없다. 재할당해서 더 큰 동적 배열을 할당 받으면 되기 때문이다.


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

DataStructure2 카테고리 내 다른 글 보러가기

댓글 남기기