9.3 – Массивы и циклы

Добавлено 5 июня 2021 в 01:05
Глава 9 – Массивы, строки, указатели и ссылки  (содержание)

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

int numStudents{ 5 };
int score0{ 84 };
int score1{ 92 };
int score2{ 76 };
int score3{ 81 };
int score4{ 56 };
 
int totalScore{ score0 + score1 + score2 + score3 + score4 };
auto averageScore{ static_cast<double>(totalScore) / numStudents };

Это много переменных и много набора текста – и это всего 5 студентов! Представьте, сколько работы нам нужно было бы сделать для 30 или 150 студентов.

Кроме того, если добавится новый студент, необходимо будет объявить и инициализировать новую переменную и добавить ее в расчет totalScore. Каждый раз, когда вам нужно изменить старый код, вы рискуете внести ошибки.

Использование массивов предлагает немного лучшее решение:

int scores[]{ 84, 92, 76, 81, 56 };
int numStudents{ std::size(scores) }; // требуется C++17 и заголовок <iterator>
int totalScore{ scores[0] + scores[1] + scores[2] + scores[3] + scores[4] };
auto averageScore{ static_cast<double>(totalScore) / numStudents };

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

Если бы только был способ перебрать наш массив и напрямую вычислить totalScore.

Циклы и массивы

В предыдущем уроке вы узнали, что индекс массива не обязательно должен быть постоянным значением – он может быть переменной. Это означает, что в качестве индекса массива мы можем использовать переменную цикла для перебора всех элементов нашего массива и выполнения над ними каких-либо вычислений. Это настолько обычное дело, что везде, где вы найдете массивы, вы почти наверняка найдете и циклы! Когда цикл используется для доступа к каждому элементу массива по очереди, это часто называется итерацией по массиву.

Вот наш пример выше, модифицированный для использования цикла for:

int scores[]{ 84, 92, 76, 81, 56 };
int numStudents{ std::size(scores) };
// если компилятор не поддерживает C++17, используйте следующую строку
// const int numStudents{ sizeof(scores) / sizeof(scores[0]) };
int totalScore{ 0 };
 
// используем цикл для вычисления totalScore
for (int student{ 0 }; student < numStudents; ++student)
    totalScore += scores[student];
 
auto averageScore{ static_cast<double>(totalScore) / numStudents };

Это решение идеально как с точки зрения читабельности, так и с точки зрения поддержки. Поскольку все обращения к нашим элементам массива выполняет цикл, формулы автоматически корректируются с учетом количества элементов в массиве. Это означает, что для учета новых студентов вычисления не нужно изменять вручную, и нам не нужно вручную добавлять имена новых элементов массива!

Вот пример использования цикла для поиска в массиве, чтобы определить лучший результат в классе:

#include <iostream>
#include <iterator> // for std::size
 
int main()
{
    constexpr int scores[]{ 84, 92, 76, 81, 56 };
    constexpr int numStudents{ std::size(scores) };
 
    int maxScore{ 0 }; // ищем самый высокий результат
    for (int student{ 0 }; student < numStudents; ++student)
    {
        if (scores[student] > maxScore)
        {
            maxScore = scores[student];
        }
    }
 
    std::cout << "The best score was " << maxScore << '\n';
 
    return 0;
}

В этом примере мы используем переменную maxScore, не являющуюся переменной цикла, для поиска максимального результата. maxScore инициализируется значением 0, чтобы показать, что мы еще не видели никаких оценок. Затем мы перебираем каждый элемент массива и, если находим результат выше, чем тот, который мы видели ранее, мы устанавливаем maxScore в это значение. Таким образом, maxScore всегда представляет собой максимальный результат из всех элементов, среди которых мы искали на данный момент. К тому времени, когда мы достигаем конца массива, maxScore будет равна наивысшему баллу во всем массиве.

Смешивание циклов и массивов

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

  1. расчет значения (например, среднее значение, общее значение);
  2. поиск значения (например, наибольшее значение, наименьшее значение);
  3. реорганизация массива (например, по возрастанию, по убыванию)

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

При поиске значения переменная обычно используется для хранения значения лучшего кандидата, просмотренного на данный момент (или индекса лучшего кандидата в массиве). В приведенном выше примере, где мы используем цикл для поиска лучшего результата, maxScore используется для хранения максимального результата, обнаруженного на данный момент.

Сортировка массива немного сложнее, поскольку обычно включает вложенные циклы. Сортировку массива мы рассмотрим в следующем уроке.

Массивы и ошибки на единицу

Один из самых сложных моментов в использовании циклов с массивами – убедиться, что цикл повторяется нужное количество раз. Легко допустить ошибку на единицу, и попытка получить доступ к элементу, который больше, чем длина массива, может иметь ужасные последствия. Рассмотрим следующую программу:

#include <iostream>
#include <iterator>
 
int main()
{
  constexpr int scores[]{ 84, 92, 76, 81, 56 };
  constexpr int numStudents{ std::size(scores) };
 
  int maxScore{ 0 }; // ищем максимальный результат
  for (int student{ 0 }; student <= numStudents; ++student)
  {
      if (scores[student] > maxScore)
      {
          maxScore = scores[student];
      }
  }
 
  std::cout << "The best score was " << maxScore << '\n';
  
  return 0;
}

Проблема с этой программой в том, что условие в цикле for неверно! Объявленный массив имеет 5 элементов, пронумерованных от 0 до 4. Однако этот массив перебирается в цикле от 0 до 5. Следовательно, на последней итерации массив выполнит следующее:

if (scores[5] > maxScore)
{
    maxScore = scores[5];
}

Но элемент scores[5] не определен! Это может вызвать всевозможные проблемы, наиболее вероятно, что scores[5] приведет к мусорному значению. В этом случае вероятный результат – maxScore окажется неверным.

Однако представьте, что произойдет, если мы случайно присвоим значение элементу scores[5]! Мы можем перезаписать другую переменную (или ее часть) или, возможно, что-то повредить – эти типы ошибок очень сложно отследить!

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

Небольшой тест

Вопрос 1

С помощью цикла выведите на экран следующий массив:

constexpr int array[]{ 4, 6, 7, 3, 8, 2, 1, 9, 5 };

Подсказка: чтобы определить длину массива, вы можете использовать std::size (начиная с C++17) или трюк с sizeof() (до C++17).

#include <iostream>
#include <iterator> // для std::size
 
int main()
{
    constexpr int array[]{ 4, 6, 7, 3, 8, 2, 1, 9, 5 };
 
    for (int index{ 0 }; index < std::size(array); ++index)
    {
        std::cout << array[index] << ' ';
    }
 
    std::cout << '\n';
 
    return 0;
}

Вопрос 2

Используя массив из вопроса 1.

Попросите пользователя ввести число от 1 до 9. Если пользователь не вводит число от 1 до 9, повторно запрашивайте целочисленное значение, пока он не введет его. После того, как он ввел число от 1 до 9, распечатайте массив. Затем найдите в массиве значение, введенное пользователем, и напечатайте индекс этого элемента.

Проверить std::cin на недопустимый ввод вы можете, используя следующий код:

// если пользователь ввел что-то недопустимое
if (std::cin.fail())
{
    сбрасываем все флаги ошибок
    std::cin.clear(); 
}

// игнорируем любые символы во входном буфере
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

#include <iostream>
#include <iterator> // для std::size
#include <limits>
 
int main()
{
    // Сначала считываем допустимый ввод от пользователя
    int number{};
    do
    {
        std::cout << "Enter a number between 1 and 9: ";
        std::cin >> number;
 
        // если пользователь ввел недопустимый символ
        if (std::cin.fail())
            std::cin.clear(); // сбрасываем все флаги ошибок
        
        // игнорируем любые лишние символы во входном буфере
        // (независимо от того, была у нас ошибка или нет)
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
 
    } while (number < 1 || number > 9);
 
    // далее распечатываем массив
    constexpr int array[]{ 4, 6, 7, 3, 8, 2, 1, 9, 5 };
 
    for (int index{ 0 }; index < std::size(array); ++index)
    {
        std::cout << array[index] << ' ';
    }
 
    std::cout << '\n';
 
    // затем ищем в массиве совпадающее число и распечатываем индекс
    for (int index{ 0 }; index < std::size(array); ++index)
    {
        if (array[index] == number)
        {
            std::cout <<  "The number " << number << " has index " <<  index << '\n';
            // поскольку каждое число в массиве уникально,
            // нет необходимости искать в остальной части массива
            break; 
        }
    }
 
    return 0;
}

Вопрос 3

Измените следующую программу так, чтобы вместо того, чтобы maxScore содержала непосредственно наибольший результат, переменная с именем maxIndex содержала индекс элемента с наибольшим результатом.

#include <iostream>
#include <iterator> // для std::size
 
int main()
{
    constexpr int scores[]{ 84, 92, 76, 81, 56 };
 
    int maxScore{ 0 }; // Для начала предположим, что наш наибольший результат равен 0
 
    // теперь ищем максимальный результат во всем массиве
    for (int student{ 0 }; student < std::size(scores); ++student)
    {
        if (scores[student] > maxScore)
        {
            maxScore = scores[student];
        }
    }
 
    std::cout << "The best score was " << maxScore << '\n';
 
    return 0;
}

#include <iostream>
#include <iterator> // для std::size
 
int main()
{
    constexpr int scores[]{ 84, 92, 76, 81, 56 };
 
    int maxIndex{ 0 }; // Предположим, что элемент с индексом 0 является самым большим
 
    // теперь ищем максимальный результат в остальной части массива
    for (int student{ 1 }; student < std::size(scores); ++student)
    {
        if (scores[student] > scores[maxIndex])
        {
            maxIndex = student;
        }
    }
 
    std::cout << "The best score was " << scores[maxIndex] << '\n';
 
    return 0;
}

Теги

C++ / CppLearnCppДля начинающихМассивОбучениеПрограммированиеЦикл

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.