C++로 동적 배열 라이브러리 구현하기

1. 동적 배열의 특징

일반 정적 배열은 선언 시점에 크기가 고정되지만, 동적 배열은 실행 중에 필요한 만큼 크기를 늘리거나 줄일 수 있다. 이를 직접 라이브러리 형태로 구현하면 메모리 관리와 사용자 인터페이스를 분리할 수 있어, 재사용성과 안정성이 아진다.

2. 헤더 설계

다음은 라이브러리에서 제공하는 모든 기능을 선언한 헤더 파일이다. 실제 struct 정의는 구현 파일로 숨기고, 외부에는 포인터 타입과 함수 시그니처만 노출한다.

#ifndef RESIZABLE_ARRAY_H
#define RESIZABLE_ARRAY_H

struct ResizableArray;
typedef struct ResizableArray RArray;
typedef struct ResizableArray* PRArray;

PRArray create_array(int initial_size);
void    destroy_array(PRArray& arr);
int     get_size(PRArray& arr);
int*    get_buffer(PRArray& arr);

void read_elements(PRArray& arr);
void print_elements(PRArray& arr);
void swap_elements(PRArray& arr, int i, int j);

void bubble_sort(PRArray& arr);
int  binary_search(PRArray& arr, int target);

void resize(PRArray& arr, int new_size);
void append(PRArray& arr, int count);
void insert_at(PRArray& arr, int idx, int value);
void remove_last(PRArray& arr, int count);
void remove_at(PRArray& arr, int idx);

#endif

3. 구현

구현 파일에서 구조체를 정의하고, 각 함수의 구체적인 동작을 기술한다. size는 현재 저장된 요소 수, capacity는 할당된 메모리 크기를 의미한다.

#include <iostream>
#include "ResizableArray.h"

struct ResizableArray {
    int size;
    int capacity;
    int* buffer;
};

PRArray create_array(int initial_size) {
    PRArray arr = new RArray;
    arr->size = initial_size;
    arr->capacity = initial_size;
    arr->buffer = new int[initial_size];
    return arr;
}

void destroy_array(PRArray& arr) {
    delete[] arr->buffer;
    delete arr;
    arr = nullptr;
}

int get_size(PRArray& arr) {
    return arr->size;
}

int* get_buffer(PRArray& arr) {
    return arr->buffer;
}

void read_elements(PRArray& arr) {
    std::cout << "Enter " << arr->size << " integers: ";
    for (int i = 0; i < arr->size; ++i) {
        std::cin >> arr->buffer[i];
    }
}

void print_elements(PRArray& arr) {
    for (int i = 0; i < arr->size; ++i) {
        std::cout << arr->buffer[i] << " ";
    }
    std::cout << "\n";
}

void swap_elements(PRArray& arr, int i, int j) {
    int n = arr->size;
    if (i < 0 || j < 0 || i >= n || j >= n) {
        std::exit(1);
    }
    int tmp = arr->buffer[i];
    arr->buffer[i] = arr->buffer[j];
    arr->buffer[j] = tmp;
}

void bubble_sort(PRArray& arr) {
    int n = arr->size;
    int* data = arr->buffer;
    int last_swap = n - 1;
    for (int i = 0; i < n - 1; ++i) {
        int end = last_swap;
        bool swapped = false;
        for (int j = 0; j < end; ++j) {
            if (data[j] > data[j + 1]) {
                int tmp = data[j];
                data[j] = data[j + 1];
                data[j + 1] = tmp;
                swapped = true;
                last_swap = j;
            }
        }
        if (!swapped) break;
    }
}

int binary_search(PRArray& arr, int target) {
    int low = 0, high = arr->size - 1;
    int* data = arr->buffer;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (data[mid] == target) return mid;
        if (data[mid] > target) high = mid - 1;
        else low = mid + 1;
    }
    return -1;
}

void resize(PRArray& arr, int new_size) {
    int* tmp = new int[new_size];
    int copy_count = (new_size < arr->size) ? new_size : arr->size;
    for (int i = 0; i < copy_count; ++i) {
        tmp[i] = arr->buffer[i];
    }
    if (new_size > arr->size) {
        std::cout << "Enter " << new_size - arr->size << " integers: ";
        for (int i = arr->size; i < new_size; ++i) {
            std::cin >> tmp[i];
        }
    }
    delete[] arr->buffer;
    arr->buffer = tmp;
    arr->size = new_size;
    if (new_size > arr->capacity) {
        arr->capacity = new_size;
    }
}

void append(PRArray& arr, int count) {
    resize(arr, arr->size + count);
}

void insert_at(PRArray& arr, int idx, int value) {
    int n = arr->size;
    if (idx < 0 || idx > n) std::exit(1);
    int* tmp = new int[n + 1];
    for (int i = 0; i < idx; ++i) {
        tmp[i] = arr->buffer[i];
    }
    tmp[idx] = value;
    for (int i = idx; i < n; ++i) {
        tmp[i + 1] = arr->buffer[i];
    }
    delete[] arr->buffer;
    arr->buffer = tmp;
    arr->size = n + 1;
    if (arr->size > arr->capacity) {
        arr->capacity = arr->size;
    }
}

void remove_last(PRArray& arr, int count) {
    if (count > arr->size) std::exit(1);
    resize(arr, arr->size - count);
}

void remove_at(PRArray& arr, int idx) {
    int n = arr->size;
    if (idx < 0 || idx >= n) std::exit(1);
    int* tmp = new int[n - 1];
    for (int i = 0; i < idx; ++i) {
        tmp[i] = arr->buffer[i];
    }
    for (int i = idx; i < n - 1; ++i) {
        tmp[i] = arr->buffer[i + 1];
    }
    delete[] arr->buffer;
    arr->buffer = tmp;
    arr->size = n - 1;
}

4. 사용 예제

다음 main.cpp는 라이브러리의 주요 기능을 테스트한다.

#include <iostream>
#include "ResizableArray.h"

int main() {
    PRArray arr = create_array(5);

    std::cout << "Size: " << get_size(arr) << "\n";
    std::cout << "Buffer address: " << get_buffer(arr) << "\n";

    read_elements(arr);
    print_elements(arr);

    swap_elements(arr, 0, 2);
    print_elements(arr);

    bubble_sort(arr);
    print_elements(arr);

    int pos = binary_search(arr, 3);
    std::cout << "Index of 3: " << pos << "\n";

    std::cout << "Before resize: " << get_buffer(arr) << "\n";
    resize(arr, 3);
    std::cout << "After resize: " << get_buffer(arr) << "\n";
    print_elements(arr);

    append(arr, 3);
    print_elements(arr);

    insert_at(arr, 2, 9);
    print_elements(arr);

    remove_last(arr, 2);
    print_elements(arr);

    remove_at(arr, 3);
    print_elements(arr);

    destroy_array(arr);
    return 0;
}

5. 불완전형 선언과 정보 은닉

헤더에서 struct ResizableArray;만 선언하고 구현 파일에서 정의하는 이유는 정보 은닉을 위해서다. main.cpp에서 헤더만 포함하면 ResizableArray의 멤버를 알 수 없으므로, arr->sizearr->buffer 같은 직 접근은 컴파일 오류가 발생한다. 사용자는 포인터와 공개된 함수만 사용해야 하며, 내부 데이터는 라이브러리 구현에서만 조작된다.

이 방식은 클래스의 private 멤버를 외부에 숨기는 것과 같은 효과를 가진다. 또한 이진 배포 형태로 라이브러리를 제공할 때 구현 세부 사항이 노출되지 않아, 사용자의 임의 수정으로 인한 문제를 방지할 수 있다.

태그: C++ DynamicArray DataStructure OpaquePointer InformationHiding

8월 17일 19:06에 게시됨