자료구조

*자료구조 제 6강

한 면만 쓴 종이 2022. 2. 15. 21:55

phonebook03

-잘못된 명령어에 대해서 적절히 반응

-저장된 사람의 수가 배열의 용량을 초과할 경우 동적 메모리 할당으로 배열의 크기를 키움

 

 

구분문자를 이용하여 하나의 긴 문자열을 작은 문자열들로 자르는 일을 문자열 tokenizing이라고 부른다. 잘라진 작은 문자열들을 보통 token이라고 부른다.

 

 

strtok

  • 원본 문자열을 변화시킴(구분자 자리에 '\0'을 삽입) -> 원본 문자열을 보존해야 한다면 문자열을 복사한 후 strtok을 실행해야함
  • 문자열을 선언할 때 string literal (ex. char *str = "Hello world";)로 선언 할 경우 수정 불가능 하므로 strtok 오류 발생 (char str[] = "Hello world"; 가능)
  • strtok은 새로운 배열을 생성하지 않음 -> _strdup와는 다름

 

/*
* 2022-02-15
* 전화번호부v3.0
* 배열 재할당, 라인 단위 입력과 문자열 tokenizing
*/
#define _CRT_SECURE_NO_WARNINGS

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

#define INIT_CAPACITY 3			//배열 재할당을 테스트하기 위해 일부러 아주 작은 값으로 설정
#define BUFFER_SIZE 50

void init_directory();
int read_line(char str[], int limit);
void process_command();
void load(char* fileName);
void add(char* name, char* number);
void status();
void find(char* name);
void remover(char* name);
void save(char* fileName);
void reallocate();

char** names;					// char* 타입의 배열의 이름이므로 char** 타입의 변수
char** numbers;

int capacity = INIT_CAPACITY;
int n = 0;

char delim[] = " ";

int main() {
	init_directory();			//이 함수에서 배열 names와 numbers를 생성한다.
	process_command();			//사용자의 명령을 받아 처리하는 부분을 별개의 함수로 만들었다.

	return 0;
}

void init_directory()
{
	names = (char**)malloc(INIT_CAPACITY * sizeof(char*));
	numbers = (char**)malloc(INIT_CAPACITY * sizeof(char*));
}

//line 단위의 입력은 fgets, getline 등의 함수들을 이용하여 할 수도 있다.
//많은 c프로그래머들은 직접 함수로 만들어서 쓰는 경우가 많음 (각 상황의 미묘한 차이 때문에)
int read_line(char str[], int limit)	//limit보다 더 긴 경우에는 뒷부분이 짤린다.
{
	int ch, i = 0;

	while ((ch = getchar()) != '\n')	//줄바꿈 문자가 나올 때 까지 읽는다.
		if (i < limit - 1)				//배열의 용량을 초과하지 않을 때만 저장한다.
			str[i++] = ch;

	str[i] = '\0';						//마지막에 null character을 추가한다.

	return i;							//실제로 읽은 문자 수 반환
}

void process_command() {
	char command_line[BUFFER_SIZE];							//한 라인을 통째로 읽어오기 위한 버퍼
	char* command, * argument1, * argument2;

	while (1) {
		printf("$ ");

		if (read_line(command_line, BUFFER_SIZE) <= 0)		//명령줄을 통째로 읽음
			continue;
		command = strtok(command_line, delim);

		if (strcmp(command, "read") == 0) {
			argument1 = strtok(NULL, delim);
			if (argument1 == NULL) {
				printf("File name required.\n");
				continue;
			}
			load(argument1);								//파일명을 인자로 주면서 load를 호출한다.
		}
		else if (strcmp(command, "add") == 0) {
			argument1 = strtok(NULL, delim);				//명령어에 이어지는 2개의 토큰은 각각 이름과 전화번호이다.
			argument2 = strtok(NULL, delim);

			if (argument1 == NULL || argument2 == NULL) {
				printf("Invalid arguments.\n");
				continue;
			}
			add(argument1, argument2);						//이름과 전화번호를 인자로 주면서 add를 호출한다.
			printf("%s was added successfully.\n", argument1);
		}
		else if (strcmp(command, "find") == 0) {
			argument1 = strtok(NULL, delim);
			if (argument1 == NULL) {
				printf("Invalid arguments.\n");
				continue;
			}
			find(argument1);
		}
		else if (strcmp(command, "status") == 0)
			status();
		else if (strcmp(command, "delete") == 0) {
			argument1 = strtok(NULL, delim);
			if (argument1 == NULL) {
				printf("Invalid arguments.\n");
				continue;
			}
			remover(argument1);
		}
		else if (strcmp(command, "save") == 0) {
			argument1 = strtok(NULL, delim);
			argument2 = strtok(NULL, delim);

			if (argument1 == NULL || strcmp("as", argument1) != 0 || argument2 == NULL) {
				printf("Invalid command format.\n");
				continue;
			}
			save(argument2);
		}
		else if (strcmp(command, "exit") == 0)
			break;
	}
}

void load(char* fileName) {
	char buf1[BUFFER_SIZE];
	char buf2[BUFFER_SIZE];

	FILE* fp = fopen(fileName, "r");
	if (fp == NULL) {
		printf("Open failed.\n");
		return;
	}
	while ((fscanf(fp, "%s", buf1) != EOF)) {		//file에서 이름 읽음, 파일의 끝(End Of File)에 도달할 때 까지 반복해서 이름과 전화번호를 읽어서 배열에 저장
		fscanf(fp, "%s", buf2);						//file에서 전화번호 읽음
		add(buf1, buf2);
	}
	fclose(fp);
}

void add(char* name, char* number) {
	if (n >= capacity)	//배열이 꽉찬 경우 재할당
		reallocate();

	int i = n - 1;
	while (i >= 0 && strcmp(names[i], name) > 0) {
		names[i + 1] = names[i];
		numbers[i + 1] = numbers[i];
		i--;
	}
	names[i + 1] = _strdup(name);		//strdup를 사용해야하는 이유: 전 함수의 지역변수인 buf1과 buf2가 name과 number이기 때문에 load함수가 return되면 사라지기 때문
	numbers[i + 1] = _strdup(number);
}

void status() {
	int i;
	for (i = 0; i < n; i++)
		printf("%s  %s\n", names[i], numbers[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
		printf("%s\n", numbers[index]);
}

int search(char* name) {
	int i;
	for (i = 0; i < n; i++) {
		if (strcmp(name, names[i]) == 0)
			return i;						//name과 동일한 사람이 있는 경우 해당 인덱스인 i리턴
	}
	return -1;								//name과 동일한 사람이 없는 경우 -1리턴
}

void remover(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++) {
		names[j] = names[j + 1];
		numbers[j] = numbers[j + 1];
	}
	n--;
	printf("'%s' was deleted successfully.\n", name);
}

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 %s\n", names[i], numbers[i]);
	}
	fclose(fp);
}

void reallocate() {
	int i;
	capacity *= 2;
	char** tmp1 = (char**)malloc(capacity * sizeof(char*));		//크기가 2배인 배열들 할당
	char** tmp2 = (char**)malloc(capacity * sizeof(char*));

	for (i = 0; i < n; i++) {									//원본 배열 names와 numbers의 값을 새로운 배열에 복사
		tmp1[i] = names[i];
		tmp2[i] = numbers[i];
	}
	free(names);												//원본 배열들은 더이상 필요 없으므로 free함수를 사용하여 반환
	free(numbers);

	names = tmp1;												//names와 numbers가 새로운 배열을 가리키도록 한다.
	numbers = tmp2;
}

 

*실행이 제대로 안되는 부분있음 -> 후에 고칠 예정