Range-based for в C++: удобный перебор контейнеров без ошибок индексов

Range-based for в C++: удобный перебор контейнеров без ошибок индексов

Range-based for в C++: удобный перебор контейнеров без ошибок индексов

Поисковый запрос «range based for C++ примеры» часто возникает у тех, кто уже знает обычный цикл for, но хочет писать код короче и безопаснее. Range-based for, или цикл по диапазону, появился в C++11 и стал стандартным способом обхода массивов, строк и контейнеров STL.

Что такое range-based for

Range-based for позволяет пройти по всем элементам диапазона без явного индекса. Базовый синтаксис выглядит так:

for (тип переменная : контейнер) {
    // работа с переменной
}

Например, выведем элементы вектора:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {10, 20, 30, 40};

    for (int x : numbers) {
        std::cout << x << ' ';
    }
}

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

Используйте auto, чтобы не дублировать тип

В реальном коде тип элемента может быть длинным: std::string, std::pair<…>, пользовательский класс. Поэтому часто используют auto:

#include <iostream>
#include <string>
#include <vector>

int main() {
    std::vector<std::string> names = {"Анна", "Игорь", "Мария"};

    for (auto name : names) {
        std::cout << name << 'n';
    }
}

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

auto& и const auto&: когда нужны ссылки

Если вы хотите только читать элементы и не копировать их, используйте const auto&:

for (const auto& name : names) {
    std::cout << name << 'n';
}

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

Если же элементы нужно изменить, используйте auto&:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> scores = {7, 8, 9};

    for (auto& score : scores) {
        score += 1;
    }

    for (const auto& score : scores) {
        std::cout << score << ' ';
    }
}

Результат будет 8 9 10. Если бы мы написали auto score, изменялась бы только копия, а исходный вектор остался бы прежним.

Range-based for со строками и массивами

Цикл по диапазону работает не только с std::vector. Например, можно перебрать символы строки:

#include <iostream>
#include <string>

int main() {
    std::string word = "cpp";

    for (char ch : word) {
        std::cout << ch << 'n';
    }
}

Также он подходит для обычных массивов, если размер массива известен компилятору:

int values[] = {1, 2, 3, 4};

for (int value : values) {
    std::cout << value << ' ';
}

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

Перебор std::map и structured bindings

Для ассоциативных контейнеров range-based for особенно удобен. В C++17 можно использовать структурные привязки:

#include <iostream>
#include <map>
#include <string>

int main() {
    std::map<std::string, int> ages = {
        {"Анна", 25},
        {"Игорь", 31}
    };

    for (const auto& [name, age] : ages) {
        std::cout << name << ": " << age << 'n';
    }
}

Без структурных привязок пришлось бы обращаться к pair.first и pair.second, что менее читаемо.

Когда обычный for всё ещё лучше

Range-based for хорош, когда нужно пройти по всем элементам. Но обычный цикл с индексом полезнее, если:

  • нужен номер текущего элемента;
  • нужно идти не по всем элементам, а с шагом;
  • требуется обход в обратном порядке;
  • нужно сравнивать текущий элемент с соседними.
  • std::vector<int> numbers = {5, 10, 15};
    
    for (std::size_t i = 0; i < numbers.size(); ++i) {
        std::cout << "index=" << i
                  << ", value=" << numbers[i] << 'n';
    }
    

    Главная рекомендация: если индекс не нужен — выбирайте range-based for.

    Типичные ошибки новичков

    Ошибка 1: пытаться изменить элементы без ссылки.

    for (auto x : numbers) {
        x *= 2; // меняется копия, а не элемент вектора
    }
    

    Правильно так:

    for (auto& x : numbers) {
        x *= 2;
    }
    

    Ошибка 2: копировать тяжёлые объекты. Для строк, структур и классов чаще используйте const auto&, если не планируете менять элементы.

    Ошибка 3: изменять размер контейнера во время обхода. Например, добавлять элементы в std::vector внутри range-based for опасно: ссылки и итераторы могут стать недействительными.

    // Плохая идея
    for (auto x : numbers) {
        numbers.push_back(x); // может привести к неожиданному поведению
    }
    

    Если нужно добавлять или удалять элементы, лучше заранее продумать алгоритм: создать новый контейнер, использовать индексы или подходящие STL-алгоритмы.

    Практические рекомендации

  • Для простых чисел можно писать auto x.
  • Для чтения объектов используйте const auto& x.
  • Для изменения элементов используйте auto& x.
  • Не меняйте размер контейнера внутри range-based for без необходимости.
  • Если нужен индекс, используйте обычный for.
  • Если вы хотите уверенно разобраться не только с циклами, но и с контейнерами STL, функциями, классами и современными возможностями языка, посмотрите пошаговый курс «Программирование на C++ с Нуля до Гуру» — он хорошо подходит для системного изучения C++ с практикой.

    Итог

    Range-based for в C++ делает перебор контейнеров короче, безопаснее и понятнее. В большинстве случаев для чтения элементов выбирайте const auto&, для изменения — auto&, а обычный цикл с индексом оставляйте для ситуаций, где индекс действительно нужен.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий