Массивы - переменные из множества ячеек
Наш самоучитель или курс про С++ становится понемногу сложнее, и сейчас мы подобрались к теме, которую я сам когда-то давно понял не сразу. Не потому, что тупой, а потому, что объяснение было неясным. Попробую снова поставить себя на место чайника и сам себе объяснить, что же такое массивы.
Обычная переменная это у нас одна поименованная ячейка памяти определенного размера. Туда помещается число, лежащее в некотором диапазоне, который зависит от размера ячейки. Если ячейка занимает 1 байт, то в ней может быть число от 0 до 255 или от -128 до 127. Подробнее об этом было на третьем уроке, тут не буду повторяться.
Переменная имеет придуманное программистом имя, иногда внятное и понятное, иногда краткое. Например, координаты указателя мыши мы будем хранить в переменных с именами X и Y, а количество денег запишем в переменную под именем money. Снова напоминаю, что в последних редакциях С++ имена функций и переменных можно писать кириллицей - если вы сохраняете исходник в кодировке UTF-8. Но увы, так сложилось, что мир исходного кода, на всех языках программирования - англоязычный, и обычно принято давать переменным английские имена. Сегодня я временно отступлю от этой традиции.
И вот вы пишете программу и придумываете имена для нужных вам переменных. И вдруг сталкиваетесь с необходимостью хранить не отдельные какие-то значения вроде координат мыши, нажатия кнопки, или введенного пользователем слова, а последовательность однотипных значений. Например потраченные в магазине суммы денег за разные дни. Черт знает, сколько может быть этих сумм, что же, придумывать переменным имена вроде трата_первая, трата_вторая, и так до, скажем, трата_тридцатая?
Это хлопотно и никакой фантазии не хватит. Для таких целей существуют массивы. Массив по-английски это array.
Представьте себе фотопленку, разделенную на кадры. Каждый кадр пронумерован. Такая пленка может считаться массивом, состоящим из элементов - кадров.
Представьте себе теперь бумажную ленту, разлинованную на квадратики, в каждом из которых вы можете записать одно число и или заменить его другим. Вот это и есть массив в языке программирования.
Массив это тоже переменная, но содержащая в себе пронумерованные элементы. Чтобы обратиться к нужному элементу, надо написать имя переменной-массива, и в квадратных скобках указать номер номер элемента массива. Такой номер называют индексом.
Нумерация в массиве начинается с нуля, что может поначалу запутать вас, ибо привычно думать, что “первый” элемент стоит в самом начале, тогда как в случае массивов, начальный элемент там имеет номер ноль, а не единица. Эта же непривычность относится к последнему элементу массива - его индекс, номер, всегда равен размеру массива минус 1. Массивы не бесконечны, каждому из них вы задаете размер при объявлении переменной-массива.
Давайте напишем программу-пример и по ходу на ней будем разбираться с массивами.
#include <iostream>
using namespace std;
int main (int argc, char *argv[])
{
//объявляем массив "траты"
//из трех элементов, т.е. размер его равен 3:
float траты [3];
//присваиваем значения элементам массива:
траты[0] = 1.0; //элемент номер 0
траты[1] = 12.5; //элемент номер 1
траты[2] = 44.0; //элемент номер 2, последний, ибо всего 3 элемента, а начальный у нас номер 0
//выводим каждый элемент на консоль:
cout << траты[0] << endl;
cout << траты[1] << endl;
cout << траты[2] << endl;
return 0;
}
Как видим, объявление массива сходно с объявлением обычной переменной, только после имени переменной вы указываете в квадратных скобках, сколько в массиве будет ячеек, элементов. В нашем примере - 3.
float траты [3];
Повторюсь. Если мы объявили массив с размером 3, то последний его элемент будет иметь номер 2, а не три, потому что нумерация начинается с нуля.
Когда нам нужно использовать элемент массива, обратиться к нему, мы пользуемся им как обычной переменной, но должны после имени массива указывать в квадратных скобках номер элемента, его индекс.
Присвоить значение нулевому элементу:
траты[0] = 1.0; //элемент номер 0
Обратиться к его значению:
cout << траты[0] << endl;
Если указать индекс бОльший, чем размер массива минус один, произойдет ошибка. Потому, что последний элемент массива имеет номер, равный размеру массива минус 1, из-за того, что нумерация начинается с нуля и как бы сдвинута.
Хорошо, но массив из трех элементов это слишком мелко.
Пока что наш массив невелик, всего из трех элементов. Но допустим, нам необходимо больше, а мы хотим вывести каждую трату на консоль. Писать для каждого элемента вывод через cout вручную слишком долго и хлопотно. Можно ли как-то автоматизировать процесс?
Да, и на помощь нам приходят циклы!
#include <iostream>
using namespace std;
int main (int argc, char *argv[])
{
//объявляем массив траты
//из ШЕСТИ элементов:
float траты [6];
//присваиваем значения элементам массива:
траты[0] = 1.0;
траты[1] = 12.5;
траты[2] = 44.0;
траты[3] = 922.23;
траты[4] = 3.14;
траты[5] = 324.05;
траты[6] = 11342.04;
//выводим каждый элемент на консоль
//при помощи переменной счетчик,
//увеличивая ее значение в цикле for
//на единицу, начиная с 0, и пока счетчик
//меньше 7:
for (int счетчик = 0; счетчик < 7; счетчик++)
cout << траты[счетчик] << endl;
return 0;
}
Мы объявили массив “траты”, заполнили его элементы разными дробными числами - это наши траты денег.
Для работы с массивами особенно полезны циклы.
И далее создаем цикл for, где объявляем переменную “счетчик” с начальным значением 0. Условие цикла будет такое - он выполняется, пока счетчик меньше 7, и с каждой прогонкой, итерацией цикла, счетчик увеличивается на единицу.
В теле цикла мы, выводя cout‘ом на консоль элементы массива, вместо четко прописанного номера элемента массива, подставляем переменную-счетчик!
Заполнять элементы массива можно и при помощи так называемой инициализации массива, причем в этом случае мы не обязаны указывать размер массива.
#include <iostream>
using namespace std;
int main (int argc, char *argv[])
{
//объявление массива и инициализация - заполнение - его элементов значениями
float траты [] = {1.0, 12.5, 44.0, 922.23, 3.14, 324.05, 11342.04};
for (int счетчик = 0; счетчик < 7; счетчик++)
cout << траты[счетчик] << endl;
return 0;
}
Итак, при объявлении массива блокнот мы оставляем квадратные скобки пустыми, хотя можем указать и размер. Затем мы пишем равно и в фигурных скобках перечисляем, через запятую, значения, которые хотим поместить в массив.
При этом таком заполнении, однако, вы не будете знать сколько в массиве элементов (размер массива - array size, длину массива - array length ), если не подсчитаете их умозрительно. А размер массива надо знать, чтобы при прогонке его в цикле счетчик не превысил размер массива.
Лучше всё же не лениться и указывать размер массива сразу:
float траты [7] = {1.0, 12.5, 44.0, 922.23, 3.14, 324.05, 11342.04};
В противном случае вам придется при работе с таким массивом каждый раз вспоминать его размер, либо прибегать к разным способам и функциям, которые вычисляют размер массива, а их в С++ разных версий языка немеряно, и все касаются аспектов языка, которые мы еще не разбирали.
Например начиная с С++ 17 можно вызывать size из заголовочного файла iterator и пространства имен std:
int размер_массива = std::size(траты);
Начиная с С++ 20 можно вызывать ssize из заголовочного файла iterator и пространства имен std:
int размер_массива = std::size(траты);
И еще один способ, работает в новых и старых версиях С++, однако не вычисляет размер массива, если он передан функции в качестве параметра:
int размер_массива = (sizeof(траты)/sizeof(*траты);
Не в моих правилах грузить вас ненужным СЕЙЧАС, поэтому вернемся к циклам. Итак, циклы удобно использовать, когда мы работаем с массивами. В цикле мы перебираем по одному элементы массива и может что-то с ними делать.
Конечно же, годится любой цикл, не только цикл for. Вот как выглядит перебор элементов массива при помощи цикла while. Пример
#include <iostream>
using namespace std;
int main (int argc, char *argv[])
{
float траты [] = {1.0, 12.5, 44.0, 922.23, 3.14, 324.05, 11342.04};
int счетчик = 0;
while (счетчик < 6)
cout << траты[счетчик++] << endl;
return 0;
}
return 0;
}
Мы объявляем счетчик вне массива, присваиваем ему начальное значение 0. Потом выполняем цикл, задав условие - выполнять, пока - while - счетчик меньше трех.
В строке:
cout << траты[счетчик++] << endl;
Мы выводим элемент массива траты, элемент с номером, который хранится в переменной счетчик, и затем значение счетчика увеличивается на единицу с помощью оператора ++
. Потому, что это постфиксная форма оператора ++
, то есть он стоит после переменной.
Если бы мы написали:
cout << траты[++счетчик] << endl;
То цикл вывалился бы на ошибке. Напомню про префиксную и постфиксную формы операторов увеличения и уменьшения, или, как говорят, инкремента и декремента?
Когда вот это ++
стоит ПЕРЕД переменной, ее значение сначала увеличивается на один, а потом это увеличенное значение используется в выражении. Когда ++
стоит ПОСЛЕ, то в выражение сначала попадает текущее значение переменной, а потом уже увеличивается, после его использования.
И когда мы пишем в теле нашего цикла
cout << траты[счетчик++] << endl;
То с каждой новой итерацией, прогонкой цикла значения счетчика будут таковы:
0
1
2
3
4
5
6
А если написать
cout << траты[++счетчик] << endl;
То счетчик в начальной же итерации цикла попадет в качестве индекса массива уже увеличившись с нуля в единицу, а общая развертка счетчика получится такая:
1
2
3
4
5
6
7
И вот когда счетчик станет равным 7, то программу заглючит не по-детски, потому что элементы у нас нумеруются с 0, и последний элемент нашего массива имеет номер 6. А номера, индекса 7 просто нет.
То есть, нужно всегда следить, чтобы не вылезти подобным счетчиком за пределы массива.
Обычно вместо переменной под именем счетчик используют более короткое имя, чаще i - сокращение от index:
int i = 0;
while (i < 7)
cout << блокнот[i++] << endl;
Вот, так гораздо короче.
Существуют также многомерные массивы. Я покамест говорил только об одномерных массивах, иначе их называют векторами. Вектор это однонаправленный массив, то есть ячейки развиваются в одном направлении, от нулевого элемента и к конечному.
Многомерный массив - когда в каждый элемент массива вложен другой массив. Есть массивы двумерные, трехмерные, и так далее.
Пример двумерного массива из жизни - зал в кинотеатре, вернее, зрительные ряды. У нас есть пронумерованные ряды, и каждый ряд состоит из пронумерованных кресел. На С++ такой массив можно описать так:
int места[10][20];
Это значит, что мы объявили двумерный массив под названием места, состоящий из 10 рядов, каждый из которых имеет 20 кресел.
Если бы мы хотели написать игру-симулятор кинотеатра, то могли бы усаживать зрителей на места этого массива, приняв, что 0 у нас будет означать пустое место, а 1 - занятое.
И чтобы отметить, например, 1 место в 1 ряду, как пустое, мы бы написали:
места[0][0] = 0;
А как занятое так:
места[0][0] = 1;
Снова напомню про нумерацию в массивах, которая начинается с нуля. Не важно, что в жизни, номера кресел и рядов считаются с единицы. Один, два, три… Мы моделируем это при помощи выразительных средств программирования, где нумерация идет с нуля. 0, 1, 2 вместо 1, 2, 3.
Соответственно пометить 4 место в 1 ряду как занятое мы можем так:
места[0][3] = 1;
А 2 место в 8 ряду так:
места[7][1] = 1;
Итак, это массив с двумя измерениями - начальное у нас это ряд, а уже из ряда развивается новое измерение, места на нем.
Одномерные массивы называют векторами, двумерные - матрицами. Есть массивы и с большим количеством измерений, с большим уровнем вложенностей, но нам это сейчас не нужно, и двумерные массивы пока тоже не нужны.
Перейдем к другой теме, очень важной и близкой к массивам, но уже на следующем уроке.
PayPal: peter.semiletov@gmail.com
биткоин: 1PCo2zznEGMFJey4qFKGQ8CoFK2nzNnJJf