ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [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]);
    }

    댓글

Designed by Tistory.