ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 자료구조 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] 칸은 빈 칸이 되므로 해당 칸에 새로운 사람의 정보를 저장하기 위해서는 동적 메모리 할당으로 구조체 객체를 만들어야함.

Designed by Tistory.