-
[C언어/자료구조] 전화번호부 v4.0 (인프런)CS/자료구조&알고리즘 2020. 1. 13. 07:00
※ 인프런 무료강좌 C로 배우는 자료구조(권오흠 교수님)를 보고 개인적인 복습을 위해 정리한 내용입니다.
전화번호부 v4.0의 개선사항
https://skm1104.tistory.com/29 전화번호부 v1.0
https://skm1104.tistory.com/30 전화번호부 v2.0
https://skm1104.tistory.com/31 전화번호부 v3.0
v1.0 ~ v3.0 에서는 이름, 전화번호를 각각 배열에 저장하는 자료구조를 사용했다.
v4.0에서는 이름, 전화번호 두 가지 항목 외에 더 많은 항목을 추가할 수 있도록
구조체를 사용한 새로운 버전을 만들 것이다.
새로운 버전에서는 각 사람에 대해서 이름, 전화번호, 이메일 주소, 그룹을 지정할 수 있다.
이름을 제외한 다른 항목들은 비워둘 수도 있다. (이름 : 필수항목, 나머지 : 선택입력)
이름이 하나 이상의 단어로 구성될 수 있고, 단어 사이에 여러 개의 공백이 있을 경우
한 칸의 공백으로 저장된다.
또한 데이터를 키보드 입력 뿐만 아니라 파일 형태로도 읽어오고 저장할 수 있다.
파일 형식
이름#전화번호#이메일주소#그룹#
이름#전화번호#이메일주소#그룹#
이름#전화번호#이메일주소#그룹#
...
한 줄에 한 명씩 저장하고, '#' 문자를 필드 간의 구분자로 사용한다.
존재하지 않는 항목의 경우 하나의 공백문자로 표시한다.
그리고 모든 라인은 구분자로 끝난다.
구조체 (structure)
한 사람의 이름, 전화번호, 이메일 주소 등 항상 같이 붙어다녀야 하는 데이터를 별개의 변수에 분산해서 저장하는 것보다는 구조체로 묶어서 저장하는 것이 바람직하다.
#include <studio.h> #include <string.h> #include <stdlib.h> #define CAPACITY 100 #define BUFFER_LENGTH 100 typedef struct person { /*person 생략가능 */ char *name; char *number; char *email; char *group; } Person; /* 구조체 struct person을 정의 & Person으로 renaming (structure tag인 person 생략 가능) struct person -> Person */ Person directory[CAPACITY]; /* phone directory */ int n = 0; /* number of persons */
변경된 함수들
main 함수
int main() { char command_line[BUFFER_LENGTH]; char *command, *argument; char name_str[BUFFER_LENGTH]; while (1) { printf("$ "); /* 아무것도 입력되지 않은 경우 예외처리 */ if (read_line(stdin, command_line, BUFFER_LENGTH) <=0) continue; command = strtok(command_line, " "); /* 첫번째 토큰 읽기 */ /* 첫번째 토큰 확인하고 적절한 함수 등 실행 */ if (strcmp(command, "read") == 0) { argument = strtok(NULL, " "); if (argument == NULL) { printf("Invalid arguments. \n"); continue; } load(argument); } else if (strcmp(command, "add") == 0) { if (compose_name(name_str, BUFFER_LENGTH) <=0) { printf("Name required. \n"); continue; } handle_add(name_str); } if (strcmp(command, "find") == 0) { if (compose_name(name_str, BUFFER_LENGTH) <=0) { printf("Name required. \n"); continue; } find(name_str); } else if (strcmp(command, "status") == 0) { status(); } else if (strcmp(command, "delete") == 0) { if (compose_name(name_str, BUFFER_LENGTH) <=0) { printf("Name required. \n"); continue; } remove(name_str); } else if (strcmp(command, "save") == 0) { argument = strtok(NULL, " "); if (strcmp(argument, "as") != 0) { printf("Invalid arguments. \n"); continue; } argument = strtok(NULL, " "); if (argument == NULL) { printf("Invalid arguments. \n"); continue; } save(argument); } else if (strcmp(command, "exit") == 0) { break; } } return 0; }
read 명령 처리에 필요한 함수들
read_line 함수
파일로부터 라인 단위로 읽기
int read_line(File *fp, char str[], int n) { int ch, i = 0; while((ch = fgetc(fp)) != '\n' && ch != EOF) { if (i < n) str[i++] = ch; str[i] = '\0'; return i; } }
파일이나 키보드 입력을 읽어서 배열에 라인 단위로 저장하는 기능의 함수이다.
키보드 입력을 읽을 때는 첫번째 매개변수 fp로 stdin을 넘겨주면 된다.
(stdin : 표준입력파일, 키보드 입력)
str[]는 읽은 데이터를 저장할 배열, n은 배열의 크기이다. (n을 초과하면 더 이상 저장되지 않는다.)
getchar()함수 대신 fgetc() 함수(파일버전 getchar 함수)로 문자를 하나씩 읽는다.
모든 줄이 '\n'으로 끝나는 키보드 입력과 달리,
파일의 맨 마지막 줄은 '\n'으로 끝나지 않기 때문에 EOF 조건을 추가해야 한다.
load 함수
void load(char *fileName) { char buffer[BUFFER_LENGTH]; char *name, *number, *email, *group; /* 파일 열기 */ FILE *fp = fopen(fileName, "r"); if (fp == NULL) { printf("Open failed. \n"); return; } /* 파일 읽기 & 저장하기 */ while (1) { if (read_line(fp, buffer, BUFFER_LENGTH) <= 0) break; name = strtok(buffer, "#"); number = strtok(NULL, "#"); email = strtok(NULL, "#"); group = strtok(NULL, "#"); add(name, number, email, group); } /* 파일 닫기 */ fclose(fp); }
read_line 함수로 입력된 데이터를 line 단위로 배열 buffer에 저장한다.
만약 read_line 함수의 리턴값이 0이하라면 (i ≤0)
더 이상 읽을 라인이 없는 것이므로 while문을 종료하고 빠져나간다. (break;)
buffer에 담긴 데이터를 strtok 함수로 나눠(구분자 "#" 이용) 각각 name, number, email, group에 저장한 후
add 함수에 넘겨준다.
사용이 끝난 파일은 닫아준다.
add 명령 처리에 필요한 함수들
add 명령을 처리할 때는,
이름에 있는 불필요한 공백을 제거한 다음 이름을 저장해야하기 때문에
따로 compose_name이라는 함수를 만들어서 처리해준다.
add 명령 외에도 find, delete 명령에서도 명령어에 이름이 포함되기 때문에
마찬가지로 compose_name을 통해 먼저 이름의 불필요한 공백들을 제거하고 나서
정돈된 이름을 가지고 검색, 삭제 등 다음 작업을 진행해야한다.
compose_name 함수
int compose_name(char str[], int limit) { char *ptr; int length = 0; ptr = strtok(NULL, " "); if (ptr == NULL) return 0; strcpy(str, ptr); length += strlen(ptr); while ((ptr = strtok(NULL, " ")) != NULL) { if (length + strlen(ptr) + 1 < limit) { str[length++] = ' '; str[length] = '\0'; strcat(str, ptr); length += strlen(ptr); } } return length; }
이름의 앞뒤에 있는 불필요한 공백과, 단어와 단어 사이 두 개 이상의 공백은 하나의 공백으로 축약한다.
첫번째 토큰은 이미 잘라서 command에 저장했기 때문에
strtok 함수로 두번째 토큰부터 잘라내기 시작한다. ( strtok(NULL, " ") ) //두번째 토큰부터는 매개변수로 NULL을 넘겨줘야 함
만약 두번째 토큰이 존재하지 않는다면 0을 리턴하며 함수를 종료시킨다.
두번째 토큰이 있다면 str에 복사해 저장하고, ( strcpy(str, ptr) )
str의 길이를 나타내는 변수인 length의 값을 ptr의 길이만큼 증가시킨다. ( length += strlen(ptr) )
이렇게 저장한 두번째 토큰은 이름의 첫단어였을 것이다. (ex- Ariana Grande의 Ariana)
이름의 두번째 단어부터(=세번째 토큰부터)는 while문을 돌면서 저장한다.
strtok을 반복 호출해 계속해서 이름의 남은 단어들을 하나씩 잘라내고, (더 이상 남은 단어가 없다면 while문 종료)
이름의 첫단어가 저장되어있는 str의 끝에 공백(' ')을 추가하고, ( str[length++] = ' '; )
그 다음 자리에 '\0'를 추가한다. ( str[length] = '\0'; )
그리고나서 이름의 다음 단어(ptr)를 strcat 함수를 이용해 기존 str에 연결해준다. ( strcat(str, ptr); )
length는 ptr의 길이만큼 증가시켜준다.
이 과정은 기존 이름 [ str의 길이(length) + 추가할 단어 ptr의 길이(strlen(ptr)) + 1 ]이 limit보다 작을 때에만 수행된다.
입력된 이름을 모두 올바른 형식으로 저장한 후에는, (혹은 limit를 초과해 더 이상 이름을 저장할 수 없을 때에는)
이름의 길이 length를 리턴하며 함수를 종료한다.
handle_add 함수
void handle_add(char *name) { char number[BUFFER_LENGTH], email[BUFFER_LENGTH], group[BUFFER_LENGTH]; char empty[] = " "; printf(" Phone: "); read_line(stdin, number, BUFFER_LENGTH); printf(" Email: "); read_line(stdin, number, BUFFER_LENGTH); printf(" Group: "); read_line(stdin, number, BUFFER_LENGTH); add(name, (char *)(strlen(number)>0 ? number : empty), (char *)(strlen(email)>0 ? email : empty), (char *)(strlen(group)>0 ? group : empty)); }
사용자가 "add name" 과 같이 명령을 했다면,
phone, email, group 등 다른 정보도 입력을 받아야 한다.
따라서 handle_add 함수를 실행해 다른 정보들을 입력받아 add함수로 넘겨줄 것이다.
입력된 정보가 없는 항목은 하나의 공백문자로 구성된 문자열을 대신 저장한다.
이 때 number, email, group은 handle_add 함수 안에서 선언한 변수들이므로
함수가 종료되면 사라진다.
따라서 동적 메모리할당을 통해 위 변수들이 사라지지 않도록 저장하면서 add함수로 넘겨준다.
이렇게 사용자가 add 명령을 하면,
compose_name 함수로 불필요한 공백을 제거하고 이름을 정돈시키고
handle_add 함수로 이름 외에 다른 정보들을 사용자로부터 입력받아 이름과 함께
add 함수로 넘겨주고,
add 함수에서 이 정보들을 구조체에 저장하게 된다.
그 외의 명령을 처리하는 함수들
save 함수
void save(char *fileName) { int i; FILE *fp = fopen(fileName, "w"); if (fp == NULL) { printf("Open failed.\n"); return; } for (i=0; i<n; i++) { fprintf(fp, "%s#", directory[i].name); fprintf(fp, "%s#", directory[i].number); fprintf(fp, "%s#", directory[i].email); fprintf(fp, "%s#\n", directory[i].group); } fclose(fp); }
search 함수
int search(char *name) { int i; for (i=0; i<n; i++) { if (strcmp(name, directory[i].name)==0) { return i; } } return -1; }
찾는 이름이 존재하는 배열의 인덱스를 리턴한다.
만약 찾는 이름이 없다면 -1을 리턴한다.
print_person 함수
void print_person(Person p) { printf("%s:\n", p.name); printf(" Phone: %s\n", p.number); printf(" Email: %s\n", p.email); printf(" Group: %s\n", p.group); }
remove 함수
void remove(char *name) { int i = search(name); if (i == -1) { printf("No person named '%s' exists.\n", name); return; } int j = i; for (; j<n-1; j++) { directory[j] = directory[j+1]; } n--; printf("'%s' was deleted successfully. \n", name); }
directory[j] = directory[j+1];
구조체 변수 간에는 치환연산이 지원되기 때문에, 멤버 항목들을 따로따로 치환할 필요가 없다.
status 함수
void status() { int i; for (i=0; i<n; i++) print_person(directory[i]); printf("Total %d persons. \n", n); }
find 함수
void find(char *name) { int index = search(name); if (index == -1) printf("No person named '%s' exists.\n", name); else print_person(directory[index]); }
'CS > 자료구조&알고리즘' 카테고리의 다른 글
[C언어/자료구조] 연결리스트 - 다항식 (0) 2020.01.19 [C언어/자료구조] 연결리스트(Linked list) 기본 연산 예제 (인프런) (0) 2020.01.17 [C언어/자료구조] 연결리스트(Linked list) 개념 (인프런) (0) 2020.01.16 [C언어/자료구조] 전화번호부 v5.0 (인프런) (0) 2020.01.14 [C언어/자료구조] 전화번호부 v3.0 (인프런) (0) 2020.01.12