Массивы в C++

В этой статье мы поговорим про массивы в языке C++. 

Массив (англ. array) - это упорядоченная коллекция элементов одного и того же типа данных. Массив имеет имя, а также размер, который задаёт количество элементов, которые в нём находятся. Можно также представить себе массив как коллекцию переменных, у которых одинаковый тип данных. Имя массива - это по сути имя переменной для массива, по которой осуществляется доступ к его элементам.

Давайте посмотрим на пример программы на C++, где объявляется массив целых чисел (тип данных int) с именем my_array:

#include <iostream>

using namespace std;

int main() {
    int my_array[] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };

    int my_array_size = sizeof(my_array) / sizeof(int);
    cout << "The size of array 'my_array': " << my_array_size << endl;
    cout << "Elements of 'my_array':" << endl;

    for (int i = 0; i < my_array_size; i++) {
        cout << "my_array[" << i << "] = " << my_array[i] << endl;
    }
}

Итак, рассмотрим, что делает этот пример:

  • в начале метода main(), который является входной точкой нашей программы, объявляется массив с именем my_array. Обратите внимание, что после имени массива указываются квадратные скобки [], что как раз является индикатором того, что my_array - это не просто переменная типа int, а именно массив элементов типа int. В этой же строке мы производим инициализацию нашего массива с перечислением всех входящих в него чисел внутри фигурных скобок { }.
  • в этом примере мы не указали явным образом количество элементов, входящих в массив: квадратные скобки [] указаны без явной спецификации размера массива. Однако компилятор C++ сделает это за нас - поскольку мы инициализируем наш массив статически, то компилятор может самостоятельно понять, что в наш массив входит 10 элементов.
  • далее мы объявляем переменную my_array_size, которой присваиваем результат от деления sizeof(my_array) на sizeof(int). Такая форма записи и вычисления размера массива может подойти  как раз для случая, когда размер массива в программе не задан явно, но нужно понять, сколько элементов всё же входит в массив. Оператор sizeof(my_array) вернёт значение 40, поскольку в данном случае оператор sizeof вычисляет общее количество байт, выделенных для хранения элементов нашего массива в памяти. Поскольку из предыдущих статей мы уже знаем с вами, что размер типа данных int составляет 4 байта, а также знаем, что мы поместили в массив 10 элементов, то под массив при запуске программы будет выделено суммарно 40 байт. Оператор sizeof(int) вернёт значение 4, т.к. это размер в байтах типа int. В результате деления 40 на 4 мы получим 10, что и является расчётным размером нашего массива.
  • в последующих строках программы мы выводим на экран размер нашего массива, а также все элементы нашего массива в цикле for. С циклами мы также знакомились с вами ранее в соответствующей статье.

Если вы запустите программу, то увидите на экране консоли следующий результат:

The size of array 'my_array': 10
Elements of 'my_array':
my_array[0] = 10
my_array[1] = 20
my_array[2] = 30
my_array[3] = 40
my_array[4] = 50
my_array[5] = 60
my_array[6] = 70
my_array[7] = 80
my_array[8] = 90
my_array[9] = 100

Теперь давайте чуть изменим строку, где происходит инициализация нашего массива, следующим образом, указав явно размер массива ( 10 ) в квадратных скобках:

int my_array[10] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };

Как видите, вы также можете сразу напрямую указать точный размер массива при статической инициализации его элементов. Если вы снова запустите программу с этим исправлением, то результат вывода на консоль ничуть не изменится относительно предыдущего запуска.

Давайте поменяем размер нашего массива с 10 на 15:

int my_array[15] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };

Снова запустим нашу программу. В этот раз мы увидим изменения в выводе на консоль:

The size of array 'my_array': 15
Elements of 'my_array':
my_array[0] = 10
my_array[1] = 20
my_array[2] = 30
my_array[3] = 40
my_array[4] = 50
my_array[5] = 60
my_array[6] = 70
my_array[7] = 80
my_array[8] = 90
my_array[9] = 100
my_array[10] = 0
my_array[11] = 0
my_array[12] = 0
my_array[13] = 0
my_array[14] = 0

Вы можете заметить, что оставшиеся 5 элементов массива, которые мы не инициализировали статически, проинициализировались автоматически значением 0, об этом следует помнить и учитывать в том случае, если указываете при инициализации массива не все элементы сразу.

Массив в программе на C++ можно объявить без явной статической инициализации его элементов. Давайте немного перепишем нашу программу, удалив часть с фигурными скобками, где указываются элементы нашего массива. У вас должно получиться следующее:

#include <iostream>

using namespace std;

int main() {
    int my_array[15];

    int my_array_size = sizeof(my_array) / sizeof(int);
    cout << "The size of array 'my_array': " << my_array_size << endl;
    cout << "Elements of 'my_array':" << endl;

    for (int i = 0; i < my_array_size; i++) {
        cout << "my_array[" << i << "] = " << my_array[i] << endl;
    }
}

Запустите программу и посмотрите на результат. У меня вышло следующее:

The size of array 'my_array': 15
Elements of 'my_array':
my_array[0] = -858993460
my_array[1] = -858993460
my_array[2] = -858993460
my_array[3] = -858993460
my_array[4] = -858993460
my_array[5] = -858993460
my_array[6] = -858993460
my_array[7] = -858993460
my_array[8] = -858993460
my_array[9] = -858993460
my_array[10] = -858993460
my_array[11] = -858993460
my_array[12] = -858993460
my_array[13] = -858993460
my_array[14] = -858993460

Я думаю, что вы сразу заметили разницу от предыдущих запусков: все элементы нашего массива имеют теперь какие-то странные отрицательные значения. Вывод из этого следующий: в случае отсутствия блока статической инициализации массива, компилятор присваивает всем элементам массива неопределённое значение по умолчанию. Поэтому если вы задаёте массив как в примере выше, помните всегда о том, что нет абсолютно никакой гарантии того, что компилятор C++ присвоит всем элементам массива значение 0.

Динамическое выделение памяти под массивы

Теперь для тех читателей, которые уже прочитали статью на тему указателей в языке C++, посмотрим на пример объявления динамического массива с помощью указателя. Снова перепишем нашу программу следующим образом:

#include <iostream>

using namespace std;

int main() {
    int* my_array;

    my_array = new int[15];

    int my_array_size = sizeof(my_array) / sizeof(int); // ОШИБКА: в случае динамического массива теперь так делать нельзя!
    cout << "The size of array 'my_array': " << my_array_size << endl;
    cout << "Elements of 'my_array':" << endl;

    for (int i = 0; i < my_array_size; i++) {
        cout << "my_array[" << i << "] = " << my_array[i] << endl;
    }

    delete[] my_array;
}

Итак, теперь наш массив my_array стал указателем на тип данных int. Обратите внимание, как мы выделяем память под наш массив - через оператор new int[15]. Этим самым мы динамически выделяем память под элементы нашего массива my_array. Также обратите внимание на комментарий к строке, где раньше вычислялся размер массива через деление результатов операторов sizeof и присваивался переменной my_array_size. Теперь в этой строке кроется ошибка расчёта размера массива: в случае динамического выделения памяти и наш массив становится также динамическим. Это означает, что компилятор не знает ровным счётом ничего о том, сколько же элементов в нём хранится. Переменная my_array является "указателем на int", а размер памяти под указатель типа int равен 4 байтам. Поэтому оператор sizeof(my_array) вернёт единицу, а результат от деления будет равен также единице: 4 / 4 = 1 (попробуйте запустить эту ошибочную программу и проанализировать её вывод на консоль). Но в нашем динамическом массиве мы выделили память под 15 элементов! Дело в том, что в случае динамического выделения памяти под массив, разработчик C++ самостоятельно должен следить и управлять размером этого динамического массива. Чтобы устранить ошибку в программе, давайте введём сразу отдельную константу для хранения размера нашего динамического массива и перепишем программу следующим образом:

#include <iostream>

using namespace std;

int main() {
    int* my_array;
    const int my_array_size = 15;

    my_array = new int[my_array_size];
    
    cout << "The size of array 'my_array': " << my_array_size << endl;
    cout << "Elements of 'my_array':" << endl;

    for (int i = 0; i < my_array_size; i++) {
        cout << "my_array[" << i << "] = " << my_array[i] << endl;
    }

    delete[] my_array;
}

Теперь мы объявили my_array_size как константу, равную размеру нашего массива, и динамически выделяем память под массив, также обращаясь к этой константе. Давайте снова запустим этот вариант программы. На экране вы должны увидеть примерно следующее:

The size of array 'my_array': 15
Elements of 'my_array':
my_array[0] = -842150451
my_array[1] = -842150451
my_array[2] = -842150451
my_array[3] = -842150451
my_array[4] = -842150451
my_array[5] = -842150451
my_array[6] = -842150451
my_array[7] = -842150451
my_array[8] = -842150451
my_array[9] = -842150451
my_array[10] = -842150451
my_array[11] = -842150451
my_array[12] = -842150451
my_array[13] = -842150451
my_array[14] = -842150451

В этот раз мы вывели значения всех 15-ти элементов массива, однако снова обращаем внимание, что в случае динамического массива компилятор также не гарантирует, что значения элементов будут равны 0 по умолчанию. Вновь выводятся какие-то отрицательные значения. Поэтому при динамическом выделении памяти для массива обязательным образом нужно инициализировать элементы явно до обращения к ним.

Ещё хочу пояснить последнюю строку с оператором delete[] my_array. При динамическом выделении памяти обязательным является её освобождение, в противном случае может происходить такое неприятное явление как утечка памяти в программе на C++. Если оператор new выделяет область памяти под массив, то оператор delete[] позволяет освободить эту область памяти, когда она уже не нужна.

Многомерные массивы в C++

В предыдущих примерах, которые мы рассмотрели с вами выше, мы использовали так называемый одномерный массив (с именем my_array). Однако возможности языка C++ предоставляют программисту также способ объявления многомерных массивов, т.е. массивов имеющих более одного измерения. К наиболее популярным и распространённым видам многомерных массивов относят двухмерные и трёхмерные массивы.

Давайте перепишем нашу программу и посмотрим, как будет выглядеть наш массив my_array, если он станет двухмерным массивом:

#include <iostream>

using namespace std;

int main() {
    int my_array[5][2] = { {10, 20}, {30, 40}, {50, 60}, {70, 80}, {90, 100} };

    int my_array_size = sizeof(my_array) / sizeof(int);

    cout << "The size of array 'my_array': " << my_array_size << endl;
    cout << "Elements of 'my_array':" << endl;

    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 2; j++) {
            cout << "my_array[" << i << "][" << j << "] = " << my_array[i][j] << endl;
        }        
    }
}

Обращаем внимание, что добавилась ещё одна пара квадратных скобок и мы указали два числа 5 и 2 - по одному для каждой размерности нашего массива. Двухмерный массив ещё принято называть матрицей, которую можно представить в виде "таблицы", имеющую строки и столбцы. В этом случае, представим мысленно, что наша "таблица" имеет 5 строк и 2 столбца.

Тогда элементы матрицы запишутся в неё следующим образом:

10 20
30 40
50 60
70 80
90 100

Также теперь у нас целых два цикла вместо одного - для вывода элементов нашего двухмерного массива. Это и понятно: нам нужно "перебрать" каждую размерность нашего массива, чтобы добраться до каждого элемента матрицы. Один цикл с переменной цикла i - "идёт" по строкам, а второй, с переменной цикла j, - по столбцам нашей матрицы. При запуске программы на экране увидим следующее:

The size of array 'my_array': 10
Elements of 'my_array':
my_array[0][0] = 10
my_array[0][1] = 20
my_array[1][0] = 30
my_array[1][1] = 40
my_array[2][0] = 50
my_array[2][1] = 60
my_array[3][0] = 70
my_array[3][1] = 80
my_array[4][0] = 90
my_array[4][1] = 100

Интересной особенностью многомерных массивов является то, что первую размерность массива можно не указывать явно, например: 

int my_array[][2] = { {10, 20}, {30, 40}, {50, 60}, {70, 80}, {90, 100} };

Напоследок рассмотрим вариант трёхмерного массива и выведем все его элементы на экран консоли:

#include <iostream>

using namespace std;

int main() {
    int other_array[][2][3] = { { {1, 1, 1}, {2, 2, 2} }, { {3, 3, 3}, {4, 4, 4} } };

    for (int x = 0; x < 2; x++) {
        for (int y = 0; y < 2; y++) {
            for (int z = 0; z < 3; z++) {
                cout << "other_array[" << x << "][" << y << "][" << z << "] = " << other_array[x][y][z] << endl;
            }
        }
    }
}

Если двухмерный массив - это матрица или таблица, то трёхмерный массив можно представить себе в виде трёхмерного куба с тремя измерениями - "ось X", "ось Y", "ось Z". В примере выше у нас по оси X - два элемента. по оси Y - тоже два элемента, а по оси Z - три элемента. Если запустить программу, то будет выведен следующий результат на консоли:

other_array[0][0][0] = 1
other_array[0][0][1] = 1
other_array[0][0][2] = 1
other_array[0][1][0] = 2
other_array[0][1][1] = 2
other_array[0][1][2] = 2
other_array[1][0][0] = 3
other_array[1][0][1] = 3
other_array[1][0][2] = 3
other_array[1][1][0] = 4
other_array[1][1][1] = 4
other_array[1][1][2] = 4

Динамически память можно выделять не только под одномерные, но и под многомерные массивы. Для выделения памяти под многомерный массив также используется оператор new, а для её высвобождения - оператор delete[]. Когда выделяется память под многомерный массив, то все измерения за исключением первого, должны быть константными выражениями, которые вычисляют и возвращают положительные значения. Наиболее левое измерение многомерного массива может быть любым выражением, которое вернёт положительное значение.

Завершая эту статью, предложу моим читателям поупражняться с использованием массивов и написать следующие примеры программ:

  • Объявить двухмерный массив типа double с именем my_double_array. Количество строк массива 5, столбцов 3. Каждый элемент массива инициализировать по формуле: <значение элемента в строке i и столбце j> = i * 2 / (j + 1). Вывести все элементы массива на экран консоли и посмотреть на результат.
  • Объявить указатель на одномерный массив типа char с именем chars_array. Динамически выделить память под 26 элементов. Пройтись в цикле по всем элементам массива и каждому очередному элементу присвоить значение по следующей формуле: <i-й элемент массива> = 65 + i. В этом же цикле, после инициализации очередного элемента массива вывести его значение на экран с помощью конструкции cout << "[" << i << "] = " << chars_array[i] << endl; Не забыть очистить выделенную память в конце программы.

Делитесь в комментариях к статье результатами своих решений для данных упражнений или просто делитесь мыслями и задавайте свои вопросы. А пока на этом всё про массивы в C++, удачи в написании программ C++ с использованием массивов!

 

 

 

 

Яндекс.Метрика