이 글은 개인의 학습을 목적으로 정리한 글입니다. 이점 참고하고 읽어주세요;)
1) main.cpp
#pragma warning (disable:4996)
#include "string_tools.h"
#include "library.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_LENGTH 200
void handle_add();
void process_command();
void handle_load();
void handle_search();
void handle_play();
void handle_save();
void handle_remove();
int main()
{
initialize(); // Artist 배열을 초기화
handle_load();
process_command();
}
void handle_load()
{
char buffer[BUFFER_LENGTH];
printf("Data file name ? ");
if (read_line(stdin, buffer, BUFFER_LENGTH) <= 0) // 줄 단위로 입력을 받음(파일 포인터, 데이터를 읽어올 변수, 길이)
return; // 데이터 파일을 불러오지 않음
FILE* fp = fopen(buffer, "r"); // 파일포인터 fp에 buffer에 저장된 파일의 이름을 "읽기 모드"로 저장함
if (fp == NULL) {
printf("No such file exists.\n");
return;
}
load(fp); // 파일 포인터를 매개변수로 넘겨줌
fclose(fp); // 파일을 다 읽은 다음 파일을 닫아줌
}
void process_command()
{
char command_line[BUFFER_LENGTH];
char* command;
while (1) // 무한 루프를 돌면서 -> 프롬프트를 출력하며 사용자의 명령어를 입력받음
{
printf("$ "); // prompt
if (read_line(stdin, command_line, BUFFER_LENGTH) <= 0) // 줄 단위로 입력을 받음(파일 포인터, 데이터를 읽어올 변수, 길이)
continue; // 다시 명령 프롬프트를 호출
command = strtok(command_line, " "); // 공백문자 전까지 tokenize하여 저장
if (strcmp(command, "add") == 0)
handle_add();
else if (strcmp(command, "search") == 0)
handle_search();
else if (strcmp(command, "play") == 0)
handle_play();
else if (strcmp(command, "remove") == 0)
handle_remove();
else if (strcmp(command, "save") == 0){
char* tmp = strtok(NULL, " ");
if (strcmp(tmp, "as") != 0)
continue;
handle_save();
}
else if (strcmp(command, "status") == 0)
status();
else if (strcmp(command, "exit") == 0)
break;
}
}
void handle_remove() // 노래의 index를 tokenizing해서 해당 곡을 삭제
{
char* id_str = strtok(NULL, " "); // COMMAND 다음에 오는 번호를 읽어냄
int index = atoi(id_str);
remove(index);
}
void handle_save()
{
char* file_name = strtok(NULL, " ");
FILE* fp = fopen(file_name, "w"); // 파일을 열어서 새 노래를 저장
save(fp);
fclose(fp);
}
void handle_play()
{
char* id_str = strtok(NULL, " "); // COMMAND 다음에 오는 번호를 읽어냄
int index = atoi(id_str);
play(index);
}
void handle_search()
{
char name[BUFFER_LENGTH], title[BUFFER_LENGTH];
printf(" Artist: ");
if (read_line(stdin, name, BUFFER_LENGTH) <= 0) {
printf(" Artist name required \n");
return;
}
printf(" Title: "); // 1. 가수가 존재하지 않을 때 2. 가수가 존재할 때
int length = read_line(stdin, title, BUFFER_LENGTH);
if (length <= 0) {
search_song(name);
}
else
search_song(name, title); // 함수 오버로딩을 통해 search_song 호출
}
void handle_add()
{
char buffer[BUFFER_LENGTH];
char* artist=NULL, *title=NULL, * path=NULL;
printf(" Artist: ");
if (read_line(stdin, buffer, BUFFER_LENGTH) > 0) // 적어도 한 글자 이상 입력을 받음
artist = strdup(buffer); // 입력받은 이름을 복사해서 저장
printf(" Title: ");
if (read_line(stdin, buffer, BUFFER_LENGTH) > 0) // 적어도 한 글자 이상 입력을 받음
title = strdup(buffer); // 입력받은 곡을 복사해서 저장
printf(" Path: ");
if (read_line(stdin, buffer, BUFFER_LENGTH) > 0) // 적어도 한 글자 이상 입력을 받음
path = strdup(buffer); // 입력받은 경로를 복사해서 저장
printf("%s %s %s \n", artist, title, path); // 입력받은 정보를 출력
/* add to the music library */
add_song(artist, title, path);
}
2. library.h
#pragma once
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifndef LIBRARY_H
#define LIBRARY_H
typedef struct song Song;
typedef struct snode SNode;
typedef struct artist Artist;
struct song
{
Artist* artist;
char* title;
char* path;
int index;
};
struct snode
{
struct snode* next, * prev;
Song* song;
};
struct artist
{
char* name;
struct artist* next; // 이름의 초성이 동일한 가수들을 단방향 리스트로 연결
SNode* head, * tail;
};
void initialize();
Artist* create_artist_instance(char* name);
Artist* add_artist(char* name);
Song* create_song_instance(Artist* ptr_artist, char* title, char* path);
void insert_node(Artist* ptr_artist, SNode* ptr_snode);
void status();
void add_song(char* artist, char* title, char* path);
Artist* find_artist(char* name);
void print_artist(Artist* p);
void print_song(Song* ptr_song);
void load(FILE* fp);
void search_song(char* artist);
void search_song(char* artist, char* title);
void play(int index);
void save(FILE* fp);
void save_artist(Artist* p, FILE* fp);
void save_song(Song* ptr_song, FILE* fp);
void remove(int index);
SNode* find_song(int index);
void remove_snode(Artist* ptr_artist, SNode* ptr_snode);
void destroy_song(Song* ptr_song);
#endif // !1
3. library.cpp
#pragma warning (disable:4996)
#include "library.h"
#include "string_tools.h"
#define NUM_CHARS 256 // 2^8 = 256. 한 바이트가 가질 수 있는 최대 크기
#define SIZE_INDEX_TABLE 100
#define BUFFER_LENGTH 200
Artist* artist_directory[NUM_CHARS]; // 배열 한 칸당 artist 포인터 타입 자료형을 저장하는 배열
SNode* index_directory[SIZE_INDEX_TABLE];
int num_index = 0;
void initialize() // Aritst 배열과 INDEX 배열을 초기화
{
for (int i = 0; i < NUM_CHARS; i++)
artist_directory[i] = NULL;
for (int i = 0; i < SIZE_INDEX_TABLE; i++)
index_directory[i] = NULL;
}
void load(FILE* fp)
{
char buffer[BUFFER_LENGTH];
char* name, * title, * path;
while (1)
{
if (read_line(fp, buffer, BUFFER_LENGTH) <= 0)
break;
name = strtok(buffer, "#"); // 공백문자 전까지 tokenize하여 저장
if (strcmp(name, " ") == 0) // 이름이 존재하지 않는다면
name = NULL;
else
name = strdup(name); // 이름을 복제하여 저장
title = strtok(NULL, "#"); // 공백문자 전까지 tokenize하여 저장
if (strcmp(title, " ") == 0) // 이름이 존재하지 않는다면
title = NULL;
else
title = strdup(title); // 이름을 복제하여 저장
path = strtok(NULL, "#"); // 공백문자 전까지 tokenize하여 저장
if (strcmp(path, " ") == 0) // 이름이 존재하지 않는다면
path = NULL;
else
path = strdup(path); // 이름을 복제하여 저장
printf("%s %s %s\n", name, title, path);
add_song(name, title, path);
}
}
Artist* create_artist_instance(char* name) // Artist 객체를 생성하는 함수
{
Artist* ptr_artist = (Artist*)malloc(sizeof(Artist));
ptr_artist->name = name;
ptr_artist->head = NULL;
ptr_artist->tail = NULL;
ptr_artist->next = NULL;
return ptr_artist;
}
Artist* add_artist(char* name)
{
// Artist 객체를 만드는 일을 함수에게 맡김
Artist* ptr_artist = create_artist_instance(name);
Artist* p = artist_directory[(unsigned char)name[0]]; // 새롭게 만들 연결리스트의 첫 번째(배열의 이름)
Artist* q = NULL; // 연결 리스트에서 p를 따라가며 위치를 기억하는 용도
while (p != NULL && strcmp(p->name, name) < 0) // ordered list에서 p보다 작은 값이 나타날 때 까지
{
q = p;
p = p->next;
}
if (p == NULL && q == NULL) // 리스트가 비어있을 때. 즉 p가 유일한 노드가 됨
{
artist_directory[(unsigned char)name[0]] = ptr_artist;
}
else if (q == NULL) // p가 맨 앞에 위치함
{
ptr_artist->next = artist_directory[(unsigned char)name[0]];
artist_directory[(unsigned char)name[0]] = ptr_artist;
}
else
{
ptr_artist->next = p;
q->next = ptr_artist;
}
return ptr_artist;
}
Song* create_song_instance(Artist* ptr_artist, char* title, char* path)
{
Song* ptr_song = (Song*)malloc(sizeof(Song));
ptr_song->artist = ptr_artist;
ptr_song->title = title;
ptr_song->path = path;
ptr_song->index = num_index;
num_index++;
return ptr_song;
}
void insert_node(Artist* ptr_artist, SNode* ptr_snode)
{
SNode* p = ptr_artist->head;
// head에 저장된 값보다 ptr_snode에 저장된 제목이 클 때까지 while문 반복
while (p != NULL && strcmp(p->song->title, ptr_snode->song->title)<0)
p = p->next;
// p가 ptr_snode가 들어갈 자리보다 한 자리 뒤에 있기 때문에 p의 앞 위치에 새로운 snode를 대입
// 1. 연결리스트가 비어있을 때 2. 맨 앞에 대입할 때 3. 맨 뒤에 대입할 때 4. 노래 사이에 대입
if (ptr_artist->head == NULL) { // 1번 케이스
ptr_artist->head = ptr_snode;
ptr_artist->tail = ptr_snode; // 리스트의 유일한 곡이기 때문에 이중연결리스트의 head와 tail이 모두 ptr_snode
}
else if (p == ptr_artist->head) { // 2번 케이스.
ptr_snode->next = ptr_artist->head;
ptr_artist->head->prev = ptr_snode;
ptr_artist->head = ptr_snode; // 새로운 곡이 곡 리스트의 첫 번째 곡 자리에 위치함
}
else if(p==NULL){ // 3번 케이스.
ptr_snode->prev = ptr_artist->tail;
ptr_artist->tail->next = ptr_snode;
ptr_artist->tail = ptr_snode;
}
else { // 4번 케이스. p 앞에 노래를 추가
ptr_snode->next = p;
ptr_snode->prev = p->prev;
p->prev->next = ptr_snode;
p->prev = ptr_snode;
}
}
void insert_to_index_directory(Song* ptr_song) // 단방향 연결리스트
{
SNode* ptr_snode = (SNode*)malloc(sizeof(SNode));
ptr_snode->song = ptr_song;
ptr_snode->next = NULL;
ptr_snode->prev = NULL;
int index = ptr_song->index % SIZE_INDEX_TABLE; // 노래의 인덱스를 배열의 크기로 나눈 나머지
// add the snode into the single linked list at index_table[index]
SNode* p = index_directory[index]; // 해당 인덱스의 첫 번째 곡의 주소
SNode* q = NULL; // 단방향 연결리스트를 ordered list로 만들기 위한 조건
while (p != NULL && strcmp(p->song->title, ptr_song->title) < 0) { // 알파벳순 정렬
q = p; // p보다 한 순서 뒤에 있는 노드
p = p->next;
}
if (q == NULL) { // 첫 번째 위치에 저장
ptr_snode->next = p;
index_directory[index] = ptr_snode; // 해당 인덱스의 첫 번째 곡이 됨
}
else { // add after q
ptr_snode->next = p;
q->next = ptr_snode;
}
}
void add_song(char* artist, char* title, char* path)
{
// 가수가 이미 존재하는 경우
// 존재하지 않는다면 NULL return
Artist* ptr_artist = find_artist(artist); // 가수를 찾아서 Artist 포인터를 return
if (ptr_artist == NULL) // 만약 가수가 플레이리스트에 존재하지 않는다면
{
ptr_artist = add_artist(artist); // artist라는 이름을 가진 Artist 객체를 하나 추가하여 그 주소를 리턴
}
Song* ptr_song = create_song_instance(ptr_artist, title, path);
SNode* ptr_snode = (SNode*)malloc(sizeof(SNode));
ptr_snode->song = ptr_song; // SNode들끼리 이중연결리스트로 연결되는 구조.
ptr_snode->next = NULL; // 의도치 않은 실수를 방지하기 위해 구조체의 포인터 값은 null로 설정해주는 것도 좋음
// insert node
insert_node(ptr_artist, ptr_snode);
insert_to_index_directory(ptr_song);
}
Artist* find_artist(char* name)
{
Artist* p = artist_directory[(unsigned char)name[0]]; // 찾는 가수가 속한 초성 그룹의 첫 번째 가수. 배열의 이름이 곧 주소이기 때문!
/* name[0] : 00000000 ~ 11111111 */
// int에서 첫 비트 1은 음수를 나타냄
// 때문에 unsigned char로 형변환
// char = 8bit, int = 32bit
while (p != NULL && strcmp(p->name, name) < 0) // 알파벳 순으로 더 큰 이름이 나온다면 굳이 끝까지 읽을 필요x
p = p->next; // 이름을 찾을 때 까지 p는 다음 노드로 이동
if (p != NULL && strcmp(p->name, name) == 0)
return p; // p가 NULL이라면 NULL return, name을 찾으면 p를 return
else
return NULL;
}
void status()
{
for (int i = 0; i < NUM_CHARS; i++)
{
Artist* p = artist_directory[i]; // 배열의 이름이 곧 주소이기 때문에, 배열의
while (p!=NULL)
{
print_artist(p);
p = p->next;
}
}
}
void print_artist(Artist* p)
{
printf("name: %s\n", p->name);
SNode* ptr_snode = p->head;
while (ptr_snode != NULL)
{
print_song(ptr_snode->song);
ptr_snode = ptr_snode->next;
}
}
void print_song(Song* ptr_song)
{
printf(" %d: %s, %s\n", ptr_song->index, ptr_song->title, ptr_song->path);
}
void search_song(char* artist) {
Artist* ptr_artist = find_artist(artist);
if (ptr_artist == NULL) { // 가수가 존재하지 않음
printf("No such artist exists.\n");
return;
}
print_artist(ptr_artist);
}
SNode* find_snode(Artist* ptr_artist, char* title)
{
SNode* ptr_snode = ptr_artist->head;
while (ptr_snode != NULL && strcmp(ptr_snode->song->title, title) < 0) // ptr_snode의 노래 제목이 title보다 더 크더라도 더 이상 돌 이유가 없음
ptr_snode = ptr_snode->next;
if (ptr_snode != NULL && strcmp(ptr_snode->song->title, title) == 0) // 곡을 찾았을 때
return ptr_snode;
else // 곡을 찾지 못 했을 때
return NULL;
}
void search_song(char* artist, char* title) {
Artist* ptr_artist = find_artist(artist);
if (ptr_artist == NULL) { // 가수가 존재하지 않음
printf("No such artist exists.\n");
return;
}
SNode* ptr_snode = find_snode(ptr_artist, title); // 독립된 함수로 만들어서 사용함
if (ptr_snode != NULL){
printf("Found:\n");
print_song(ptr_snode->song);
}
else {
printf("No such song exists.\n");
return;
}
}
SNode* find_song(int index)
{
SNode* ptr_snode = index_directory[index%SIZE_INDEX_TABLE]; // 해당 index의 첫 번째 노래
while (ptr_snode != NULL && ptr_snode->song->index != index)
ptr_snode = ptr_snode->next;
return ptr_snode;
}
void play(int index)
{
SNode* ptr_snode = find_song(index);
if (ptr_snode == NULL) {
printf("No such song exists.\n");
}
printf("Play the song: %s \n", ptr_snode->song->title);
}
void save_song(Song* ptr_song, FILE* fp)
{
if (ptr_song->artist != NULL)
fprintf(fp, "%s#", ptr_song->artist->name); // 파일에 출력
else
fprintf(fp, " #");
if (ptr_song->title != NULL)
fprintf(fp, "%s#", ptr_song->title);
else
fprintf(fp, " #");
if (ptr_song->path != NULL)
fprintf(fp, "%s\n", ptr_song->path);
else
fprintf(fp, " #\n"); // 파일 저장 경로가 저장되어있지 않을 때
}
void save_artist(Artist* p, FILE* fp)
{
SNode* ptr_snode = p->head;
while (ptr_snode != NULL)
{
save_song(ptr_snode->song, fp);
ptr_snode = ptr_snode->next;
}
}
void save(FILE* fp)
{
for (int i = 0; i < NUM_CHARS; i++)
{
Artist* p = artist_directory[i]; // 배열의 이름이 곧 주소이기 때문에, 배열의
while (p != NULL)
{
save_artist(p, fp);
p = p->next;
}
}
}
void remove(int index)
{
SNode* q = NULL; // previous to p
SNode* p = index_directory[index % SIZE_INDEX_TABLE]; // 해당 index의 첫 번째 노래. head node
while (p != NULL && p->song->index != index) {
q = p;
p = p->next;
}
if (p == NULL) { // 처음부터 빈 list or 곡이 존재하지 않을 때
printf("No such song exists.\n");
return;
}
Song* ptr_song = p->song; // SNode와 Song 모두 삭제해야 함. Artist에서의 노래와 인덱스에서의 노래 모두를 삭제
if (q == NULL) { // remove first
index_directory[index % SIZE_INDEX_TABLE] = p->next; // p 다음 노래를 배열의 head로 저장
}
else { // remove after q
q->next = p->next;
}
free(p); // 삭제된 인덱스의 SNode를 삭제
Artist* ptr_artist = ptr_song->artist;
// find the snode in the double linked list of ptr_artist
SNode* ptr_snode = find_snode(ptr_artist, ptr_song->title);
if (ptr_snode == NULL){
printf("Not found snode - something wrong.\n");
return;
}
// Artist의 SNode 찾음
remove_snode(ptr_artist, ptr_snode);
destroy_song(ptr_song);
}
void remove_snode(Artist* ptr_artist, SNode* ptr_snode)
{
SNode* first = ptr_artist->head;
SNode* last = ptr_artist->tail;
if (first == ptr_snode && last == ptr_snode) { // SNode가 유일한 노드일 때
first = NULL;
last = NULL;
}
else if (first == ptr_snode) { // remove first
ptr_snode->next->prev = NULL;
first = ptr_snode->next;
}
else if (last == ptr_snode) { // remove last
ptr_snode->prev->next = NULL;
last = ptr_snode->prev;
}
else{ // in the middle
ptr_snode->next->prev = ptr_snode->prev;
ptr_snode->prev->next = ptr_snode->next;
}
free(ptr_snode);
}
void destroy_song(Song* ptr_song)
{
if (ptr_song->title != NULL)
free(ptr_song->title);
if (ptr_song->path != NULL)
free(ptr_song->path);
free(ptr_song);
}
4. string_tools.h
#pragma once // ifndef와 동일한 효과
#ifndef STRING_TOOLS_H
#define STRING_TOOLS_H
#include <stdio.h>
int read_line(FILE* fp, char str[], int n); // 헤더 파일에 프로토타입 저장
#endif
5. string_tools.cpp
#pragma warning (disable:4996)
#include "string_tools.h" // 소스 파일은 항상 자신의 헤더 파일을 include 해야함
int read_line(FILE* fp, char str[], int n) // (파일 포인터, 문자열을 저장할 배열, 문자열의 크기)
{
int ch, i = 0;
while ((ch = fgetc(fp)) != '\n' && ch != EOF) // fp에서 읽어오는 문자열이 엔터키도 아니고 파일의 끝도 아닐 때 까지 읽음
if (i < n - 1)
str[i++] = ch; // str[i]에 ch를 저장하고 i를 1 증가
str[i] = '\0'; // C-style 문자열. 배열의 마지막에 null 문자 저장
return i; // 몇 글자를 읽었는지 리턴
}
'Information Technology > C' 카테고리의 다른 글
[C언어] MP3 관리 프로그램(4) (0) | 2020.02.11 |
---|---|
[C언어] MP3 관리 프로그램(3) (0) | 2020.01.29 |
[C언어] MP3 관리 프로그램(2) (0) | 2020.01.29 |
[C언어] MP3 관리 프로그램(1) (0) | 2020.01.28 |
[C언어] 이중 연결 리스트 (0) | 2020.01.20 |