Итераторы в C++: понятное руководство для начинающих с примерами и ошибками новичков
Поисковый запрос, под который оптимизирована статья: «итераторы в C++» и «итераторы C++ для начинающих».
Что такое итераторы в C++ и зачем они нужны
Итератор — это «указатель-подобный» объект, который позволяет перебирать элементы контейнера и передавать поддиапазоны в алгоритмы стандартной библиотеки. Благодаря итераторам алгоритмы не зависят от конкретного контейнера: один и тот же std::sort сортирует std::vector, а std::find ищет в std::list или std::string — всё через единый интерфейс begin/end.
Базовый синтаксис: begin, end, инкремент и разыменование
#include <vector>
#include <iostream>
int main() {
std::vector<int> v {1, 2, 3};
// Итерация с помощью итераторов
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << " "; // *it — доступ к значению
}
}
Ключевые операции: *it (разыменование), ++it (переход к следующему), сравнение с end() (конец диапазона). Никогда не разыменовывайте итератор, равный end() — это UB (неопределённое поведение).
Константные итераторы: защищаем данные от изменений
Если изменять элементы не нужно, используйте const_iterator или удобные cbegin()/cend().
#include <vector>
#include <iostream>
int main() {
const std::vector<int> v {10, 20, 30};
// v.begin() у const-объекта уже const_iterator,
// но можно явно: v.cbegin(), v.cend()
for (auto it = v.cbegin(); it != v.cend(); ++it) {
// *it = 42; // Ошибка компиляции — нельзя менять
std::cout << *it << " ";
}
}
Итерация по std::map: пара key/value
#include <map>
#include <string>
#include <iostream>
int main() {
std::map<std::string, int> scores {{"Ann", 5}, {"Bob", 8}};
for (auto it = scores.begin(); it != scores.end(); ++it) {
std::cout << it->first << ": " << it->second << "\n";
}
}
Элемент map — это std::pair<const Key, T>, поэтому доступ через it->first (ключ) и it->second (значение).
Категории итераторов (коротко и по делу)
- Input — только чтение по одному проходу (потоки).
- Forward — многократный проход вперёд (например,
forward_list). - Bidirectional — как Forward + шаг назад (например,
list,map). - RandomAccess — произвольный доступ и арифметика (например,
vector,deque). - Contiguous (C++20) — элементы в непрерывной памяти (например,
vector,string).
Чем «сильнее» категория, тем больше операций поддерживается (например, it + n доступно только для RandomAccess).
Алгоритмы STL с итераторами: must‑have примеры
#include <vector>
#include <algorithm> // find, sort, remove_if
#include <numeric> // accumulate
#include <iostream>
int main() {
std::vector<int> v {3, 1, 4, 1, 5, 9};
// Поиск первого вхождения 4
auto it = std::find(v.begin(), v.end(), 4);
if (it != v.end()) std::cout << "Нашли: " << *it << "\n";
// Сортировка (RandomAccess итераторы)
std::sort(v.begin(), v.end());
// Сумма элементов
int sum = std::accumulate(v.begin(), v.end(), 0);
std::cout << "Сумма: " << sum << "\n";
// Удаление нечётных: erase + remove_if (идиома erase-remove)
v.erase(std::remove_if(v.begin(), v.end(), [](int x){ return x % 2 != 0; }), v.end());
}
Идиома erase-remove — базовый приём: remove_if сдвигает «ненужные» в конец и возвращает новый логический конец, а erase физически удаляет хвостовой диапазон.
Инвалидация итераторов: где тонко — там рвётся
- vector: вставки/удаления в середине и рост емкости могут инвалидировать все итераторы, ссылки и указатели на элементы. Безопаснее заново получать итераторы после модификаций.
- list, forward_list: итераторы сохраняются при вставках/удалениях других элементов, инвалидируется только удалённый элемент.
- map, set: вставки сохраняют валидность других итераторов; удаление инвалидирует итераторы на удалённые элементы.
#include <vector>
#include <iostream>
int main() {
std::vector<int> v {1,2,3,4,5};
auto it = v.begin(); // Указывает на 1
// Вставка может перераспределить память и сделать it невалидным
v.insert(v.begin() + 2, 42);
// Нельзя больше использовать старый it — возьмите заново:
it = v.begin();
std::cout << *it; // ОК
}
Диапазоны C++20 (ranges): чище, короче, безопаснее
Модерновый подход — не таскать пары begin(), end(), а работать с «диапазонами» и конвейерами представлений (views).
#include <vector>
#include <ranges>
#include <iostream>
int main() {
std::vector<int> v {3,1,4,1,5,9,2,6};
// Отфильтровать чётные и умножить на 10, затем вывести
auto pipe = v | std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return x * 10; });
for (int x : pipe) std::cout << x << " ";
}
Компиляция с поддержкой C++20: для g++ -std=c++20, для MSVC выберите стандарт в настройках проекта. Диапазоны уменьшают ошибки с границами и улучшают читаемость.
Частые ошибки новичков
- Разыменование
end()или итератора на удалённый элемент. - Хранение итераторов через долгие промежутки и повторное использование после модификаций контейнера.
- Неправильная работа с
eraseв цикле: пропуск элементов. Решение — использовать возвращаемое значениеeraseкак новый итератор. - Излишнее копирование: там, где достаточно
const_iteratorили диапазонов, используют мутабельные итераторы.
#include <vector>
#include <iostream>
int main() {
std::vector<int> v {1,2,3,4,5};
for (auto it = v.begin(); it != v.end(); ) {
if (*it % 2 == 0) {
it = v.erase(it); // Возвращает итератор на следующий элемент
} else {
++it;
}
}
for (int x : v) std::cout << x << " "; // 1 3 5
}
Мини‑практика: удалить дубликаты из vector
Классический приём: сортировка + unique + erase.
#include <vector>
#include <algorithm>
#include <iostream>
int main() {
std::vector<int> v {5,1,2,1,3,2,4,3};
std::sort(v.begin(), v.end());
auto last = std::unique(v.begin(), v.end());
v.erase(last, v.end());
for (int x : v) std::cout << x << " "; // 1 2 3 4 5
}
Здесь unique возвращает итератор на первый «повтор», а erase удаляет хвост диапазона дубликатов.
Лучшие практики
- Предпочитайте range-based for и C++20 ranges там, где это уместно — меньше кода и ошибок.
- Используйте const_iterator или cbegin/cend, если не изменяете элементы — так вы документируете намерение и избегаете случайных модификаций.
- После операций, которые могут инвалидировать итераторы (вставки, удаление, изменение ёмкости), получайте их заново.
- Для удаления по условию применяйте идиому erase-remove или цикл с использованием возвращаемого
erase.
Заключение
Итераторы — фундаментальный инструмент C++: с ними вы эффективно используете контейнеры и алгоритмы STL, пишете менее связанный и более безопасный код. Освойте базовые паттерны (erase-remove, const_iterator), не забывайте о правилах инвалидации и постепенно переходите к C++20 ranges — это сделает ваш код короче и понятнее.
Хотите системно прокачать язык и научиться писать производительный и современный C++‑код? Рекомендую пройти практический курс: Прокачать C++ с нуля до уровня уверенного разработчика — перейти к курсу.
-
Создано 13.04.2026 17:00:59
-
Михаил Русаков

Комментарии (0):
Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.