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);
#endif3. 구현
구현 파일에서 구조체를 정의하고, 각 함수의 구체적인 동작을 기술한다. 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->size나 arr->buffer 같은 직 접근은 컴파일 오류가 발생한다. 사용자는 포인터와 공개된 함수만 사용해야 하며, 내부 데이터는 라이브러리 구현에서만 조작된다.
이 방식은 클래스의 private 멤버를 외부에 숨기는 것과 같은 효과를 가진다. 또한 이진 배포 형태로 라이브러리를 제공할 때 구현 세부 사항이 노출되지 않아, 사용자의 임의 수정으로 인한 문제를 방지할 수 있다.