💡 자료구조

전화번호부 v4.0 - 더 많은 항목 추가하기, 구조체

ji-hyun 2021. 7. 11. 04:50

이름과 전화번호뿐만이 아니라 더 많은 항목을 추가하는 것이 목표이다. 이를 위해 구조체를 이용하는 방법을 생각해본다. 

 

① 각 사람에 대해서 이름, 전화번호, 이메일 주소, 그리고 그룹(친구, 가족 등)을 지정할 수 있다.

(단 이름을 제외한 다른 항목들은 비워 둘 수도 있다.)

 

② 이름이 하나 이상의 단어로 구성될 수 있으며, 단어 사이에 여러 개의 공백이 있을 경우 한 칸의 공백으로 저장된다.

 

 

 

 

 

directory.txt 의 파일내용은 다음과 같다.

# 가 구분자가 되고 # 사이의 공백은 해당 항목이 없음을 의미한다.

 

David K.#0517778888# #Friend#
Hong Gil-Dong# #henry@gmail.com# #
John Doe# # # #
Sean#01067356574#sean@naver.com#colleague#

 

 

 

 

 

 

구조체를 사용하는 이유는 다음과 같다.

  • 항상 같이 붙어다녀야 하는 데이터를 별개의 변수들에 분산해서 저장하는 것은 바람직하지 않다.
  • 어떤 한 사람의 이름, 전화번호, 이메일 주소 등이 그런 예이다.
  • C 언어에서는 이런 경우 구조체(structure)를 사용한다.

 

 

 

 

 

 

구조체 기본 특징

 

① 정의한 구조체를 사용하려면 그 구조체를 변수로 선언해야 한다.

-> struct 구조체명 변수명;

 

 

② 구조체를 초기화시킬 수도 있다. -> { } 중괄호를 사용해야 한다. 

ex.

struct car mycar = {"bmw", "7시리즈", 10000}

 

 

③ 문자배열을 대입하려면 strcpy 함수를 써줘야 한다.

ex.

 

struct car mycar;  

 

strcpy(mycar.maker, "bmw");               /* mycar.maker = "bmw" 불가 */

strcpy(mycar.model, "7시리즈");

mycar.price = 10000

 

 

 

 

 

 

구조체와 함수

구조체는 함수의 인수로도 사용이 가능하고 함수에서 반환값으로 반환될 수 있다.

 

구조체는 복사되어 함수로 전달된다.

함수에는 구조체의 복사본이 매개변수로 전달되므로 함수 안에서 매개변수의 값이 변경되더라도 원본 구조체에 영향을 주지 않는다. 단점으로 만약 구조체의 크기가 클 경우에는 구조체의 멤버를 일일이 복사하여야 하므로 상당한 시간이 소요된다. 따라서 이 경우에는 구조체를 직접 전달하거나 반환하는 것보다는 구조체의 포인터를 사용하는 것이 바람직하다.

 

 

아래는 구조체가 함수로 전달될 때의 경우이다.

 

int main(){
	struct point a = {1, 2 };
    struct point b = {3, 4 };
    
    if(equal(a, b) == 1){   /* 같으면 1을 반환, 다르면 0을 반환 */
    	printf("같은점\n");
    }
    else{
    	printf("다른점\n");
    }
}


int equal(struct point p1, struct point p2){
	if( p1.x == p2.x && p1.y == p2.y )
    	return 1;
    else
    	return 0;
 }

 

 

 

 

위 코드문을 "구조체의 포인터를 인수로 보내는 방법"으로 수정해보자.

 

int main(){
	struct point a = {1, 2 };
    struct point b = {3, 4 };
    
    if(equal(&a, &b) == 1){   /* 같으면 1을 반환, 다르면 0을 반환 */
    	printf("같은점\n");
    }
    else{
    	printf("다른점\n");
    }
}


int equal(struct point *p1, struct point *p2){
	if( p1->x == p2->x && p1->y == p2->y )
    	return 1;
    else
    	return 0;
 }

 

 

 

 

 

typedef

C언어에서는 프로그래머가 자신이 필요한 자료형을 정의하여서 사용할 수 있다. typedef 은 말 그대로, 새로운 자료형(type)을 정의(define)하는 것이다. 이 키워드는 C의 기본 자료형을 확장시키는 역할을 한다. 즉 사용자가 새로운 자료형을 추가할 수 있도록 한다.

 

typedef 기존자료형 새로운자료형;

 

위와 같이 선언한 후 새로운자료형을 기본자료형 처럼 사용할 수 있다.

 

typedef struct point POINT;

POINT a, b;

 

 

 

 

 

여기서 구조체의 선언과 typedef을 같이 사용할 수도 있다.

 

typedef struct point{
	int x;
    	int y;
} POINT;

 

 


#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define CAPACITY 100
#define BUFFER_LENGTH 100

typedef struct person {
 char *name;
 char *number;
 char *email;
 char *group;
} Person;

Person directory[CAPACITY];
int n = 0;        /* number of people in phone directory */

 

자료구조는 위와 같이 구조체로 선언되어 진다.

 

 

 

 

 

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을 수정하여 키보드만이 아니라 파일로부터도 읽을 수 있도록 하였다.

 

fgetc 함수설명

첫번째 인자 : 접근할 파일포인터 입니다.

반환형 : 읽어온 문자를 반환합니다. int 타입입니다. char 타입으로 받으면 아스키 코드 표를 따라서 문자로 인식하게 됩니다.

파일의 끝까지 가게 되면 EOF를 반환합니다. EOF는 언어상에서 이미 -1을 #define 해놓은 것 입니다. 파일의 끝일땐 저걸 반환한다고 미리 정해놓은 것 입니다.

 


출처: https://blockdmask.tistory.com/403 [개발자 지망생]

 

 

 

 

 

 

 

 

int compose_name(char str[], int limit) {
	char * ptr;
	int length = 0;

	ptr = strtok(NULL, " ");   /* ptr은 이름의 첫번째 단어이다. */
	if (ptr == NULL)
		return 0;

	strcpy(str, ptr);          /* 이름의 첫번째 단어를 str로 복사시킨다. */
	length += strlen(ptr);

	while ((ptr = strtok(NULL, " ")) != NULL) {    /* 다음 단어가 null 일때까지 시행한다. */

		if (length + strlen(ptr) + 1 < limit) {    
			str[length++] = ' ';         /* 토큰과 토큰 사이에 하나의 공백문자를 삽입한다. */
			str[length] = '\0';          /* 공백 문자 뒤에 널캐릭터가 삽입된다 */
			strcat(str, ptr);             /* str 뒤에 ptr을 붙인다. */
			length += strlen(ptr);
		}
	}
	return length;
}

 

두번째 if 문에서 토큰과 토큰 사이에 공백 문자를 삽입하고 그 이후에 널 캐릭터를 붙여준다. 다시 말해서 length 뒤에 ++이 붙어져 있으므로 공백문자 뒤에 널 캐릭터가 붙는다.

 

이런 작업을 하는 이유는 strcat 은 두 인자가 모두 널 캐릭터가 존재해야 제대로 작동하기 때문이다.

 

 

 

 

 

 

 

 

전화번호부 v4.0

 

 

 

 

 

실습코드

 

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define CAPACITY 100
#define BUFFER_LENGTH 100

typedef struct person {
	char *name;
	char *number;
	char *email;
	char *group;
} Person;

Person directory[CAPACITY];
int n = 0;        /* number of people in phone directory */




void add(char *name, char *number, char *email, char *group) {
	int i = n - 1;

	while (i >= 0 && strcmp(directory[i].name, name) > 0) {
		directory[i + 1] = directory[i];
		i--;
	}

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


int compose_name(char str[], int limit) {
	char * ptr;
	int length = 0;

	ptr = strtok(NULL, " ");   /* ptr은 이름의 첫번째 단어이다. */
	if (ptr == NULL)
		return 0;

	strcpy(str, ptr);          /* 이름의 첫번째 단어를 str로 복사시킨다. */
	length += strlen(ptr);

	while ((ptr = strtok(NULL, " ")) != NULL) {    /* 다음 단어가 null 일때까지 시행한다. */

		if (length + strlen(ptr) + 1 < limit) {    
			str[length++] = ' ';         /* 토큰과 토큰 사이에 하나의 공백문자를 삽입한다. */
			str[length] = '\0';            /* 공백 문자 뒤에 널캐릭터가 삽입된다 */
			strcat(str, ptr);             /* str 뒤에 ptr을 붙인다. */
			length += strlen(ptr);
		}
	}
	return length;
}


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





char *strdup(char *s)
{
	char *p;
	p = (char *)malloc(strlen(s) + 1);
	if (p != NULL)
		strcpy(p, s);
	return p;
}



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

void remover(char *name) {
	int i = search(name); /* returns -1 if not exists */
	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 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));    /* 존재하지 않는 항목들을 하나의 공백문자로 구성된 문자열로 대체한다. */

	printf("%s was added successfully.\n", name);
}

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, " ");      /* read 명령에서 두번째 토큰은 파일명이다. */

			if (argument == NULL) {
				printf("Invalid arguments.\n");
				continue;
			}

			load(argument);    /* 파일명을 인자로 주면서 load를 호출한다. */
		}
		else if (strcmp(command, "add") == 0) {

			if (compose_name(name_str, BUFFER_LENGTH) <= 0) {   /* compose_name 함수는 나머지 토큰들을 merge하여 이름을 구성한다. */
				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;          /* 이름이 존재하지 않으면 반복문의 처음으로 되돌아간다. */
			}

			remover(name_str);
		}
		else if (strcmp(command, "save") == 0) {

			argument = strtok(NULL, " ");   /* save 뒤 명령어들을 토크나이징한다. */

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