Массивы в C++: C‑массивы и std::array — понятное практическое руководство

Массивы в C++: C‑массивы и std::array — понятное практическое руководство

Массивы в C++: C‑массивы и std::array — понятное практическое руководство

Массивы в C++: C‑массивы vs std::array — практическое руководство для начинающих

Запрос, который часто вводят в поиск: «массивы в C++ std::array для начинающих». В этой статье вы разберётесь, чем отличаются C‑массивы и std::array, как правильно передавать их в функции, как использовать со стандартными алгоритмами и где чаще всего случаются ошибки. В конце — мини‑практикум и рекомендации, что выбрать: C‑массив, std::array или std::vector.

1) Что такое C‑массив и с чего начинается боль

#include <iostream>

int main() {
    int a[5] = {1, 2, 3}; // недостающие элементы обнулятся: {1,2,3,0,0}

    std::cout << a[0] << "n";   // 1
    std::cout << sizeof(a) << "n"; // 20 (на тип int по 4 байта) — только здесь, в этой области видимости!
}

Особенность C‑массива: при передаче в функцию он «растворяется» до указателя на первый элемент, и информация о размере теряется. Из‑за этого новички часто ошибаются с sizeof и выходом за границы.

Неправильная передача C‑массива в функцию

#include <iostream>

void print_bad(int arr[]) { // arr == int*; размер неизвестен!
    // Плохо: sizeof(arr) == sizeof(int*), а не размер массива в байтах
    std::cout << "sizeof(arr) = " << sizeof(arr) << "n";
}

int main() {
    int a[5] = {1,2,3,4,5};
    print_bad(a);
}

Как исправить? Либо передавать размер отдельно, либо — лучше — передавать ссылку на массив известного размера через шаблон.

Правильно: шаблонная ссылка на массив

#include <iostream>

template <std::size_t N>
void print_array(const int (&arr)[N]) {
    for (std::size_t i = 0; i < N; ++i) {
        std::cout << arr[i] << (i + 1 == N ? "n" : " ");
    }
}

int main() {
    int a[5] = {1,2,3,4,5};
    print_array(a); // N=5 выведен из типа
}

Это безопасно и даёт компилятору знать размер на этапе компиляции. Но работать так с каждым C‑массивом неудобно. Поэтому есть std::array.

2) std::array: фиксированный размер, безопасность и удобство

std::array<T, N> — контейнер фиксированной длины из стандартной библиотеки. Он хранит элементы прямо внутри себя (как C‑массив), но при этом:

  • знает свой размер: .size()
  • работает со стандартными алгоритмами STL
  • имеет методы front, back, fill, data
  • безопаснее: at() проверяет границы и бросает исключение
  • #include <iostream>
    #include <array>
    
    int main() {
        std::array<int, 5> a = {1,2,3,4,5};
    
        std::cout << a.size() << "n";  // 5
        std::cout << a.front() << "n"; // 1
        std::cout << a.back()  << "n"; // 5
    
        a[0] = 10;          // без проверки
        // a.at(10) = 42;   // std::out_of_range (если раскомментировать)
    
        for (int x : a) std::cout << x << ' ';
    }
    

    3) Алгоритмы STL со std::array

    Алгоритмы работают «из коробки», поскольку std::array предоставляет итераторы.

    #include <iostream>
    #include <array>
    #include <algorithm> // sort, reverse, find
    
    int main() {
        std::array<int, 6> a = {3,1,4,1,5,9};
    
        std::sort(a.begin(), a.end());
        std::reverse(a.begin(), a.end());
    
        auto it = std::find(a.begin(), a.end(), 4);
        if (it != a.end()) {
            std::cout << "found at index: " << std::distance(a.begin(), it) << "n";
        }
    }
    

    4) Взаимодействие с C‑API: .data()

    Нужно передать «сырой» указатель в функцию C‑библиотеки? У std::array есть .data() — адрес первого элемента.

    #include <array>
    #include <cstring> // memcpy
    
    int main() {
        std::array<char, 8> buf{};
        const char* src = "hello";
        std::memcpy(buf.data(), src, 6); // включая завершающий 
    }
    

    5) Универсальная передача массивов: std::span (C++20)

    std::span<T> — «не владеющий» вид на непрерывную последовательность. Подходит для C‑массивов, std::array и std::vector без копирования и без потери размера.

    #include <span>
    #include <vector>
    #include <array>
    #include <iostream>
    
    int sum(std::span<const int> s) {
        int acc = 0;
        for (int x : s) acc += x;
        return acc;
    }
    
    int main() {
        int c_arr[3] = {1,2,3};
        std::array<int,3> a = {4,5,6};
        std::vector<int> v = {7,8,9};
    
        std::cout << sum(c_arr) << "n"; // 6
        std::cout << sum(a)     << "n"; // 15
        std::cout << sum(v)     << "n"; // 24
    }
    

    Если у вас C++17 и ниже — можно принимать пару итераторов или указатель+длину, но span заметно упрощает интерфейс.

    6) Частые ошибки с массивами

  • Использование sizeof на параметре функции, где массив уже «схлопнулся» до указателя. Решение: std::array, std::span или шаблонная ссылка.
  • Выход за границы (arr[i]>=arr.size()). Для отладки используйте at() у std::array — он бросает исключение.
  • Ожидание «динамической» длины от std::array. Его размер фиксирован во время компиляции. Нужна изменяемая длина — используйте std::vector.
  • Забыли инициализировать. Для C‑массивов используйте список инициализации в фигурных скобках; std::array можно сбросить методом fill.
  • 7) Мини‑практикум: частотность и фильтрация

    Посчитаем количество чётных элементов и отфильтруем исходную последовательность в новый буфер. Реализуем на std::span и std::array.

    #include <array>
    #include <span>
    #include <iostream>
    
    std::size_t count_even(std::span<const int> s) {
        std::size_t cnt = 0;
        for (int x : s) if (x % 2 == 0) ++cnt;
        return cnt;
    }
    
    // Фильтруем чётные в целевой std::array фиксированного размера
    // Возвращаем реальное число записанных элементов
    std::size_t filter_even(std::span<const int> in, std::span<int> out) {
        std::size_t w = 0;
        for (int x : in) {
            if (x % 2 == 0) {
                if (w < out.size()) out[w++] = x; // защита от переполнения
            }
        }
        return w;
    }
    
    int main() {
        std::array<int, 8> src = {3, 6, 1, 8, 10, 5, 4, 7};
        std::array<int, 8> dst{};
    
        std::cout << "even count = " << count_even(src) << "n";
    
        std::size_t n = filter_even(src, dst);
        for (std::size_t i = 0; i < n; ++i) std::cout << dst[i] << ' '; // 6 8 10 4
    }
    

    8) Что выбрать: C‑массив, std::array или std::vector?

  • C‑массив: минимум накладных расходов, хорош для низкоуровневого кода и взаимодействия с C‑API. Требует дисциплины в передаче размера и проверке границ.
  • std::array: фиксированная длина + удобный интерфейс STL. Идеален для небольших статических буферов, константных таблиц, быстрых стековых структур.
  • std::vector: когда размер должен меняться во время выполнения. Если сомневаетесь — начинайте с vector.
  • 9) Советы и лучшие практики

  • Для фиксированного размера в современном C++ почти всегда предпочтителен std::array.
  • Для универсальных API используйте std::span<T> (C++20) вместо пары указатель+длина.
  • При отладке обращений по индексу у std::array используйте at() — поймаете выход за границы раньше.
  • Не злоупотребляйте memcpy; для тривиальных типов ок, но для нетривиальных используйте алгоритмы и копирование элемент‑за‑элементом.
  • Хотите быстро и системно прокачать основы и практику современного C++ с задачами и обратной связью? Рекомендую посмотреть программу и начать обучение по курсу: Стартовать в C++ с нуля до гуру — пошаговый курс с практикой.

    Итог

    Если вам нужен фиксированный по размеру массив — выбирайте std::array. Он безопаснее и удобнее C‑массива, дружит с алгоритмами и облегчает написание корректного кода. Для изменяемых по размеру коллекций используйте std::vector. А чтобы делать универсальные функции без копирования — применяйте std::span (C++20).

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий