본문 바로가기
[ PROJECT ]/[ GENERAL ]

[ C 언어 ] 전화번호부 무작정 만들기 (feat. CRUD)

by fwanggus 2021. 1. 10.

무작정 전화번호부 만들 겁니다. 단, 아래와 같은 기준과 목표로 작성했습니다.

전화번호부 책

첫째. CRUD(Create. Read. Update. Delete.) 기능을 갖춘다.

둘째. 전화번호부 DB는 간단히 CSV 파일로 대응한다.

셋째. 죽이 되든 밥이 되든 남의 코드를 참고하지 않고 만든다.

 

✅  전체 메뉴 구성

계산기 실행 시 유저가 선택하게 될 가장 큰 분류는 아래와 같이 결정했다.

1️⃣ Add➕

2️⃣ Search🔍

3️⃣ Update🛠

4️⃣ Delete

5️⃣ PrintAll🖨

9️⃣ EXIT🙋🏻‍♂️

 

각 메뉴로 분기는 if ... else if ... else 기능을 사용했다.

기회가 된다면 switch문으로 리팩토링을 해보려 한다.

다 작성하고 보니 switch 문이 더 간단하면서도 직관적인 것 같다.

하지만 본 내용에서는 기본적으로 if 구문을 사용했다.

 

✅  메뉴별 기능 구성

(전체 코드는 깃헙을 참고 부탁드립니다.)

 

1️⃣ Add ➕

 

전화번호부 신규등록을 수행한다. 유저는 이름과 전화번호를 순서대로 입력하게 되며, csv파일로 데이터를 출력한다.

 

📋 주 코드

int addPerson()
{
	...(생략)
	
    // 전화번호부 1개에 해당되는 구조체 p1을 선언(동적 메모리 할당으로 처리)
    person *p1 = malloc(sizeof(person));
    
    // 이름과 전화번호를 순서대로 입력받는다.
    printf("\n NAME : ");
    scanf("%s", p1->name);
    getchar();
    printf(" PHONE NUMBER : ");
    scanf("%s", p1->pNumber);
    getchar();

    // 파일 입출력, csv file format
    FILE *fp;
    fp = fopen(FILE_NAME, "a");

	...(생략)
    
	// 입력받은 전화번호부 정보를 csv파일에 출력함.    
    fputs(p1->name, fp);
    fputs(",", fp);
    fputs(p1->pNumber, fp);
    
    // csv파일에 출력이 끝나면, 메모리를 해제하고 작업중인 파일을 닫아준다.
    free(p1);
    fclose(fp);

    return 0;
}

 

2️⃣ Search 🔍

 

 

이름 또는 전화번호로 전화번호부 내용을 검색한다.

검색 term이 전화번호부 내용과 완전 일치할 경우에만 결과를 반환한다.

검색 term과 전화번호부 내용의 일부 일치에 의한 검색이 가능하게 하는 등 개선의 여지가 남아있다.

 

📋 주 코드

int searchInfo()
{
    /*

    search_input can be name or phoneNumber.
    input  : searched_index number for searching method.
        c or C - cancel searching method.
        1 - method by name
        2 - method by phone number

    todos :
        done .loop all elements [name, phone number] array type

        x
            .fitering by name or phone number
            .make array and append the element searched.
            .return data as array? pointer?

    return : filtered person struct info as array type.

    */

    char search_by;

    while (1)
    {
        printf("\n*----- search -----* \n\n");
        printf("( 1. name ?  2. Phone Number ? C : Cancel ) : ");
        scanf("%c", &search_by);
        getchar();

        char search_input[20] = "";

        if ((search_by == 'c') || (search_by == 'C'))
        {
            printf("\n Canceled...\n");
            return 0;
        }
        else if (search_by == SEARCH_NAME || search_by == SEARCH_NUMBER)
        {

            FILE *fp;
            fp = fopen(FILE_NAME, "r");
            // check if target file is exist or not.
            if (fp == NULL)
            {
                printf("No file specified on diretory. \n");
                return 0;
            }

            // 구조체를 자료형을 가지는 배열을 선언
            // 임시로 배열 크기는 30으로 지정 -> 추후 전체 데이터 수를 참조해서 크기를 지정하는 방법으로 변경.
            person *filtered_person_array[30];
            int searched_index = 0;
            int i = 0;

            switch (search_by)
            {
            case SEARCH_NAME:
                // searching by name.--------------------------
                printf("\n searching by name : ");
                scanf("%s", search_input);
				
                ... (생략)
                
                // show matched result....
                showMatchedResult(searched_index, filtered_person_array);

                fclose(fp);
                break;

            case SEARCH_NUMBER:
                // searching by number.--------------------------
                printf("\n searching by number : ");
                scanf("%s", search_input);
                getchar();

                // filtering person info by search term entered.
				... (생략)

                // show matched result....
                showMatchedResult(searched_index, filtered_person_array);

                fclose(fp);
                break;
            default:
                //
                break;
            }
        }
        else
        {
            printf("Input Error. You should select from specified number or C(cancel) \n");
            return 1;
        }
    }
    return 0;
}

 

3️⃣ Update 🛠

 

 

Search 동작이 선행되는 부분입니다. 검색 후 원하는 형태로 전화번호부 내용을 갱신하게 됩니다.

파일의 전체 내용을 읽은 후, 변경 부분의 인덱스를 기준으로 데이터를 검색하게 되며, 이름과 전화번호를 다시 입력하여 갱신합니다.

 

갱신된 구조체 타입의 배열을 다시 한번 csv파일로 출력되며(덮어 씌우기) 갱신 작업은 끝이 납니다.

 

꾸역꾸역 하는 느낌이 없지 않아 있었습니다. 말하고 싶은 내용은, 유저가 이름의 일부 또는 전화번호 일부만 변경하고 싶을 경우, 이전 저장 내용을 참조하지 못하고 새롭게 작성해야 되는 구조로 되어 있어 유저를 위한? 구조의 프로그램으론 부족하지 않나 하는 생각이 드네요.

그야말로 무작정 만들다 보니, 세심하게 고려하지 못했네요. 반성할 점입니다.

 

📋 주 코드

int updatePerson(char *fileName)
{
    printf("*----- update -----* \n");

    // show all person infos.
    showAll();

    char update_confirm;
    int update_number = 0;
    char new_name[20] = "";
    char new_number[20] = "";
    int total_lines = 0;
    // 파일의 전화번호 수(라인 수)를 반환하는 함수를 작성하여, 반복하여 사용.
    total_lines = checkTotalLines(fileName);

    printf("\nSELECT BY NUMBER : ");
    scanf("%d", &update_number);
    getchar();

    int i_onStruct = 0;
    i_onStruct = update_number - 1;

    // p4 : array type data
    person *p4 = getPersonInfo(fileName);
    printf("UPDATE PERSON : %d. %s *** %s \n",
           p4[i_onStruct].info_index,
           p4[i_onStruct].name,
           p4[i_onStruct].pNumber);

    printf("NEW NAME : ");
    scanf("%s", new_name);
    getchar();
    printf("NEW NUMBER : ");
    scanf("%s", new_number);
    getchar();

    printf("Are you sure ? ( Y / N ) : ");
    scanf("%c", &update_confirm);
    getchar();

    // update the struct p4 element and write updated struct p4 on the file.
    if (update_confirm == 'Y' || update_confirm == 'y')
    {
        // initialize name and pNumber
        strcpy(p4[i_onStruct].name, "");
        strcpy(p4[i_onStruct].pNumber, "");

        // write new name and number on the specified struct element.
        strcpy(p4[i_onStruct].name, new_name);
        strcpy(p4[i_onStruct].pNumber, new_number);

        // write updated p4 on the file.
        writePInfo2File(fileName, p4);
    }
    else if (update_confirm == 'N' || update_confirm == 'n')
    {
        printf("Cancel Info Update. \n");
    }
    else
    {
        printf("Wrong Input. It takes Y or N \n");
    }

    free(p4);
    return 0;
}

 

4️⃣ Delete ❌

 

Update 기능의 변형 정도로 생각하여 작성했습니다.

특징은, 전화번호 부가 2개 이상일 경우는 삭제 요청 인덱스 부분의 데이터를 그다음 인덱스 데이터로 덮어 씌우기 처리를 했습니다.

그리고, 데이터가 1개만 있을 경우는, memset(string.h의 내장 함수) 함수로 초기화해주었다.

 

📋 주 코드

int deletePerson(char *fileName)
{

    printf("*----- delete -----* \n");

    // show all person infos.
    showAll();

	... (변수 선언 생략)

    printf("\n DELETE BY NUMBER : ");
    scanf("%d", &delete_number);
    getchar();

    int i_onStruct = 0;
    i_onStruct = delete_number - 1;

    // p4 : array type data
    person *p4 = getPersonInfo(fileName);
    total_lines = checkTotalLines(fileName);

    // check whether delete_number input is correct.
	... (생략)
    
    // check user selection.
	... (생략)

    if (delete_confirm == 'Y' || delete_confirm == 'y')
    {
        if (total_lines == 1)
        {
            printf("last one element deleted ... \n");
            // make 0 index item to '\0' text.
            // by using memset
            memset(p4[0].name, '\0', sizeof(p4[0].name));
            memset(p4[0].pNumber, '\0', sizeof(p4[0].pNumber));

            fileMakeEmpty(fileName);
        }
        else
        {
            // Delete the specified element by overwriting 
            // and update total number of array and its order.
			... (생략)
            writePInfo2File(fileName, p4);
        }
    }
    else if (delete_confirm == 'N' || delete_confirm == 'n')
    {
        printf("not deleting. \n");
        return 0;
    }
    else
    {
        printf("ERROR Input. Please Choose Y or N");
    }
    free(p4);
    return 0;
}

 

5️⃣ PrintAll 🖨

 

 

가장 간단히 구현한 내용입니다. 

단순히 csv파일을 읽은 후 커맨드 창에 출력해서 유저에게 보여주도록 했습니다.

이 함수는 사실 Update, Delete 메뉴에도 사용했습니다. 해당 기능들을 유저가 잘 선택할 수 있도록 정보제공의 목적으로 사전에 실행되도록 배치했습니다.

 

특별 한 점이 없기 때문에 코드 내용을 이곳에 싣진 않겠습니다.

 

✅  파일 분리 ✂️

프로젝트 크기에 상관없이 프로그램에서 사용되는 요소들을 각 기능에 맞게 분리하여, 가져다 쓰는 습관을 기르는 것은 프로그램 유지보수에도 좋으며, 개발자에게도 유익한 것은 부정할 수 없을 것 같습니다.

그래서, 매우 간단한 구조의 프로그램이지만 각 역할에 맞게 적당히 파일을 나눴습니다.

 

main.c

     global.c

     menu.c

     string_.c

     handleFile.c

 

기본적으로 main 파일로부터 1차 종속관계? 까지만 파일을 분리했고, 하위 파일은 필요에 따라 헤더 파일로 정보를 참조/전달했습니다.

 

  • global.c : 구조체의 형태를 작성하거나, csv파일 이름을 저장하거나 등등 매크로 변수들도 저장했다.
  • menu.c : main파일에서 선택되는 각 메뉴의 함수를 작성했다. 그래서 유저 선택에 따라, 다음 엔트리 포인트는 이 파일로부터 시작된다.
  • string_.c : 문자열 데이터를 다루기 위한 함수들을 이곳에 저장했다. 순수하게 문자열을 조작하는 파일이라고 하긴 힘들지만 어느 정도 문자열 조작 함수를 배치하려고 노력했다.
  • handleFile.c : 데이터(구조체 타입의 배열 데이터)를 csv파일로 처리하기 위한 파일 관련 함수를 저장했다. 

✅  중복기능 함수화 🔨

파일 입출력 및 데이터의 검색 작업 부분에서 일부 중복해서 필요한 작업들은 함수로 처리하여 손쉽게 작업할 수 있었다.

 

파일의 전체 데이터 수를 파악한다거나,

배열 데이터를 파일로 출력한다거나,

csv타입의 한 줄 데이터를 분리시켜 이름/전화번호 데이터로 가져온다거나 등등 다양한 부분에서 함수화 작업이 유용하게 사용되었다.

 

☑️  더 고민해봐야 할 점 🧐

  • 메모리 사용의 효율화(데이터 수가 많아질 경우 대응 방법) 시키기
  • 유저 친화적인 프로그램 구조(특히, Update메뉴에서 나올 수 있는 다양한 경우의 수를 고려)로 개선
  • 리팩토링이 필요한 부분 수정
반응형