В этой статье мы поговорим про массивы в языке 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++ с использованием массивов!