[자료구조 - C언어] 자료구조 제9강: 전화번호부 v4.0

2022. 6. 2. 17:09CS/자료구조

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;
        };

 

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) 실행

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