Avatar ·

Как правильно использовать массивы в C++: от статических до динамических и std::array

Всем привет! Я только начал изучать C++ после Python, и у меня полная каша в голове с массивами. В Python я просто писал `arr = [1, 2, 3]` и всё работало. А тут... Я пытаюсь сделать простую программу, которая хранит 5 чисел и выводит их сумму. Написал вот так: ```cpp int arr[5]; arr = {1, 2, 3, 4, 5}; ``` Но компилятор ругается: "array type 'int[5]' is not assignable". Хорошо, попробовал инициализировать сразу: ```cpp int arr[5] = {1, 2, 3, 4, 5}; ``` Это сработало. Но потом мне понадобилось передать этот массив в функцию. Я написал: ```cpp void printSum(int arr[]) { int sum = 0; for(int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) { sum += arr[i]; } cout << sum; } ``` И тут вообще странность — `sizeof(arr)` возвращает 8 (размер указателя?), а не 20 (5 * 4 байта). Почему так? Плюс, я читал про `std::vector` и `std::array`, но не понимаю, когда что использовать. Ещё видел, как люди пишут `int* arr = new int[5]` — это вообще магия какая-то. Объясните, пожалуйста, как правильно работать с массивами в C++? Какие есть типы массивов, чем они отличаются, и как не наступать на грабли с размерами и указателями? Буду очень благодарен за примеры кода! <<>>

Основные типы массивов в C++

В C++ существует три основных способа работы с массивами, и каждый имеет свои особенности. Давайте разберем их от простого к сложному.

1. Статические массивы (C-style arrays)

Это то, с чем вы столкнулись. Они существуют в C++ еще со времен C и имеют ряд ограничений:

// Правильная инициализация
int arr[5] = {1, 2, 3, 4, 5};  // OK
int arr[] = {1, 2, 3, 4, 5};   // Размер выводится автоматически (5)

// НЕЛЬЗЯ присваивать после объявления
int arr[5];
arr = {1, 2, 3, 4, 5};  // Ошибка!

// НЕЛЬЗЯ копировать
int arr1[5] = {1, 2, 3, 4, 5};
int arr2[5] = arr1;  // Ошибка!

Проблема с sizeof в функции:

Когда вы передаете массив в функцию, он "распадается" (decays) в указатель. Поэтому sizeof(arr) внутри функции возвращает размер указателя (8 байт на 64-битной системе), а не размер массива.

// Правильный способ передачи статического массива
void printSum(int arr[], int size) {
    int sum = 0;
    for(int i = 0; i < size; i++) {
        sum += arr[i];
    }
    cout << sum;
}

// Или с использованием шаблона (сохраняет размер)
template<size_t N>
void printSum(int (&arr)[N]) {
    int sum = 0;
    for(int i = 0; i < N; i++) {
        sum += arr[i];
    }
    cout << sum;
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    printSum(arr);  // N = 5
}

2. Динамические массивы (new/delete)

Используются, когда размер массива неизвестен на этапе компиляции:

int size;
cout << "Enter size: ";
cin >> size;

int* arr = new int[size];  // Выделяем память в куче

// Заполняем
for(int i = 0; i < size; i++) {
    arr[i] = i * 10;
}

// Обязательно освобождаем память!
delete[] arr;

Важно: Каждый new[] требует соответствующего delete[]. Забыли — получите утечку памяти.

3. Современные C++ контейнеры (рекомендуемый способ)

std::array (фиксированный размер, C++11)

#include <array>

std::array<int, 5> arr = {1, 2, 3, 4, 5};

// Можно копировать!
std::array<int, 5> arr2 = arr;

// Знает свой размер
cout << arr.size();  // 5

// Безопасный доступ с проверкой границ
cout << arr.at(10);  // Выбросит исключение std::out_of_range

std::vector (динамический размер)

#include <vector>

std::vector<int> vec = {1, 2, 3};  // Размер 3
vec.push_back(4);                  // Теперь размер 4
vec.push_back(5);                  // Теперь размер 5

// Передача в функцию (всегда знает свой размер)
void printSum(const std::vector<int>& vec) {
    int sum = 0;
    for(int val : vec) {  // Range-based for loop
        sum += val;
    }
    cout << sum;
}

Когда что использовать?

  • std::vector — 90% случаев. Динамический размер, удобный, безопасный.
  • std::array — когда размер фиксирован и известен на этапе компиляции (например, матрица 3x3).
  • Статические массивы — в legacy коде или при работе с низкоуровневыми API (OpenGL, WinAPI).
  • Динамические массивы (new[]) — никогда в современном C++. Всегда используйте std::vector.

Исправленный пример с суммой:

#include <iostream>
#include <vector>

int main() {
    // Современный способ
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    
    int sum = 0;
    for(int num : numbers) {
        sum += num;
    }
    
    std::cout << "Sum: " << sum << std::endl;
    return 0;
}

Запомните главное правило: в современном C++ старайтесь избегать сырых массивов и указателей. Используйте std::vector или std::array — они безопаснее, удобнее и не дают совершить глупых ошибок с памятью.

<<>>

Альтернативный подход: работа с многомерными массивами

Если ваш вопрос касается многомерных массивов (матрицы, таблицы), то здесь тоже есть свои тонкости:

// Статический двумерный массив
int matrix[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

// Доступ к элементу
cout << matrix[1][2];  // 7

// Использование std::vector для динамической матрицы
std::vector<std::vector<int>> dynamicMatrix(3, std::vector<int>(4, 0));
dynamicMatrix[1][2] = 42;

// Или более эффективный вариант с одномерным вектором
std::vector<int> flatMatrix(3 * 4, 0);
int rows = 3, cols = 4;
flatMatrix[1 * cols + 2] = 42;  // Эквивалент [1][2]

Для многомерных массивов также настоятельно рекомендую использовать контейнеры STL. Они избавят вас от головной боли с ручным управлением памятью и обеспечат безопасность типов.