STUDY/C, C++

[C/C++] 연결 리스트 Linked List

sinawi95 2022. 2. 21. 20:31
728x90

이번 포스팅의 목표는 포인터를 사용해서 연결 리스트(linked list)를 구현하는 것이다. 선수지식은 C의 포인터 인데 사실 몰라도 이해는 될 것 이다. 구현이 조금 어려울뿐

 

위가 배열 아래가 연결리스트. 대충 이런 느낌이라고 생각하면 된다. 너무 발그림이다.

우선 연결 리스트라는 단어를 뜯어보면 연결된(linked) 리스트라는 뜻이다. 파이썬이나 C, C++등 어느정도 지식이 있는 사람들은 배열은 이미 연결되어있지 않나 생각이 들수 있다. 하지만 배열처럼 처음부터 사이즈를 정해져있는게 아니라 새로 추가하든 삭제하든 할때 파편화된 데이터를 만들고 이를 서로 연결해서 리스트로 사용한다는 뜻으로 이해하면 된다.

 

각 노드를 prev와 next로 연결해준다. prev가 없이 next만있으면 single linked list이다.

연결 리스트를 만들기 위해선 각각의 데이터마다 다음 데이터가 어디있는지 알고 있어야한다. 그래서 데이터 뿐만아니라 주소를 저장하는 공간이 있어야한다. 주소 공간이 몇개 인지에 따라 또 구분할수 있는데, 다음 주소만 알고있으면 single linked list, 이전, 다음 주소 모두 알고 있으면 double linked list라고 부른다. 그리고 각각의 데이터와 주소를 같이 저장한 단위를 노드라고 부른다.(다들 그렇게 부르더라)

 

HEAD와 TAIL은 첫 노드와 마지막 노드를 가리킨다.

이렇게 연결만하면 처음과 끝을 모르기때문에 이를 알려줄 노드도 필요하다. 통상 HEAD, TAIL이라고 표시하고, NULL 값을 가리킨다. HEAD에서 가리키는 값은 첫 노드이고 TAIL은 마지막 노드이다. 노드가 아무 것도 없을 땐 HEAD와 TAIL은 아무것도 가리키지 않다가 노드가 생기면 그때부터 가리킨다. 그리고 HEAD가 가리키는 노드가 삭제되거나 새로운 값이 추가 되면(TAIL이 가리키는 노드가 바뀌면) 갱신해야한다.


여기까지가 연결리스트에 대한 설명이고 위에 있는 세 장의 그림과 설명을 코드로 구현하면 연결리스트가 완성된다. 참 쉽다. 이제 구현만 하면되는데 연결 리스트만 구현하긴 조금 재미없으니 DB를 구축하는 것처럼 만들면서 한번 배워보자.

우선 위에 설명한 것들을 구현해보자.

typedef struct data {
    int data_id;
    char name[30];
    char gender;
    int age;
} Data;

첫번째는 데이터가 들어가는 구조이다. struct를 사용해서 구조체를 만들었고, id와 이름, 성별(m/f/-), 나이를 넣을수 있게 만들었다. 그리고 typedef를 지정해서 struct data를 Data로 지정했다.

typedef struct node {
    Data data;
    struct node *prev;
    struct node *next;
} Node;

두번째는 노드를 만든다. 위에서 만든 데이터 구조를 사용하고 이전 노드의 주소와 다음 노드의 주소를 저장할수 있게 만들었다. 얘도 똑같이 typedef를 사용해서 Node로 지정했다. 이 구조체에서 조금 눈여겨 봐야할 것은 prev와 next이다. (struct node *)로 포인터를 사용해서 노드의 주소를 가리킬수 있게 만들었다.

typedef struct linked_list {
    struct node *head;
    struct node *tail;
    int count;
} List;

세번째는 연결 리스트에 대한 구조체이고 head와 tail, 그리고 연결 리스트안에 들어있는 원소의 개수(리스트의 크기)를 생성했다.

 

이정도만 하면 구조도 얼추 구현했다. 이제 노드의 CRUD(추가, 탐색, 수정, 삭제)정도만 구현하면 DB 기능을 모두 구현했다고 할 수 있을 것이다. 이를 함수 프로토타입으로 표현하면 다음과 같다.

List* init(void);
void add_item(List **list);
void remove_item(List **list);
void search_item(List *list);
void print_all_items(List *list);
void modify_item(List **);
void before_exit(List** list);

CRUD는 add, search, modify, remove 쯤이고, init과 exit는 초기화하고 free하고 하는 거라 별 거 없다. 어떤 함수는 반환값이 List*이고 어떤건 void인데 이건 밑에서 설명할 것이다.

지금까지 구현한 것들은 헤더파일에 저장하고 모든 파일에 추가해서 사용한다.

더보기
#ifndef main_h
#define main_h

// struct
typedef struct data {
    int data_id;
    char name[30];
    char gender;
    int age;
} Data;

typedef struct node {
    Data data;
    struct node *prev;
    struct node *next;
} Node;

typedef struct linked_list {
    struct node *head;
    struct node *tail;
    int count;
} List;

// functions
List* init(void);
void add_item(List **list);
void remove_item(List **list);
void search_item(List *list);
void print_all_items(List *list);
void modify_item(List **);
void before_exit(List** list);

#endif /* main_h */

기능을 만들기 전에 DB를 추가하는 것처럼 만들기 위해 메뉴를 볼수 있게 만들어보자.

우선 main함수가 시작하면 바로 linked list를 초기화 시킨다. 노드를 추가할 때 초기화 하는게 나을수도 있겠지만 어차피 사용할 거라 시작하자마자 초기화한다. 초기화를 하기 위해선 List 구조의 모양을 가진 변수가 필요하다. 그리고 init 함수에서 List를 만든뒤 생성한 List의 주소를 넘겨 받아야하므로 반환값은 List*가 된다. (생성한 List에 계속 값을 추가, 삭제 해야하므로 주소 값을 반환받는다.)

그다음 init함수가 끝나면 기능을 사용할수 있도록 메뉴를 출력한다. while 문을 사용해서 계속 메뉴가 뜰수 있도록 만들고 특정 값(0)이 들어오면 빠져 나올수 있게 만든다. 그리고 메뉴 값이 입력되면 기능에 맞는 함수를 실행할 수 있도록 switch case문을 통해 구현한다.

마지막으로 반복문을 빠져 나온 이후엔 연결리스트에 주어진 값들을 모두 삭제하고 프로그램을 종료한다.

더보기

 

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


int main(int argc, const char * argv[]) {
    printf("Welcome!\n");
    int menu_select, break_flag = 0;
    List *list;
    list = init();
    while (1)
    {
        printf("1. Add\t");
        printf("2. Remove\t");
        printf("3. Print all\t");
        printf("4. Search\t");
        printf("5. Modify\t");
        printf("0. Exit\n");
        printf("select --> ");
        scanf("%d", &menu_select); getchar();
        switch(menu_select){
            case 1:
                add_item(&list);
                break;
            case 2:
                remove_item(&list);
                break;
            case 3:
                print_all_items(list);
                break;
            case 4:
                search_item(list);
                break;
            case 5:
                modify_item(&list);
                break;
            case 0:
                printf("Bye Bye\n");
                break_flag = 1;
                break;
                
            default:
                printf("Please enter 1~5, 0\n");
                break;
        }
        if (break_flag){
            break;
        }
    }
    
    before_exit(&list);
    return 0;
}

 

순차적으로 진행하기 위해선 switch case내부에 있는 모든 함수를 주석 처리해야한다. 그리고 이후 함수를 구현한뒤 하나씩 주석 해제하도록하자.

메뉴까진 잘 나온다!


맨 처음 수행해야하는 초기화 함수이다. 초기화 함수에서 해야할건 List 구조를 만들고, 구조체의 내부 값들을 초기화하는 것이다.

List 구조를 만들기 위해 malloc 함수를 사용해서 동적으로 메모리를 할당 받는다. malloc 을 사용하는 이유는 local variable로 만들게 되면 return 한 이후에 값을 사용할 수 없기 때문이다. malloc으로 데이터를 heap 영역에 만들어서 return한 이후에도 값 자체가 보존이 되게 한다. 

List 구조를 만들었으면 내부 값을 초기화한다. 초기 상태엔 아직 아무값도 없으니 head와 tail은 NULL로, count는 0으로 초기화한다. 어디에선 head와 tail 을 nullptr로 지정하는데 아직 차이는 모르겠다.

더보기
#include <stdio.h>
#include <stdlib.h>
#include "main.h"


List* init(void)
{
    List *tmp = (List *)malloc(sizeof(List));
    tmp->head = tmp->tail = NULL;
    tmp->count = 0;
    return tmp;
}

연결리스트를 초기화했으니 노드를 추가해보자. 노드를 추가하는 함수엔 (1) 노드를 생성하고 (2) 값을 받아서 (3) 리스트에 연결하는 것이 필요하다.

List를 만든 것처럼 Node 구조도 하나 생성한다.

Node *new = (Node *)malloc(sizeof(Node));

 

그 다음엔 노드에 들어가는 값들을 받아서 넣는다. id값은 값을 기억해서 자동으로 증가할수 있도록 static int로 생성했다(static unsigned int로 해도 괜찮을듯 하다). 그리고  이름, 나이, 성별은 scanf(혹은 fgets)를 사용해서 값들을 받았고 int값이나 char 값은 개행처리를 따로 해주었다. 성별을 입력받을땐 m, f를 제외한 나머지 값들은 '-' 로 표현하기 위해 따로 받아서 넣어주었다. 

그리고 생성된 노드는 항상 리스트의 마지막에 들어가게 되므로 다음 노드를 가리키는 값은 NULL로 이전 노드를 가리키는 값은 현재 리스트의 TAIL을 가리키게 만든다.

 

static int id_count;
char tmp_gender;

new->data.data_id = id_count++;
printf("name:\t");
scanf(" %s", new->data.name);
printf("age:\t");
scanf(" %d", &new->data.age);  while (getchar() != '\n');
printf("gender(m/f):\t");
scanf(" %c", &tmp_gender); while (getchar() != '\n');
switch (tmp_gender){
    case 'm': case 'M':
        new->data.gender = 'm';
        break;
    case 'f': case 'F':
        new->data.gender = 'f';
        break;
    default:
        new->data.gender = '-';
        break;
}
new->next = NULL;
new->prev = (*list)->tail;

노드까지 생성했으면 리스트에 연결한다. 연결할 땐 (1) 리스트에 값이 없는 경우와 (2) 있는 경우를 따져서 넣어야한다. (1)은 연결리스트의 HEAD와 TAIL이 모두 NULL을 가리키고 있으므로. HEAD와 TAIL을 모두 새로 만든 노드를 가리키게 만들어야한다. (2)는 마지막 노드가 새로운 노드를 가리키게 하고 새로운 노드를 마지막 주소로 갱신해야한다. 

연결하고 난 이후엔 연결리스트의 count 값도 증가 시킨다.

if ((*list)->head == NULL){
    (*list)->head = (*list)->tail = new;
} else {
    (*list)->tail->next = new;
    (*list)->tail = new;
}
(*list)->count++;
더보기
#include <stdio.h>
#include <stdlib.h>
#include "main.h"

void add_item(List **list)
{
    static int id_count;
    char tmp_gender;
    Node *new = (Node *)malloc(sizeof(Node));
    // 1. create new node
    // new->data
    printf("name:\t");
    scanf(" %s", new->data.name);
    printf("age:\t");
    scanf(" %d", &new->data.age);  while (getchar() != '\n');
    printf("gender(m/f):\t");
    scanf(" %c", &tmp_gender); while (getchar() != '\n');
    switch (tmp_gender){
        case 'm': case 'M':
            new->data.gender = 'm';
            break;
        case 'f': case 'F':
            new->data.gender = 'f';
            break;
        default:
            new->data.gender = '-';
            break;
    }
    new->next = NULL;
    new->prev = (*list)->tail;
    new->data.data_id = id_count++;
    
    // 2. connect node
    if ((*list)->head == NULL){
        (*list)->head = (*list)->tail = new;
    } else {
        (*list)->tail->next = new;
        (*list)->tail = new;
    }
    (*list)->count++;
}

요렇게 계속 추가할 수있다


아마 잘 만들어졌을건데 확인은 아직 못한다. 잘 연결되었는지 확인하기 위해 모든 노드를 확인하는 함수를 만들어보자.

 이 함수에선 HEAD가 가리키는 노드부터 TAIL이 가리키는 노드까지 순회하면서 모든 값을 출력하면 된다. List의 head와 각 노드의 next 주소를 사용해서 순회 할수 있다. tail이 가리키는 노드의 next는 항상 NULL을 가리키게 되므로 이를 반복문 조건으로 사용하면 쉽게 만들수 있다.

더보기
void print_all_items(List *list)
{
    Node *cur;
    cur = list->head;
    printf("id\tname\tage\tgender\n");
    while (cur){
        printf("[%d]\t", cur->data.data_id);
        printf("%s\t", cur->data.name);
        printf("%d\t", cur->data.age);
        printf("%c\n", cur->data.gender);
        cur = cur->next;
    }
    printf("==========\n");
}

오류 없이 잘 만들어진듯하다.


HEAD부터 TAIL까지 순회할수 있으면 노드 안의 데이터를 사용해서 특정 노드를 찾는 것을 만들수 있다.

특정 노드를 찾을때 id로 찾는 방법과 name으로 찾는 방법을 구현했다. 우선 메뉴부터 만들고...

void search_item(List *list)
{
    int command, flag = 1;
    while (flag)
    {
        printf("How to search\n");
        printf("1. id, 2. name 0.quit\n");
        printf("select --> ");
        scanf("%d", &command); getchar();
        switch (command){
            case 1:
                search_by_id(list);
                break;
            case 2:
                search_by_name(list);
                break;
            case 0:
                flag = 0;
                break;
            default:
                printf("Please enter 1, 2, or 0\n");
                break;
        }
    }
}

 

id로 탐색하는 것은 HEAD부터 TAIL까지 순회하면서 특정 id와 같은 값인 데이터를 출력하게 만들었다. 그리고 id는 고유한 값이므로 데이터를 찾게되면 바로 break 문으로 빠져나올수 있도록 했다.

void search_by_id(List *list)
{
    int tmp_id, flag=1;
    Node* cur;
    
    printf("which id do you want to search? ");
    scanf(" %d", &tmp_id); getchar();
    
    cur = list->head;
    
    while (cur){
        if (cur->data.data_id == tmp_id){
            printf("[%d] name: %s\tage: %d\tgender: %c\n", cur->data.data_id, cur->data.name, cur->data.age, cur->data.gender);
            flag=0;
            break;
        }
        cur = cur->next;
    }
    if (flag){
        printf("There is no item(id=%d)\n", tmp_id);
    }
}

 

 

이름으로 탐색하는 것은 중복되는 값이 있을 수 있으므로 이름만 같으면 모두 출력하게 만들었다. 동명이인은 모두 출력해야하므로 id와 다르게 break를 하지 않았다.

void search_by_name(List *list)
{
    //if some name is same, search all 
    int flag=1;
    char tmp_name[30];
    Node* cur;
    
    printf("which name do you want to search? ");
    scanf(" %s", tmp_name);
    
    cur = (list)->head;
    
    while (cur){
        if (strcmp(cur->data.name, tmp_name) == 0){
            printf("[%d] name: %s\tage: %d\tgender: %c\n", cur->data.data_id, cur->data.name, cur->data.age, cur->data.gender);
            flag=0;
        }
        cur = cur->next;
    }
    if (flag){
        printf("There is no item(name=%s)\n", tmp_name);
    }
}

 

더보기
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "main.h"

void search_by_id(List *list)
{
    int tmp_id, flag=1;
    Node* cur;
    
    printf("which id do you want to search? ");
    scanf(" %d", &tmp_id); getchar();
    
    cur = list->head;
    
    while (cur){
        if (cur->data.data_id == tmp_id){
            printf("[%d] name: %s\tage: %d\tgender: %c\n", cur->data.data_id, cur->data.name, cur->data.age, cur->data.gender);
            flag=0;
            break;
        }
        cur = cur->next;
    }
    if (flag){
        printf("There is no item(id=%d)\n", tmp_id);
    }
}

void search_by_name(List *list)
{
    //if some name is same, search all 
    int flag=1;
    char tmp_name[30];
    Node* cur;
    
    printf("which name do you want to search? ");
    scanf(" %s", tmp_name);
    
    cur = (list)->head;
    
    while (cur){
        if (strcmp(cur->data.name, tmp_name) == 0){
            printf("[%d] name: %s\tage: %d\tgender: %c\n", cur->data.data_id, cur->data.name, cur->data.age, cur->data.gender);
            flag=0;
        }
        cur = cur->next;
    }
    if (flag){
        printf("There is no item(name=%s)\n", tmp_name);
    }
}

void search_item(List *list)
{
    int command, flag = 1;
    while (flag)
    {
        printf("How to search\n");
        printf("1. id, 2. name 0.quit\n");
        printf("select --> ");
        scanf("%d", &command); getchar();
        switch (command){
            case 1:
                search_by_id(list);
                break;
            case 2:
                search_by_name(list);
                break;
            case 0:
                flag = 0;
                break;
            default:
                printf("Please enter 1, 2, or 0\n");
                break;
        }
    }
}

보긴 어렵지만 id=0인 데이터과 name=wi인 데이터를 찾았다


특정 노드도 찾는다면 수정도 쉽게 만들수 있다.

위랑 같은 코드로 (1)탐색을 하고 (2)찾는 노드가 맞다면 (3) 값을 받아서 수정하면 된다.

(1)은 비슷하니 패스하도록 하고, (2) 가 조금 추가 되었다. 찾는 노드가 맞는지 확인을 해서 맞으면 수정 기능으로 넘기고 아니면 그 다음 노드를 탐색하게 되는데 이를 코드로 나타내면 다음과 같다.

printf("if you want to modify? (y/else) ");
scanf("%c", &tmp_ch); while(getchar() != '\n');
if (tmp_ch == 'y'){
    modify_func(&cur);
}

수정하는 기능은 add_items에 있는 코드를 재사용하였다. 연결하는 게 없어서 값만 받으면 된다.

void modify_func(Node **cur){
    char tmp_gender;
    printf("name:\t");
    scanf(" %s", (*cur)->data.name);
    printf("age:\t");
    scanf(" %d", &(*cur)->data.age); while (getchar() != '\n');
    printf("gender(m/f):\t");
    scanf(" %c", &tmp_gender); while (getchar() != '\n');
    switch (tmp_gender){
        case 'm': case 'M':
            (*cur)->data.gender = 'm';
            break;
        case 'f': case 'F':
            (*cur)->data.gender = 'f';
            break;
        default:
            (*cur)->data.gender = '-';
            break;
    }
    printf("Modifying...\n");
    printf("[%d] name: %s\tage: %d\tgender: %c\n", (*cur)->data.data_id, (*cur)->data.name, (*cur)->data.age, (*cur)->data.gender);
}

 

더보기
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "main.h"

void modify_func(Node **cur){
    char tmp_gender;
    printf("name:\t");
    scanf(" %s", (*cur)->data.name);
    printf("age:\t");
    scanf(" %d", &(*cur)->data.age); while (getchar() != '\n');
    printf("gender(m/f):\t");
    scanf(" %c", &tmp_gender); while (getchar() != '\n');
    switch (tmp_gender){
        case 'm': case 'M':
            (*cur)->data.gender = 'm';
            break;
        case 'f': case 'F':
            (*cur)->data.gender = 'f';
            break;
        default:
            (*cur)->data.gender = '-';
            break;
    }
    printf("Modifying...\n");
    printf("[%d] name: %s\tage: %d\tgender: %c\n", (*cur)->data.data_id, (*cur)->data.name, (*cur)->data.age, (*cur)->data.gender);
}

void modify_by_id(List **list)
{
    int tmp_id, flag=1;
    char tmp_ch;
    Node* cur;
    
    printf("which id do you want to modify? ");
    scanf(" %d", &tmp_id); getchar();
    
    cur = (*list)->head;
    
    while (cur){
        if (cur->data.data_id == tmp_id){
            printf("[%d] name: %s\tage: %d\tgender: %c\n", cur->data.data_id, cur->data.name, cur->data.age, cur->data.gender);
            printf("if you want to modify? (y/else) ");
            scanf("%c", &tmp_ch); while(getchar() != '\n');
            if (tmp_ch == 'y'){
                modify_func(&cur);
            }
            flag = 0;
            break;
        }
        cur = cur->next;
    }
    if (flag){
        printf("There is no item(id=%d)\n", tmp_id);
    }
}

void modify_by_name(List **list)
{
    int flag=1;
    char tmp_name[30], tmp_ch;
    Node* cur;
    
    printf("which name do you want to modify? ");
    scanf(" %s", tmp_name);
    
    cur = (*list)->head;
    
    while (cur){
        if (strcmp(cur->data.name, tmp_name) == 0){
            printf("[%d] name: %s\tage: %d\tgender: %c\n", cur->data.data_id, cur->data.name, cur->data.age, cur->data.gender);
            printf("if you want to modify? (y/else) ");
            scanf(" %c", &tmp_ch); while(getchar() != '\n');
            printf("tmp_ch: %c\n", tmp_ch);
            if (tmp_ch == 'y'){
                // remove
                modify_func(&cur);
                flag = 0;
                break;
            }
        }
        cur = cur->next;
    }
    if (flag){
        printf("There is no item you want\n");
    }
}

void modify_item(List **list)
{
    int command, flag = 1;
    while (flag)
    {
        printf("How to modify\n");
        printf("1. id, 2. name 0.quit\n");
        printf("select --> ");
        scanf("%d", &command); getchar();
        switch (command){
            case 1:
                modify_by_id(list);
                break;
            case 2:
                modify_by_name(list);
                break;
            case 0:
                flag = 0;
                break;
            default:
                printf("Please enter 1, 2, or 0\n");
                break;
        }
    }
}

Modify 를 골라서 id=0인 데이터를 Si/12/m으로 수정했고, name=na인 데이터를 na/22/f로 수정했다.
^^;

 


이제 얼마 안남았다.

노드를 삭제하는 함수이다. 만들어야하는 기능은 (1) 노드를 탐색하고 (2) 찾는 노드라면 (3) 삭제하는 것이다. (1)과 (2)는 modify와 매우 비슷하고 삭제를 원하는 노드인 경우 삭제 기능으로 넘어간다.

삭제 기능은 고려해야 것이 세가지 경우가 있다. (1) 삭제하는 노드가 HEAD가 가리키는 노드인 경우, (2) 삭제하는 노드가 TAIL이 가리키는 노드인 경우, (3) 그 외의 경우(중간에 있는 노드). 

(1)은 삭제하려고 하는 노드의 다음 노드가 HEAD가 되게 해야한다. List의 HEAD 주소를 갱신하고, 해당 노드가 가리키는 이전 노드의 값을 NULL로 갱신하면된다. (2)도 비슷하다. 삭제하려고 하는 노드의 이전 노드를 TAIL이 되게 만들면된다. (3)은 삭제 노드의 이전 노드(cur->prev)와 삭제 노드의 다음 노드(cur->prev)를 이어야한다. 잇는 방법은 주소만 서로 연결하면 된다.

이렇게 삭제할 노드의 앞 뒤 노드들을 연결한 뒤, 그 이후에 노드를 삭제한다.(노드를 먼저삭제하면 연결할수 없다)

void remove_func(Node **cur, List **list){
    if ((*cur) == (*list)->head) {
        (*cur)->next->prev = NULL;
        (*list)->head = (*cur)->next;
    } else if ((*cur) == (*list)->tail){
        (*cur)->prev->next = NULL;
        (*list)->tail = (*cur)->prev;
    } else {
        (*cur)->prev->next = (*cur)->next;
        (*cur)->next->prev = (*cur)->prev;
    }
    free(*cur);
}

 

더보기
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "main.h"

void remove_func(Node **cur, List **list){
    if ((*cur) == (*list)->head) {
        (*cur)->next->prev = NULL;
        (*list)->head = (*cur)->next;
    } else if ((*cur) == (*list)->tail){
        (*cur)->prev->next = NULL;
        (*list)->tail = (*cur)->prev;
    } else {
        (*cur)->prev->next = (*cur)->next;
        (*cur)->next->prev = (*cur)->prev;
    }
    free(*cur);
}

void remove_by_id(List **list)
{
    int tmp_id, flag=1;
    Node* cur;
    
    printf("which id do you want to remove? ");
    scanf(" %d", &tmp_id); getchar();
    
    cur = (*list)->head;
    
    while (cur){
        if (cur->data.data_id == tmp_id){
            // remove
            remove_func(&cur, list);
            flag = 0;
            break;
        }
        cur = cur->next;
    }
    if (flag){
        printf("There is no item(id=%d)\n", tmp_id);
    }
}

void remove_by_name(List **list)
{
    int flag=1;
    char tmp_name[30], tmp_ch;
    Node* cur;
    
    printf("which name do you want to remove? ");
    scanf(" %s", tmp_name);
    
    cur = (*list)->head;
    
    while (cur){
        if (strcmp(cur->data.name, tmp_name) == 0){
            printf("[%d] name: %s\tage: %d\tgender: %c\n", cur->data.data_id, cur->data.name, cur->data.age, cur->data.gender);
            printf("if you want to delete? (y/else) ");
            scanf("%c", &tmp_ch); while(getchar() != '\n');
            if (tmp_ch == 'y'){
                // remove
                remove_func(&cur, list);
                flag = 0;
                break;
            }
        }
        cur = cur->next;
    }
    if (flag){
        printf("There is no item(name=%s)\n", tmp_name);
    }
}

void remove_item(List **list)
{
    int command, flag = 1;
    while (flag)
    {
        printf("How to remove\n");
        printf("1. id, 2. name 0.quit\n");
        printf("select --> ");
        scanf("%d", &command); getchar();
        switch (command){
            case 1:
                remove_by_id(list);
                break;
            case 2:
                remove_by_name(list);
                break;
            case 0:
                flag = 0;
                break;
            default:
                printf("Please enter 1, 2, or 0\n");
                break;
        }
    }
}

캡처는 귀찮아서 생략한다.


진짜 마지막이다.

모든 기능들은 다 구현했고 main함수를 종료하기전에 수행하는 함수를 구현한다. 연결 리스트와 모든 노드는 malloc 함수를 사용해서 동적할당을 했기 때문에 heap 메모리에 남아있다. 메모리 할당된 것들을 모두 해제하는 함수가 필요하다.

연결리스트부터 해제하면 나머지 데이터를 찾지 못하므로, 데이터부터 모두 순회하면서 해제하고 마지막에 연결 리스트를 해제하면된다.

더보기
#include <stdio.h>
#include <stdlib.h>
#include "main.h"

void before_exit(List** list){
    // free all memories from head to tail
    Node* cur = (*list)->head, *tmp;
    while(cur){
        // 1. free Node
        tmp = cur->next;
        free(cur);
        cur = tmp;
    }
    // 2. free list
    free(*list);
}

포인터를 사용하는게 가장 어렵긴했지만 직접 구현한 나만 어려웠지 아마 보는 분들은 어렵지 않았을 것이다. 나처럼 포인터를 공부하고 싶은 사람이면 내가 했던 이 코드들을 직접 생각하면서 짜보길 바란다.

 

'STUDY > C, C++' 카테고리의 다른 글

[C] scanf 개행 문자 처리  (0) 2021.06.09
[C/C++] 버블 정렬, 카운트 정렬  (0) 2021.06.03
[C/C++] 숫자퍼즐게임  (0) 2021.05.11
[C] _getch를 사용한 움직임 구현  (0) 2021.04.28
[C] C 프로그래밍 문법 (1)  (0) 2021.03.31