자료구조
*자료구조 제 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;
}
*실행이 제대로 안되는 부분있음 -> 후에 고칠 예정