Динамические массивы — основа многих приложений на C++, это гибкое хранилище, которое расширяется вместе с данными. Изучим принцип работы этих массивов, как они создаются и когда используются в реальных сценариях.
Создание простого динамического массива
Вот базовая реализация с основными концепциями:
template<typename T>
class DynamicArray {
private:
T* data_;
size_t size_;
size_t capacity_;
void resize(size_t new_capacity) {
T* new_data = new T[new_capacity];
for (size_t i = 0; i < size_; ++i) {
new_data[i] = std::move(data_[i]);
}
delete[] data_;
data_ = new_data;
capacity_ = new_capacity;
}
public:
DynamicArray() : data_(new T[2]), size_(0), capacity_(2) {}
~DynamicArray() {
delete[] data_;
}
void push_back(const T& value) {
if (size_ == capacity_) {
resize(capacity_ * 2);
}
data_[size_++] = value;
}
T& operator[](size_t index) {
if (index >= size_) {
throw std::out_of_range("Index out of bounds");
}
return data_[index];
}
size_t size() const { return size_; }
size_t capacity() const { return capacity_; }
};
Реальный пример: логгер событий
Вот практическое применение динамического массива:
struct LogEntry {
std::string timestamp;
std::string message;
std::string severity;
LogEntry(std::string ts, std::string msg, std::string sev)
: timestamp(std::move(ts)),
message(std::move(msg)),
severity(std::move(sev)) {}
};
class EventLogger {
private:
DynamicArray<LogEntry> logs;
public:
void log(const std::string& message, const std::string& severity) {
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
std::string timestamp = std::ctime(&time);
timestamp.pop_back(); // Удаляется символ новой строки
logs.push_back(LogEntry(timestamp, message, severity));
}
void dump_logs() const {
for (size_t i = 0; i < logs.size(); ++i) {
std::cout << logs[i].timestamp << " ["
<< logs[i].severity << "] "
<< logs[i].message << "\n";
}
}
};
Добавление важного функционала
Расширим функционал динамического массива:
template<typename T>
class DynamicArray {
// ... предыдущий код ...
public:
// Конструктор копирования
DynamicArray(const DynamicArray& other)
: data_(new T[other.capacity_]),
size_(other.size_),
capacity_(other.capacity_) {
for (size_t i = 0; i < size_; ++i) {
data_[i] = other.data_[i];
}
}
// Конструктор перемещения
DynamicArray(DynamicArray&& other) noexcept
: data_(other.data_),
size_(other.size_),
capacity_(other.capacity_) {
other.data_ = nullptr;
other.size_ = other.capacity_ = 0;
}
// Оператор присваивания
DynamicArray& operator=(const DynamicArray& other) {
if (this != &other) {
delete[] data_;
data_ = new T[other.capacity_];
size_ = other.size_;
capacity_ = other.capacity_;
for (size_t i = 0; i < size_; ++i) {
data_[i] = other.data_[i];
}
}
return *this;
}
void pop_back() {
if (size_ > 0) {
--size_;
}
}
void clear() {
size_ = 0;
}
bool empty() const {
return size_ == 0;
}
T& front() {
if (empty()) {
throw std::out_of_range("Array is empty");
}
return data_[0];
}
T& back() {
if (empty()) {
throw std::out_of_range("Array is empty");
}
return data_[size_ - 1];
}
};
Динамические массивы в очереди задач
Вот практический пример использования динамических массивов в системе планирования задач:
class Task {
public:
std::function<void()> function;
int priority;
std::string name;
Task(std::function<void()> f, int p, std::string n)
: function(std::move(f)), priority(p), name(std::move(n)) {}
};
class TaskQueue {
private:
DynamicArray<Task> tasks;
public:
void add_task(std::function<void()> func, int priority,
const std::string& name) {
tasks.push_back(Task(std::move(func), priority, name));
// Выполняется сортировка по приоритету в порядке убывания
for (size_t i = tasks.size() - 1; i > 0; --i) {
if (tasks[i].priority > tasks[i-1].priority) {
std::swap(tasks[i], tasks[i-1]);
} else {
break;
}
}
}
void execute_all() {
while (!tasks.empty()) {
tasks.front().function();
tasks.pop_back();
}
}
void show_queue() const {
for (size_t i = 0; i < tasks.size(); ++i) {
std::cout << "Task: " << tasks[i].name
<< " (Priority: " << tasks[i].priority << ")\n";
}
}
};
Управление памятью и производительность
Вот как оптимизируется производительность динамического массива:
template<typename T>
class DynamicArray {
// ... предыдущий код ...
public:
void reserve(size_t new_capacity) {
if (new_capacity > capacity_) {
resize(new_capacity);
}
}
void shrink_to_fit() {
if (capacity_ > size_) {
resize(size_);
}
}
// Эффективная вставка в любой позиции
void insert(size_t index, const T& value) {
if (index > size_) {
throw std::out_of_range("Invalid insertion index");
}
if (size_ == capacity_) {
resize(capacity_ * 2);
}
for (size_t i = size_; i > index; --i) {
data_[i] = std::move(data_[i-1]);
}
data_[index] = value;
++size_;
}
// Эффективное удаление в любой позиции
void erase(size_t index) {
if (index >= size_) {
throw std::out_of_range("Invalid erasure index");
}
for (size_t i = index; i < size_ - 1; ++i) {
data_[i] = std::move(data_[i+1]);
}
--size_;
}
};
Реальное применение: буфер обработки изображений
Вот как динамические массивы используются при обработке изображений:
struct Pixel {
uint8_t r, g, b;
Pixel(uint8_t r = 0, uint8_t g = 0, uint8_t b = 0)
: r(r), g(g), b(b) {}
};
class ImageBuffer {
private:
DynamicArray<Pixel> pixels;
size_t width_;
size_t height_;
public:
ImageBuffer(size_t width, size_t height)
: width_(width), height_(height) {
pixels.reserve(width * height);
for (size_t i = 0; i < width * height; ++i) {
pixels.push_back(Pixel());
}
}
void set_pixel(size_t x, size_t y, const Pixel& p) {
if (x >= width_ || y >= height_) {
throw std::out_of_range("Pixel coordinates out of bounds");
}
pixels[y * width_ + x] = p;
}
Pixel& get_pixel(size_t x, size_t y) {
if (x >= width_ || y >= height_) {
throw std::out_of_range("Pixel coordinates out of bounds");
}
return pixels[y * width_ + x];
}
void apply_grayscale() {
for (size_t i = 0; i < pixels.size(); ++i) {
uint8_t gray = static_cast<uint8_t>(
(pixels[i].r + pixels[i].g + pixels[i].b) / 3);
pixels[i].r = pixels[i].g = pixels[i].b = gray;
}
}
};
Этими реализациями демонстрируется, что динамические массивы — это основа многих реальных приложений.
Не забывайте бережно относиться к памяти и при выборе между пользовательским динамическим массивом и std::vector учитывать конкретный сценарий. Хотя vector стандартной библиотеки хорошо протестирован и оптимизирован для большинства случаев, благодаря пониманию принципов работы динамических массивов принимаются более взвешенные решения относительно того, когда использовать каждый из вариантов.
Читайте также:
- Как проходится ассоциативный массив на C++
- C++: полное руководство по циклам while
- Механика разрешения имен и связывания на C++
Читайте нас в Telegram, VK и Дзен
Перевод статьи ryan: Dynamic Arrays in C++: Comprehensive Guide





