[자료구조 - C언어] 자료구조 제9강: 전화번호부 v4.0
2022. 6. 2. 17:09ㆍCS/자료구조
728x90
//공부 기록용 포스팅입니다. 틀린 부분이 있을 경우 댓글로 알려주시면 감사합니다! 😎
더 많은 항목, 구조체
- 업데이트되는 점
- 각 사람에 대해 이름, 전화번호, 이메일, 그룹을 지정할 수 있다.
- 이름을 제외한 항목은 비워둘 수 있다.
- 이름이 여러 단어일 수 있다.
- 단어 사이에 여러 개의 공백은 한 칸의 공백으로 저장된다. → 한 단어씩 읽기가 어렵다
- 각 사람에 대해 이름, 전화번호, 이메일, 그룹을 지정할 수 있다.
1. 구조체
- 항상 같이 붙어다녀야 하는 데이터를 별개의 변수들에 분산해서 저장하는 것은 바람직하지 않다.
- names, numbers, emails, goups로 서로 다른 4개의 배열에 분산해서 저장하는 것 X
- 구조체(structure): 항상 같이 붙어 다녀야 하는 데이터들을 하나의 그룹으로 묶어주는 역할
typedef struct person{
char * name;
char * number;
char * email;
char * group;
} Person;
Person directory[100];
- 구조체 struct person을 정의
- struct person은 name, number, email, group 4개의 필드로 구성
- char * name: 문자열이라는 의미
- struct person은 한 사람을 표현하기 위한 구조체 → 여러 사람을 표현하기 위해서는 배열을 사용
- struct person을 정의하는 동시에 typedef를 사용해서 Person으로 renaming
- Person 앞 까지가 정의, Person은 renaming
- typedef와 구조체의 정의를 한꺼번에 하면 structure tag인 person을 생략해도 된다
//typedef 선언을 사용하여 C에서 이미 정의된 형식이나 사용자가 선언한 형식에 대한 //보다 짧거나 의미 있는 이름을 생성할 수 있습니다. struct person1{ char * name; char * number; char * email; char * group; };
- struct person은 name, number, email, group 4개의 필드로 구성
2. 파일로부터 라인 단위로 읽기 - read_line(FILE *fp, char str[], int n)
int n = 0;
//version3와 비교했을 때 키보드만이 아니라 파일로부터도 값을 읽을 수 있게 되었다.
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;
}
- read_line의 첫 번째 매개변수인 FILE *fp
- read_line함수가 읽어올 데이터 파일의 파일 포인터를 매개변수로 주면 그 파일로부터 읽음
- 키보드에 대한 파일 포인터는 stdin - stdin을 매개변수로 주고 read_line을 호출하면 키보드를 읽음
read_line(stdin, char str[], int n)
- fgetc(fp): 파일로부터 한 character를 읽음
- (ch = fgetc(fp)) != '\n' && ch != EOF
- 키보드 입력과 다르게 파일의 맨 마지막 줄은 개행 문자가 없기 때문에 새로운 line이 시작되지 않는다 → EOF(End Of File)이 아닐 때까지 읽기
3. main()
int main(){
char command_line[BUFFER_LENGTH];
char *command, *argument;
char name_str[BUFFER_LENGTH];
while (1){
printf("$ ");
//read_line은 자신이 읽은 문자수를 리턴
//0보다 작으면 사용자가 아무것도 입력하지 않고 enter key를 누른 것
//키보드 입력은 stdin
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);
}
}
}
- read_line은 자신이 읽은 문자수를 리턴한다 → 0보다 작거나 같으면 사용자의 실수
- 키보드 입력을 받을 때에는 stdin을 첫 번째 매개변수로 사용
- 공백 단위로 strtok를 하면 첫 번째는 명령어
- 명령어가 NULL이 아니면 load함수 실행
3-1. load(char *fileName)
//매개변수는 파일 이름
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){
//fopen한 파일의 파일포인터가 파일로부터 한 번에 한 라인씩 데이터를 읽어옴
//더 이상 읽을게 없으면 break하고 while문을 빠져나감
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);
}
- name을 제외한 다른 항목들은 빈 값이 있을 수도 있다.
- 빈 값을 가지는 값은 → 한 개의 공백 문자로 이루어진 string이다.
파일 형식 - 존재하지 않는 항목의 경우 하나의 공백문자로 표시하고 - 이름, 번호, 이메일, 그룹은 '#' 문자를 사용해서 필드를 구분한다. - 모든 라인은 반드시 구분자로 끝난다. - 한 줄에 한 명씩 저장한다. JW#01012345678# #VBF JY# #JOOYEON@gamil.com# #
3-3. add(char * name, char * number, char * email, char * group)
void add(char * name, char * number, char * email, char * group){
//이름 순 정렬
int i = n-1;
while (i>=0 && strcmp(directory[i].name, name)>0){
//구조체 이기 때문에 name, number 배열을 따로 이동할 필요 없이 한 번에 이동
directory[i+1] = directory[i];
i--;
}
//strdup로 복제한 후 저장
//만약 없는 number, email, group라면 하나의 공백문자로 이루어진 string이 저장
directory[i+1].name = strdup(name);
directory[i+1].number = strdup(number);
directory[i+1].email = strdup(email);
directory[i+1].group = strdup(group);
n++;
printf("%s was added successfullly.\\n", name);
}
- directory[i+1] = directory[i]
- structure 간에도 치환할 수 있다.
- strdup로 복제한 후에 저장해야 한다.
4. main() - 2
while(1){
...
else if (strcmp(command, "add") == 0){
if(compose_name(name_str, BUFFER_LENGTH) <= 0){
printf("Name required.\\n");
continue;
}
handle_add(name_str);
}
else if (strcmp(command, "find") == 0){
if(compose_name(name_str, BUFFER_LENGTH) <= 0){
printf("Name required.\\n");
continue;
}
find(name_str);
}
4-1. main - add, find
else if (strcmp(command, "add") == 0){
if(compose_name(name_str, BUFFER_LENGTH) <= 0){
printf("Name required.\\n");
continue;
}
handle_add(name_str);
}
else if (strcmp(command, "find") == 0){
if(compose_name(name_str, BUFFER_LENGTH) <= 0){
printf("Name required.\\n");
continue;
}
find(name_str);
}
- add와 find 다음에는 단어가 여러 개 나올 수 있다.
add Kim Jung woo
- compose_name함수: 입력 command_line에서 불필요한 공백을 제거하고, 여러 개의 공백 → 1개 공백으로 축약
- compose_name은 나머지 토큰들을 merge 하여 이름을 구성하고, 구성된 이름의 길이를 return 한다.
- 즉, 리턴된 길이가 0보다 작거나 같다면 이름을 입력하지 않은 것이기 때문에 printf(”Name required.\n”)을 출력하고
- 그렇지 않다면 handle_add(name_str) 또는 find(name_str) 실행
- compose_name은 나머지 토큰들을 merge 하여 이름을 구성하고, 구성된 이름의 길이를 return 한다.
4-1-1. compose_name(char str[], int limit)
//compose_name은 나머지 토큰들을 merge하여 이름을 구성하고, 구성된 이름의 길이를 return
int compose_name(char str[], int limit){
char * ptr;
int length = 0;
//입력 라인의 두 번째 token
ptr = strtok(NULL, " ");
//length == 0: 사람 이름이 아예 없다
if(ptr == NULL)
return 0;
//그렇지 않다면 ptr은 이름의 첫 번째 token
//첫 번째 토큰인 ptr을 str에 카피
strcpy(str, ptr);
//이 시점의 length는 첫 번째 token의 길이로, 지금까지의 이름의 길이다.
length += strlen(ptr);
//이름이 여러 단어일 수 있으니 NULL이 될 때까지(= 문장의 끝에 도달할 때까지) 반복
while((ptr = strtok(NULL, " ")) != NULL){
//if문은 이름이 너무 길어서 버퍼 사이즈를 초과하는 경우에 대한 대책
//buffer의 overflow를 방지
//+1을 하는 이유는 맨 끝에 null character
if(length + strlen(ptr) + 1 < limit){
//이름-이름 사이의 공백을 넣어야 하기 때문에
//length++를 해서 그 다음 str[length]는 공백의 다음 자리에 위치
str[length++] = ' ';
//strcat은 두 단어가 null로 끝난다는 가정하에서만 정상적으로 동작하기 때문에
//공백 다음 자리에 null 넣어줌
//ex kim| |\0
str[length] = "\0";
strcat(str, ptr);
length += strlen(ptr);
}
}
return length;
}
4-1-2. handle_add(char* name)
void handle_add(char * name){
char number[BUFFER_LENGTH], email[BUFFER_LENGTH], group[BUFFER_LENGTH];
char empty[] = " "; //공백문자 하나로 구성된 string을 미리 만들어 놓음
printf(" Phone: ");
read_line(stdin, number, BUFFER_LENGTH);
printf(" Email: ");
read_line(stdin, email, BUFFER_LENGTH);
printf(" Group: ");
read_line(stdin, group, BUFFER_LENGTH);
//사용자가 입력을 한 경우는 strlen() > 0
//존재하지 않는 항목들을 하나의 공백문자로 구성된 문자열로 대체
//char * 로 타입 변환
add(name, (char *)(strlen(number) > 0 ? number : empty),
(char *)(strlen(email) > 0 ? email : empty),
(char *)(strlen(group) > 0 ? group : empty));
}
5. main() - 3
while(1){
else if (strcmp(command, "status") == 0){
status();
}
else if (strcmp(command, "delete") == 0){
if(compose_name(name_str, BUFFER_LENGTH) <= 0){
printf("Invalid arguments.\\n");
continue;
}
delete(name_str);
}
else if (strcmp(command, "save") == 0){
argument = strtok(NULL, " ");
//두 번째 token이 as여야 한다.
if (strcmp(argument, "as") != 0){
printf("Invalid argments.\\n");
continue;
}
//세 번째 token은 file 이름
argument = strtok(NULL, " ");
if(argument == NULL){
printf("Invalid arguments.\\n");
continue;
}
//모두 통과하면 save함수 호출
save(argument);
}
else if (strcmp(command, "exit") == 0)
break;
}
5-1. save(char * filename)
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);
}
5-2. search(char *name)
int search(char *name){
int i;
for(i=0;i<n;i++){
if(strcmp(name, directory[i].name) == 0)
return i;
}
return -1;
}
5-3. print_person(Person p)
//화면상에 출력
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);
}
5-4. delete(char * name)
void delete(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);
}
5-5. 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]);
}
5-6. status()
void status(){
int i;
for(i=0;i<n;i++)
print_person(directory[i]);
printf("Total %d persons.\\n", n);
}
6. 출력
$ add Kim Jung Woo
Phone:
Email:
Group: NCT
Kim Jung Woo was added successfullly.
$ status
Kim Jung Woo:
Phone:
Email:
Group: NCT
Total 1 persons.
$ add JOO YEON
Phone:
Email:
Group:
JOO YEON was added successfullly.
$ status
JOO YEON:
Phone:
Email:
Group:
Kim Jung Woo:
Phone:
Email:
Group: NCT
Total 2 persons.
$ find Kim Jung Woo
Kim Jung Woo:
Phone:
Email:
Group: NCT
$ delete JOO YEON
'JOO YEON was deleted successfully.
$ status
Kim Jung Woo:
Phone:
Email:
Group: NCT
Total 1 persons.
$
7. 전체 코드
더보기
// version4.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define CAPACITY 100
#define BUFFER_LENGTH 100
int read_line(FILE *fp, char str[], int n);
void add(char * name, char * number, char * email, char * group);
void find(char * name);
void status();
void delete(char * name);
void load(char *fileName);
void save(char *fileName);
int search(char *name);
int compose_name(char str[], int limit);
void handle_add(char * name);
typedef struct person{
char * name;
char * number;
char * email;
char * group;
} Person;
Person directory[CAPACITY];
void print_person(Person p);
int n = 0;
int main(){
char command_line[BUFFER_LENGTH];
char *command, *argument;
char name_str[BUFFER_LENGTH];
while (1){
printf("$ ");
//read_line은 자신이 읽은 문자수를 리턴
//0보다 작으면 사용자가 아무것도 입력하지 않고 enter key를 누른 것
//키보드 입력은 stdin
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);
}
else 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("Invalid arguments.\n");
continue;
}
delete(name_str);
}
else if (strcmp(command, "save") == 0){
argument = strtok(NULL, " ");
if (strcmp(argument, "as") != 0){
printf("Invalid argments.\n");
continue;
}
argument = strtok(NULL, " ");
if(argument == NULL){
printf("Invalid arguments.\n");
continue;
}
save(argument);
}
else if (strcmp(command, "exit") == 0)
break;
}
return 0;
}
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;
}
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);
}
void add(char * name, char * number, char * email, char * group){
//이름 순 정렬
int i = n-1;
while (i>=0 && strcmp(directory[i].name, name)>0){
//구조체 이기 때문에 name, number 배열을 따로 이동할 필요 없이 한 번에 이동
directory[i+1] = directory[i];
i--;
}
//strdup로 복제한 후 저장
directory[i+1].name = strdup(name);
directory[i+1].number = strdup(number);
directory[i+1].email = strdup(email);
directory[i+1].group = strdup(group);
n++;
printf("%s was added successfullly.\n", 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] = NULL;
strcat(str, ptr);
length += strlen(ptr);
}
}
return length;
}
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, email, BUFFER_LENGTH);
printf(" Group: ");
read_line(stdin, group, BUFFER_LENGTH);
add(name, (char *)(strlen(number) > 0 ? number : empty),
(char *)(strlen(email) > 0 ? email : empty),
(char *)(strlen(group) > 0 ? group : empty));
}
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);
}
int search(char *name){
int i;
for(i=0;i<n;i++){
if(strcmp(name, directory[i].name) == 0)
return i;
}
return -1;
}
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);
}
void delete(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);
}
void status(){
int i;
for(i=0;i<n;i++)
print_person(directory[i]);
printf("Total %d persons.\n", n);
}
void find(char *name){
int index = search(name);
if(index == -1)
printf("No person named '%s' exists.\n", name);
else print_person(directory[index]);
}
부경대학교 권오흠 교수님의 [c로 배우는 자료구조 및 여러 가지 예제 실습] 강의 정리입니다. 감사합니다.
https://www.youtube.com/watch?v=3YgmMH_gz5A&feature=emb_imp_woyt
728x90
'CS > 자료구조' 카테고리의 다른 글
[자료구조 - C언어] 자료구조 [1강-10강] 복습 및 정리 (0) | 2022.08.17 |
---|---|
[자료구조 - C언어] 자료구조 제10강: 전화번호부 v5.0 (0) | 2022.08.09 |
[자료구조 - C언어] 자료구조 제8강: 전화번호부 v3.0 (0) | 2022.05.24 |
[자료구조 - C언어] 자료구조 제5강: 전화번호부 v2.0 (0) | 2022.05.06 |
[자료구조 - C언어] 자료구조 제4강: 전화번호부 v1.0 (0) | 2022.04.28 |