-
자료구조 8강 (구조체 포인터, 동적 메모리 할당)자료구조 2022. 2. 18. 17:53
구조체 포인터 배열을 사용
-> 구조체 배열로 v4.0에서의 add와 remove를 할 경우 복사해야하는 데이터의 양이 많기 때문
->구조체를return하거나 함수의 매개변수로 받을 때 데이터가 모두 복사되어 데이터 사용량이 많아지기 때문
화살표 연산자
printf(" Group: %s\n", (*p).group);
printf(" Group: %s\n", p->group);
위의 둘은 완전히 동일한 의미를 가짐.
->연산자를 사용하는게 좋음
/* * 2022-02-18 * phonebook05.c * 구조체 포인터, 동적 메모리 할당 * - main함수는 처음에 init()을 호출해주는 것을 제외하면 v4와 동일 * - read_line, compose_name은 v4와 동일 * - 변경된 add함수에 맞게 수정 * -- save, search, print_person, status, find, handle_add * -- v4와 달리 존재하지 않는 항목들은 NULL로 저장되어 있다. */ #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> #include <stdlib.h> #define INIT_CAPACITY 100 #define BUFFER_LENGTH 100 /*구조체*/ typedef struct person { char* name; char* number; char* email; char* group; } Person; Person** directory; //배열의 크기를 나중에 키우거나 하고싶다면 동적메모리 할당해야 하므로 처음부터 크기를 정해서 선언하지 않음 int capacity; int n; //저장되어있는 사람의 수 /*함수*/ void init(); void status(); void print_person(Person* p); void load(char* fileName); int read_line(FILE* fp, char str[], int n); void handle_add(char* name); void add(char* name, char* number, char* email, char* group); void reallocate(); void remover(char* name); void release_person(Person* p); int compose_name(char str[], int limit); int search(char* name); void save(char* fileName); void find(char* name); int main() { init(); 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); } 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, " "); 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; } void init() { directory = (Person**)malloc(INIT_CAPACITY * sizeof(Person*)); capacity = INIT_CAPACITY; n = 0; } void status() { int i; for (i = 0; i < n; i++) print_person(directory[i]); //구조체의 주소값만 전달되기 때문에 복사되는 데이터의 양이 감소 printf("Total %d persons.\n", n); } void print_person(Person* p) { printf("%s:\n", (*p).name); printf(" Phone: %s\n", (*p).number); //p가 포인터이므로 *p로 해야 함 printf(" Email: %s\n", (*p).email); //. 이 * 보다 우선순위가 높으므로 (*p)를 해야 함. printf(" Group: %s\n", (*p).group); //만약 *p.name을 한다면 p는 구조체가 아닌 포인터이므로 오류 //printf(" Group: %s\n", p->group); 로 표현할 수 있음 } void load(char* fileName) { char buffer[BUFFER_LENGTH]; char* name, * number, * email, * group; char* token; 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, "#"); token = strtok(NULL, "#"); if (strcmp(token, " ") == 0) number = NULL; //v.4 보다 코드의 길이는 길어졌지만, 저장할 데이터가 없을 땐 NULL값을 저장는게 정석적임 else number = _strdup(token); //v.4 에서는 add함수에서 strdup을 했지만 v.5에서는 load함수에서 함. token = strtok(NULL, "#"); if (strcmp(token, " ") == 0) email = NULL; else email = _strdup(token); token = strtok(NULL, "#"); if (strcmp(token, " ") == 0) group = NULL; else group = _strdup(token); add(_strdup(name), number, email, group); } fclose(fp); } 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 handle_add(char* name) { char* token[BUFFER_LENGTH]; //char number[BUFFER_LENGTH], email[BUFFER_LENGTH], group[BUFFER_LENGTH]; char* number, * email, * group; //char* empty = '\0'; printf(" Phone: "); read_line(stdin, token, BUFFER_LENGTH); number = _strdup(token); printf(" Email: "); read_line(stdin, token, BUFFER_LENGTH); email = _strdup(token); printf(" Group: "); read_line(stdin, token, BUFFER_LENGTH); group = _strdup(token); //이게맞나? add(_strdup(name), (char*)(strlen(number) > 0 ? number : NULL), //존재하지 않는 항목들을 하나의 공백문자로 구성된 문자열로 대체한다. (char*)(strlen(email) > 0 ? email : NULL), (char*)(strlen(group) > 0 ? group : NULL)); } void add(char* name, char* number, char* email, char* group) { if (n >= capacity) reallocate(); //배열의 크기 두 배로 늘려줌 int i = n - 1; while (i >= 0 && strcmp(directory[i]->name, name) > 0) { directory[i + 1] = directory[i]; //주소가 복사됨 i--; } directory[i + 1] = (Person*)malloc(sizeof(Person)); //directory[i+1]의 자리가 비었으므로 공간을 할당해야 함 directory[i + 1]->name = name; directory[i + 1]->number = number; directory[i + 1]->email = email; directory[i + 1]->group = group; n++; } void reallocate() { capacity *= 2; Person** tmp = (Person**)malloc(capacity * sizeof(Person*)); for (int i = 0; i < n; i++) tmp[i] = directory[i]; free(directory); //malloc으로 할당받은 메모리는 필요없어지면 free시켜줘야 함. directory = tmp; } void remover(char* name) { int i = search(name); if (i == -1) { //일치하는 이름 없을 때 printf("No person named '%s' exists.\n", name); return; } Person* p = directory[i]; //*p도 directory[i]를 가리키게 함. for (int j = i; j < n - 1; j++) directory[j] = directory[j + 1]; n--; release_person(p); printf("'%s' was deleted successfully. \n", name); } void release_person(Person* p) { //strdup이 malloc을 써서 메모리를 할당했기 때문에 구조체의 요소까지 모두 free시켜줘야 함 free(p->name); if (p->number != NULL) free(p->number); if (p->email != NULL) free(p->email); if (p->group != NULL) free(p->group); free(p); } 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); } } return length; } int search(char* name) { int i; for (i = 0; i < n; i++) { if (strcmp(name, (*directory[i]).name) == 0) { return i; } } return -1; } 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#", directory[i]->group); } fclose(fp); } void find(char* name) { int index = search(name); if (index == -1) printf("No person named '%s' exists.\n", name); else print_person(directory[index]); }
add함수에서 구조체들의 포인터의 배열 directory[i+1] 칸은 빈 칸이 되므로 해당 칸에 새로운 사람의 정보를 저장하기 위해서는 동적 메모리 할당으로 구조체 객체를 만들어야함.
'자료구조' 카테고리의 다른 글
*자료구조 제 10강(연결리스트-다항식) (0) 2022.02.25 자료구조 제 9강 (연결리스트-개념과 기본 동작들) (0) 2022.02.22 자료구조 제 7강 (구조체) (0) 2022.02.17 *자료구조 제 6강 (0) 2022.02.15 자료구조 제 5강 (0) 2022.02.15