C & C++/디딤돌 C언어

Part 24. 사용자 정의 동적 배열 만들기

언휴 2024. 1. 18. 08:49

Part 24. 사용자 정의 동적 배열 만들기

C언어 사용자 정의 동적 배열 만들기

88. 사용자 정의 배열 개요

프로그래밍을 하다 보면 C언어에서 제공하는 형식 배열로 자료를 관리하는 것으로는 한계에 부딪힐 때가 있어요.
예를 들어 회원 관리 프로그램에서 최대 몇 명의 회원을 관리할 것인가를 개발 단계에서 결정할 수 없을 때도 많아요.
프로그램을 사용하는 사용자가 원하는 만큼 관리를 해야 할 때도 있어요.
그리고 프로그램이 알아서 자료를 관리할 공간을 늘려주면 더 좋겠죠.

이 때 동적 메모리 할당을 이용하여 프로그램을 작성하면 가능하겠죠.
이번에는 동적 메모리 할당 함수를 이용하여 확장 가능한 동적 배열 구조체와 관련 함수를 만드는 실습을 할 거예요.

C언어에서 제공하는 형식 배열은 컴파일 시점에 원소 개수를 정해서 한계가 있어요.
여기서는 구조체로 동적으로 생성한 개체를 보관할 수 있는 동적 배열을 정의하고 필요한 함수를 정의해 볼 거예요.

먼저 프로젝트를 생성하여 main 함수를 작성할 소스 파일을 추가하고 동적 배열을 정의할 헤더 파일(EHArray.h)과 소스 파일(EHArray.c)을 추가하세요.

89. 동적 배열 헤더 작성

여기에서 만드는 동적 배열은 순차적으로 자료를 보관하게 만들기로 해요.
그리고 자료를 보관하는 저장소가 꽉 차면 알아서 저장소의 크기를 늘려주는 확장 가능한 배열로 만들어요.

이를 위해서는 동적 배열 구조체의 멤버로 동적으로 할당한 저장소의 위치 정보를 기억하고 있어야겠죠.
그리고 현재 저장소의 크기 및 현재 보관 개수를 기억하고 있게 멤버를 정하세요.
여기에서는 동적으로 생성한 자료를 보관할 수 있게 void * 형식을 요소 형식으로 정의하세요.

typedef struct _EHArray EHArray;
typedef void *Element;
struct _EHArray
{
    Element *base; //저장소의 위치 정보
    int capacity;     //현재 저장소의 크기
    int size;           //현재 보관한 요소 개수
};

그리고 동적 배열을 사용하기 위한 여러 함수들을 제공해야겠죠.
먼저 사용자 정의 배열 구조체에 있는 멤버 속성을 확인하는 함수도 제공하세요.
C언어에서는 구조체 멤버를 어디서나 접근할 수 있어요.
하지만 형식 내부의 멤버를 다른 곳에서 접근을 허용할 것이지를 개발자가 정하는 언어도 많아요.
특히 C++, Java, C# 등의 OOP언어들은 자료의 신뢰성을 높이기 위해 개발자가 멤버의 접근 가시성을 정할 수 있어요.
이번 실습에서는 다른 소스 파일에서 구조체 배열의 멤버에 접근하지 않고 사용하기로 해요.

먼저 동적 배열을 생성할 때 사용하는 함수에는 초기 저장소의 크기와 초기값을 입력받게 하세요.

EHArray *NewEHArray(int init_capa,Element init_value); //동적으로 배열 생성

동적 배열을 소멸하는 함수, 저장소의 크기 가져오기, 보관한 요소 개수 가져오기 기능도 제공하세요.

void DeleteEHArray(EHArray *arr); //배열 소멸
int EHArrayGetCapacity(EHArray *arr); //저장소의 크기 가져오기
int EHArrayGetSize(EHArray *arr);       //보관한 요소 개수 가져오기

순차적으로 자료 보관, 보관한 요소 가져오기, 보관한 요소 설정하기 함수도 제공하세요.

void EHArrayPushBack(EHArray *arr,Element data); //순차적으로 자료 보관
Element EHArrayGetAt(EHArray *arr,int index); //보관한 요소 가져오기
void EHArraySetAt(EHArray *arr,int index, Element data); //보관한 요소 설정하기

배열에 보관한 자료들을 순차적으로 접근하기 쉽게 저장소의 시작 위치와 마지막 위치를 확인하는 기능도 제공하세요.
검색이나 목록 출력등 반복문에서 사용하기 쉽게 마지막 위치는 마지막 자료를 저장한 다음 위치로 정하세요.

Iterator EHArrayBegin(EHArray *arr); //저장소의 시작 위치
Iterator EHArrayEnd(EHArray *arr); //저장소의 마지막 위치(보관할 위치)

자료를 보관한(할) 위치를 Iterator 형식이라고 이름을 정하기로 해요.

typedef Element * Iterator;

그리고 특정 위치에 보관한 것을 제거하는 함수도 제공하세요.

void EHArrayErase(EHArray *arr,Iterator it);//보관한 요소 제거하기

◈ EHArray.h

typedef struct _EHArray EHArray;
typedef void *Element; //요소 형식명을 Element로 정의
typedef Element * Iterator;
struct _EHArray
{
    Element *base; //저장소의 위치 정보
    int capacity;     //현재 저장소의 크기
    int size;           //현재 보관한 요소 개수
};
 
EHArray *NewEHArray(int init_capa,Element init_value); //동적으로 배열 생성
void DeleteEHArray(EHArray *arr); //배열 소멸
int EHArrayGetCapacity(EHArray *arr); //저장소의 크기 가져오기
int EHArrayGetSize(EHArray *arr);       //보관한 요소 개수 가져오기
void EHArrayPushBack(EHArray *arr,Element data); //순차적으로 자료 보관
Element EHArrayGetAt(EHArray *arr,int index); //보관한 요소 가져오기
void EHArraySetAt(EHArray *arr,int index, Element data); //보관한 요소 설정하기
Iterator EHArrayBegin(EHArray *arr); //저장소의 시작 위치
Iterator EHArrayEnd(EHArray *arr); //저장소의 마지막 위치(보관할 위치)
void EHArrayErase(EHArray *arr,Iterator it);//보관한 요소 제거하기
 

90. 동적 배열 소스 작성

C언어 사용자 정의 동적 배열 만들기

먼저 EHArr.c 소스 파일에 EHArray.h 파일과 stdlib.h 파일 포함문을 추가하세요.

#include "EHArray.h"
#include <stdlib.h>

동적 배열을 생성하는 NewEHArray 함수를 구현하세요.
동적으로 원하는 형식 개체를 생성할 때는 메모리를 동적으로 할당하는 부분과 초기화 부분이 필요하죠.
동적으로 생성하는 부분은 malloc 함수를 사용하고 초기화 부분은 별도의 함수로 작성하세요.
앞으로 초기화 함수 이름은 형식 이름을 두 번 쓰기로 해요.

void EHArrayEHArray(EHArray *arr,int init_capa,Element init_value);
EHArray *NewEHArray(int init_capa,Element init_value)
{
    EHArray *arr = (EHArray *)malloc(sizeof(EHArray));
    EHArrayEHArray(arr,init_capa,init_value);
    return arr;
}

동적 배열을 초기화하는 함수에서는 EHArray 개체의 멤버를 0으로 초기화하는 것을 먼저 수행하세요.
저장소의 크기는 입력인자로 init_capa로 늘려주고 저장소에 초기값이 init_value로 순차적으로 보관하세요.

void EHArrayReserve(EHArray *arr,int capacity);
void EHArrayPushBacks(EHArray *arr,int n,Element value);
void EHArrayEHArray(EHArray *arr,int init_capa,Element int_value)
{
    arr->base = 0;
    arr->capacity = 0;
    arr->size = 0;
    if(init_capa>0)
    {
       EHArrayReserve(arr,init_capa);
       EHArrayPushBacks(arr,init_capa,init_value);
    }
}

저장소의 크기를 늘리는 Reserve함수를 구현하죠.
이 함수는 초기에 저장소의 크기를 정할 때와 순차적으로 보관할 때 저장소가 꽉차서 늘릴 때 사용할 함수예요.
따라서 realloc 함수로 멤버 base에 할당한 저장소를 재할당하세요.

void EHArrayReserve(EHArray *arr,int capacity)
{
    arr->base = (Element *)realloc(arr->base,sizeof(Element)*capacity);
    arr->capacity = capacity;
}

PushBacks 함수는 n개를 순차 보관하는 함수예요.
PushBack 함수를 n 번 호출하면 되겠죠.

void EHArrayPushBacks(EHArray *arr,int n,Element value)
{
    int i = 0;
    for(i=0;i<n;i++)
    {
        EHArrayPushBack(arr,value);
    }
}

동적인 개체를 소멸하는 함수에서는 내부에서 동적으로 생성한 부분을 해제하고 자신의 메모리를 해제하세요.
자신의 메모리를 해제하는 부분을 제외한 나머지는 별도의 함수로 작성하세요.

void EHArrayTEHArray(EHArray *arr);
void DeleteEHArray(EHArray *arr)
{
    EHArrayTEHArray(arr);
    free(arr);
}
void EHArrayTEHArray(EHArray *arr)
{
    if(arr->base)
    {
        free(arr->base);
    }
}

저장소의 현재 크기와 보관한 요소 개수를 가져오기 함수에서는 단순히 멤버를 반환하면 되겠죠.

int EHArrayGetCapacity(EHArray *arr)
{
    return arr->capacity;
}
int EHArrayGetSize(EHArray *arr)
{
    return arr->size;
}

순차적으로 보관하는 PushBack 함수를 구현하기로 해요.
먼저 꽉 찼는지 확인하세요.
꽉 찼다면 저장소의 크기를 늘려줘야겠죠.
현재 저장소의 크기가 0이 아닐 때는 2배로 늘려주고 0일 때는 1로 늘려주기로 해요.
그리고 base에서 상대적 거리 size에 보관한 후에 size를 1 증가하세요.

void EHArrayPushBack(EHArray *arr,Element data)
{
    if(arr->capacity == arr->size)
    {
        if(arr->capacity)
        {
            EHArrayReserve(arr,arr->capacity*2);
        }
        else
        {
            EHArrayReserve(arr,1);
        }
    }
    arr->base[arr->size] = data;
    arr->size++;
}

보관한 데이터를 반환하는 GetAt함수에서는 입력 인자로 전달받은 index가 유효한지 확인하세요.
유효하면 base에서 상대적 거리 index에 보관한 요소를 반환하세요.

Element EHArrayGetAt(EHArray *arr,int index)
{
    if((index>=0)&&(index<arr->size))
    {
        return arr->base[index];
    }
    return 0;
}

보관한 요소를 다른 데이터로 설정하는 SetAt 함수에서도 입력 인자가 유효한지 확인하세요.
유효하면 base에서 상대적 거리 index에 입력 인자로 받은 데이터로 설정하세요.

void EHArraySetAt(EHArray *arr,int index, Element data)
{
    if((index>=0)&&(index<arr->size))
    {
        arr->base[index] = data;
    }
}

자료를 저장한(할) 첫 위치는 base 멤버가 갖고 있어요.
그리고 마지막 위치는 base에서 상대적 위치 size에요.
이번 실습에서 마지막 위치는 저장할 위치로 마지막 보관한 데이터가 있는 다음 위치로 하기로 했었죠.

Iterator EHArrayBegin(EHArray *arr)
{
    return arr->base;
}
Iterator EHArrayEnd(EHArray *arr)
{
    return arr->base + arr->size;
}

특정 위치에 자료를 제거하는 함수를 작성하죠.
제거할 위치 뒤쪽에 있는 모든 요소를 한 칸씩 앞으로 이동시키세요.
그런데 현재 보관한 요소 개수가 7이고 제거할 위치가 4라면 5,6,7에 있는 세 개의 데이터를 4,5,6로 이동시켜야 하겠죠.
이를 쉽게 구현하기 위해 먼저 보관한 요소 개수인 size를 1 감소 시킨 후에 하나씩 이동하세요.
이렇게 하면 입력 인자로 전달받은 위치에 다음 위치의 요소를 대입하는 것을 마지막 위치까지 수행하겠네요.

void EHArrayErase(EHArray *arr,Iterator it)
{
    Iterator end;
    arr->size--;
    end = arr->base + arr->size;
    for( ; it != end; it++)
    {
        (*it) = *(it+1);
    }
}

◈ EHArray.c

#include "EHArray.h"
#include <stdlib.h>
 
void EHArrayEHArray(EHArray *arr,int init_capa,Element init_value);
void EHArrayReserve(EHArray *arr,int capacity);
void EHArrayPushBacks(EHArray *arr,int n,Element value);
void EHArrayTEHArray(EHArray *arr);
 
EHArray *NewEHArray(int init_capa,Element init_value)
{
    EHArray *arr = (EHArray *)malloc(sizeof(EHArray));
    EHArrayEHArray(arr,init_capa,init_value);
    return arr;
}  
 
void EHArrayEHArray(EHArray *arr,int init_capa,Element init_value)
{
    arr->base = 0;
    arr->capacity = 0;
    arr->size = 0;
    if(init_capa>0)
    {
        EHArrayReserve(arr,init_capa);
        EHArrayPushBacks(arr,init_capa,init_value);
    }
}
void EHArrayReserve(EHArray *arr,int capacity)
{
    arr->base = (Element *)realloc(arr->base,sizeof(Element)*capacity);
    arr->capacity = capacity;
}
void EHArrayPushBacks(EHArray *arr,int n,Element value)
{
    int i = 0;
    for(i=0;i<n;i++)
    {
        EHArrayPushBack(arr,value);
    }
}
void DeleteEHArray(EHArray *arr)
{
    EHArrayTEHArray(arr);
    free(arr);
}  
 
void EHArrayTEHArray(EHArray *arr)
{
    if(arr->base)
    {
        free(arr->base);
    }
}
int EHArrayGetCapacity(EHArray *arr)
{
    return arr->capacity;
}
int EHArrayGetSize(EHArray *arr)
{
    return arr->size;
}


void EHArrayPushBack(EHArray *arr,Element data)
{
    if(arr->capacity == arr->size)
    {
        if(arr->capacity)
        {
            EHArrayReserve(arr,arr->capacity*2);
        }
        else
        {
            EHArrayReserve(arr,1);
        }
    }
    arr->base[arr->size] = data;
    arr->size++;
}
Element EHArrayGetAt(EHArray *arr,int index)
{
    if((index>=0)&&(index<arr->size))
    {
        return arr->base[index];
    }
    return 0;
}
void EHArraySetAt(EHArray *arr,int index, Element data)
{
    if((index>=0)&&(index<arr->size))
    {
        arr->base[index] = data;
    }
}
Iterator EHArrayBegin(EHArray *arr)
{
    return arr->base;   
}
Iterator EHArrayEnd(EHArray *arr)
{
    return arr->base + arr->size;
}
void EHArrayErase(EHArray *arr,Iterator it)
{
    Iterator end;
    arr->size--;
    end = arr->base + arr->size;
    for(  ; it != end; it++)
    {
        (*it) = *(it+1);
    }
}