Массивы в 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()front, back, fill, dataat() проверяет границы и бросает исключение#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.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?
vector.9) Советы и лучшие практики
std::array.std::span<T> (C++20) вместо пары указатель+длина.std::array используйте at() — поймаете выход за границы раньше.memcpy; для тривиальных типов ок, но для нетривиальных используйте алгоритмы и копирование элемент‑за‑элементом.Хотите быстро и системно прокачать основы и практику современного C++ с задачами и обратной связью? Рекомендую посмотреть программу и начать обучение по курсу: Стартовать в C++ с нуля до гуру — пошаговый курс с практикой.
Итог
Если вам нужен фиксированный по размеру массив — выбирайте std::array. Он безопаснее и удобнее C‑массива, дружит с алгоритмами и облегчает написание корректного кода. Для изменяемых по размеру коллекций используйте std::vector. А чтобы делать универсальные функции без копирования — применяйте std::span (C++20).



