АЙТИШНЫЙ САЙТ ПЕТРА СЕМИЛЕТОВА

ПЕТР СЕМИЛЕТОВ

ПРОСТО С++: УРОК 10

Массивы - переменные из множества ячеек

Наш самоучитель или курс про С++ становится понемногу сложнее, и сейчас мы подобрались к теме, которую я сам когда-то давно понял не сразу. Не потому, что тупой, а потому, что объяснение было неясным. Попробую снова поставить себя на место чайника и сам себе объяснить, что же такое массивы.

Обычная переменная это у нас одна поименованная ячейка памяти определенного размера. Туда помещается число, лежащее в некотором диапазоне, который зависит от размера ячейки. Если ячейка занимает 1 байт, то в ней может быть число от 0 до 255 или от -128 до 127. Подробнее об этом было на третьем уроке, тут не буду повторяться.

Переменная имеет придуманное программистом имя, иногда внятное и понятное, иногда краткое. Например, координаты указателя мыши мы будем хранить в переменных с именами X и Y, а количество денег запишем в переменную под именем money. Снова напоминаю, что в последних редакциях С++ имена функций и переменных можно писать кириллицей - если вы сохраняете исходник в кодировке UTF-8. Но увы, так сложилось, что мир исходного кода, на всех языках программирования - англоязычный, и обычно принято давать переменным английские имена. Сегодня я временно отступлю от этой традиции.

И вот вы пишете программу и придумываете имена для нужных вам переменных. И вдруг сталкиваетесь с необходимостью хранить не отдельные какие-то значения вроде координат мыши, нажатия кнопки, или введенного пользователем слова, а последовательность однотипных значений. Например потраченные в магазине суммы денег за разные дни. Черт знает, сколько может быть этих сумм, что же, придумывать переменным имена вроде трата_первая, трата_вторая, и так до, скажем, трата_тридцатая?

Это хлопотно и никакой фантазии не хватит. Для таких целей существуют массивы. Массив по-английски это array.

Представьте себе фотопленку, разделенную на кадры. Каждый кадр пронумерован. Такая пленка может считаться массивом, состоящим из элементов - кадров.

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

Массив это тоже переменная, но содержащая в себе пронумерованные элементы. Чтобы обратиться к нужному элементу, надо написать имя переменной-массива, и в квадратных скобках указать номер номер элемента массива. Такой номер называют индексом.

Нумерация в массиве начинается с нуля, что может поначалу запутать вас, ибо привычно думать, что “первый” элемент стоит в самом начале, тогда как в случае массивов, начальный элемент там имеет номер ноль, а не единица. Эта же непривычность относится к последнему элементу массива - его индекс, номер, всегда равен размеру массива минус 1. Массивы не бесконечны, каждому из них вы задаете размер при объявлении переменной-массива.

Давайте напишем программу-пример и по ходу на ней будем разбираться с массивами.

10-01.cpp:

#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 вручную слишком долго и хлопотно. Можно ли как-то автоматизировать процесс?

Да, и на помощь нам приходят циклы!

10-02.cpp:

#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‘ом на консоль элементы массива, вместо четко прописанного номера элемента массива, подставляем переменную-счетчик!

Заполнять элементы массива можно и при помощи так называемой инициализации массива, причем в этом случае мы не обязаны указывать размер массива.

10-03.cpp:

#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. Пример

10-04.cpp:

#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;

Итак, это массив с двумя измерениями - начальное у нас это ряд, а уже из ряда развивается новое измерение, места на нем.

Одномерные массивы называют векторами, двумерные - матрицами. Есть массивы и с большим количеством измерений, с большим уровнем вложенностей, но нам это сейчас не нужно, и двумерные массивы пока тоже не нужны.

Перейдем к другой теме, очень важной и близкой к массивам, но уже на следующем уроке.

Поддержать курс:

Навигация:

Оглавление

Предыдущий урок

Следующий урок