1. Array 클래스: 고정 크기의 동적 배열

코드 분석

1.1 헤더 파일과 클래스 선언

#pragma once
#include <assert.h>

class Array {
    using T = int;
  • #pragma once: 헤더 파일의 중복 포함을 방지합니다.
  • #include <assert.h>: assert는 디버깅 중 조건이 참인지 확인하는 매크로입니다. 조건이 거짓일 경우 프로그램을 중단시킵니다.
  • using T = int: 타입 별칭으로 T를 정의하여, 나중에 변경이 쉽도록 합니다. 이 클래스는 정수형 배열(int)을 다룹니다.

1.2 생성자와 소멸자

public:
    explicit Array(int capacity = 100) : _capacity(capacity) {
        _buffer = new T[capacity];
    }

    ~Array() {
        delete[] _buffer;
    }
  1. explicit Array(int capacity = 100):

    • 생성자로, 기본적으로 용량을 100으로 설정하지만, 사용자가 다른 값을 지정할 수도 있습니다.
    • _buffer = new T[capacity]: 배열을 동적으로 생성합니다.
  2. ~Array():

    • 소멸자로, 객체가 소멸될 때 동적으로 할당된 메모리를 해제합니다.
    • delete[] _buffer: 배열을 삭제합니다. 메모리 누수를 방지하기 위해 반드시 호출됩니다.

1.3 데이터 추가: push_back

void push_back(const T& data) {
    if (_size == _capacity) {
        return;  // 용량 초과 시 데이터를 추가하지 않음
    }
    _buffer[_size] = data;  // 배열 끝에 데이터 추가
    _size++;                // 크기 증가
}
  • 역할: 배열의 마지막 위치에 데이터를 추가합니다.
  • 용량 검사: _size == _capacity를 확인하여 용량 초과 여부를 판단합니다.
  • 데이터 추가:
    • _buffer[_size] = data: 배열 끝에 데이터를 저장합니다.
    • _size++: 배열 크기를 증가시킵니다.

1.4 데이터 접근: operator[]

T& operator[](int index) {
    assert(index >= 0 && index < _size);  // 인덱스 범위 검사
    return _buffer[index];
}
  • 역할: 배열의 요소를 인덱스로 접근할 수 있도록 연산자를 오버로딩합니다.
  • 범위 검사:
    • assert(index >= 0 && index < _size): 유효한 인덱스인지 확인합니다. 유효하지 않으면 프로그램을 중단합니다.
  • 요소 반환:
    • return _buffer[index]: 인덱스에 해당하는 데이터를 반환합니다.

1.5 기타 멤버 함수

int size() { return _size; }
int capacity() { return _capacity; }
  • size(): 현재 저장된 데이터의 개수를 반환합니다.
  • capacity(): 배열의 용량을 반환합니다.

1.6 멤버 변수

private:
    T* _buffer = nullptr;  // 데이터 저장 공간
    int _size = 0;         // 현재 크기
    int _capacity = 0;     // 최대 용량
};
  • _buffer: 동적으로 할당된 배열을 가리키는 포인터.
  • _size: 현재 저장된 요소의 개수.
  • _capacity: 배열의 최대 크기.

Array 클래스의 사용 예제

Array arr(100);

arr.push_back(1);
arr.push_back(2);
arr.push_back(3);

arr[1] = 10;  // 배열 요소 수정
int val = arr[1000];  // 잘못된 접근 (assert로 오류 발생)

2. Vector 클래스: 크기 조정이 가능한 동적 배열

코드 분석

2.1 템플릿과 클래스 선언

#pragma once
#include <assert.h>

template<typename T>
class Vector {
public:
    explicit Vector() {}
    ~Vector() {
        if (_buffer) {
            delete[] _buffer;
        }
    }
  • 템플릿 클래스:

    • template<typename T>: 데이터 타입에 대해 일반화된 구현을 제공합니다.
    • 예: Vector<int>, Vector<float>, Vector<std::string> 등.
  • 생성자와 소멸자:

    • Vector()는 초기화를 수행하지 않습니다.
    • ~Vector()_buffer가 할당되었는지 확인 후 해제합니다.

2.2 데이터 추가: push_back

void push_back(const T& data) {
    if (_size == _capacity) {
        int newCapacity = static_cast<int>(_capacity * 1.5);
        if (newCapacity == _capacity) {
            newCapacity++;
        }
        reserve(newCapacity);
    }
    _buffer[_size] = data;
    _size++;
}
  1. 용량 검사 및 확장:

    • _size == _capacity일 경우 reserve를 호출하여 배열의 용량을 늘립니다.
    • 새 용량은 현재 용량의 1.5배로 설정되며, 최소 1씩 증가합니다.
  2. 데이터 추가:

    • _buffer[_size] = data: 배열의 마지막에 데이터를 저장합니다.
    • _size++: 크기를 증가시킵니다.

2.3 용량 조정: reserve

void reserve(int capacity) {
    if (_capacity >= capacity) {
        return;
    }

    _capacity = capacity;
    T* newData = new T[_capacity];
    for (int i = 0; i < _size; i++) {
        newData[i] = _buffer[i];
    }

    if (_buffer) {
        delete[] _buffer;
    }

    _buffer = newData;
}
  1. 확장 필요 확인:

    • _capacity >= capacity: 이미 충분한 용량이 있으면 반환합니다.
  2. 새 버퍼 생성 및 데이터 복사:

    • newData = new T[_capacity]: 새 용량으로 배열을 생성합니다.
    • newData[i] = _buffer[i]: 기존 데이터를 새 배열로 복사합니다.
  3. 기존 버퍼 해제:

    • delete[] _buffer: 기존 배열을 삭제합니다.
  4. 새 버퍼로 교체:

    • _buffer = newData: 새 배열을 현재 버퍼로 설정합니다.

2.4 기타 멤버 함수

void pop_back() {
    _size--;
}

T& back() {
    return _buffer[_size - 1];
}

void clear() {
    if (_buffer) {
        delete[] _buffer;
        _buffer = new T[_capacity];
    }
    _size = 0;
}
  1. pop_back: 배열의 마지막 요소를 제거합니다.
  2. back: 마지막 요소를 반환합니다.
  3. clear: 배열의 모든 데이터를 초기화합니다. 용량은 유지됩니다.

Vector 클래스의 사용 예제

Vector<int> v;

v.push_back(1);
v.push_back(2);
v.push_back(3);

v[1] = 10;  // 배열 요소 수정
v.pop_back();  // 마지막 요소 제거
v.clear();  // 배열 초기화

3. resizereserve의 차이

  • resize:

    • 배열의 크기를 변경합니다. 크기 증가 시 기본값으로 초기화됩니다.
    • 크기 감소 시 요소가 삭제됩니다.
  • reserve:

    • 배열의 용량만 변경하며, 크기는 변경되지 않습니다.
    • 추가 데이터를 저장할 공간을 미리 확보하는 데 사용됩니다.

동적 배열의 구현과 장점

  1. 장점:

    • 크기를 동적으로 조정할 수 있어 메모리 낭비를 최소화합니다.
    • STL의 std::vector처럼 동작하며, 다양한 데이터 타입에 적용 가능합니다.
  2. 단점:

    • 크기를 조정할 때 데이터 복사 비용이 발생합니다.
  3. 사용 사례:

    • 요소의 추가/삭제가 빈번한 경우.
    • 크기가 유동적인 데이터를 저장할 때.
profile
李家네_공부방

0개의 댓글