이름과 전화번호뿐만이 아니라 더 많은 항목을 추가하는 것이 목표이다. 이를 위해 구조체를 이용하는 방법을 생각해본다.
① 각 사람에 대해서 이름, 전화번호, 이메일 주소, 그리고 그룹(친구, 가족 등)을 지정할 수 있다.
(단 이름을 제외한 다른 항목들은 비워 둘 수도 있다.)
② 이름이 하나 이상의 단어로 구성될 수 있으며, 단어 사이에 여러 개의 공백이 있을 경우 한 칸의 공백으로 저장된다.
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 은 두 인자가 모두 널 캐릭터가 존재해야 제대로 작동하기 때문이다.
실습코드
#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;
}
'💡 자료구조' 카테고리의 다른 글
연결리스트 기본 개념 (0) | 2021.08.02 |
---|---|
전화번호부 v5.0 - 구조체에 대한 포인터, 동적 메모리 할당 (0) | 2021.07.11 |
전화번호부 v3.0 - 배열 재할당, 라인단위 입력과 문자열 tokenizing (0) | 2021.06.22 |
전화번호부 v2.0 - 파일을 저장하고 로드하기, 알파벳 순으로 정렬 (0) | 2021.06.19 |
전화번호부 v1.0 (0) | 2021.06.19 |