Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
cout << ivec[0]; // ошибка: ivec не имеет элементов!
vector<int> ivec2(10); // вектор из 10 элементов
cout << ivec2[10]; // ошибка: ivec2 имеет элементы 0...9
Попытка обращения к несуществующему элементу является серьезной ошибкой, которую вряд ли обнаружит компилятор. В результате будет получено случайное значение.
Попытка индексирования несуществующих элементов, к сожалению, является весьма распространенной и грубой ошибкой программирования. Так называемая ошибка переполнения буфера (buffer overflow) — результат индексирования несуществующих элементов. Такие ошибки являются наиболее распространенной причиной проблем защиты приложений.
Наилучший способ гарантировать невыход индекса из диапазона — это избежать индексации вообще. Для этого везде, где только возможно, следует использовать серийный оператор for.
Упражнения раздела 3.3.3Упражнение 3.16. Напишите программу, выводящую размер и содержимое вектора из упражнения 3.13. Проверьте правильность своих ответов на это упражнение. При неправильных ответах повторно изучите раздел 3.3.1.
Упражнение 3.17. Прочитайте последовательность слов из потока cin и сохраните их в векторе. Прочитав все слова, обработайте вектор и переведите символы каждого слова в верхний регистр. Отобразите преобразованные элементы по восемь слов на строку.
Упражнение 3.18. Корректна ли следующая программа? Если нет, то как ее исправить?
vector<int> ivec;
ivec[0] = 42;
Упражнение 3.19. Укажите три способа определения вектора и заполнения его десятью элементами со значением 42. Укажите, есть ли предпочтительный способ для этого и почему.
Упражнение 3.20. Прочитайте набор целых чисел в вектор. Отобразите сумму каждой пары соседних элементов. Измените программу так, чтобы она отображала сумму первого и последнего элементов, затем сумму второго и предпоследнего и т.д.
3.4. Знакомство с итераторами
Хотя для доступа к символам строки или элементам вектора можно использовать индексирование, для этого существует и более общий механизм — итераторы (iterator). Как будет продемонстрировано в части II, кроме векторов библиотека предоставляет несколько других видов контейнеров. У всех библиотечных контейнеров есть итераторы, но только некоторые из них поддерживают оператор индексирования. С технической точки зрения тип string не является контейнерным, но он поддерживает большинство контейнерных операций. Как уже упоминалось, и строки, и векторы предоставляют оператор индексирования. У них также есть итераторы.
Как и указатели (см. раздел 2.3.2), итераторы обеспечивают косвенный доступ к объекту. В случае итератора этим объектом является элемент в контейнере или символ в строке. Итератор позволяет выбрать элемент, а также поддерживает операции перемещения с одного элемента на другой. Подобно указателям, итератор может быть допустим или недопустим. Допустимый итератор указывает либо на элемент, либо на позицию за последним элементом в контейнере. Все другие значения итератора недопустимы.
3.4.1. Использование итераторов
В отличие от указателей, для получения итератора не нужно использовать оператор обращения к адресу. Для этого обладающие итераторами типы имеют члены, возвращающие эти итераторы. В частности, они обладают функциями-членами begin() и end(). Функция-член begin() возвращает итератор, который обозначает первый элемент (или первый символ), если он есть.
// типы b и е определяют компилятор; см. раздел 2.5.2
// b обозначает первый элемент контейнера v, а е - элемент
// после последнего
auto b = v.begin(), е = v.end();
// b и е имеют одинаковый тип
Итератор, возвращенный функцией end(), указывает на следующую позицию за концом контейнера (или строки). Этот итератор обозначает несуществующий элемент за концом контейнера. Он используется как индикатор, означающий, что обработаны все элементы. Итератор, возвращенный функцией end(), называют итератором после конца (off-the-end iterator), или сокращенно итератором end. Если контейнер пуст, функция begin() возвращает тот же итератор, что и функция end().
Если контейнер пуст, возвращаемые функциями begin() и end() итераторы совпадают и, оба являются итератором после конца.
Обычно точный тип, который имеет итератор, неизвестен (да и не нужен). В этом примере при определении итераторов b и е использовался спецификатор auto (см. раздел 2.5.2). В результате тип этих переменных будет совпадать с возвращаемыми функциями-членами begin() и end() соответственно. Не будем пока распространяться об этих типах.
Операции с итераторамиИтераторы поддерживают лишь несколько операций, которые перечислены в табл. 3.6. Два допустимых итератора можно сравнить при помощи операторов == и !=. Итераторы равны, если они указывают на тот же элемент или если оба они указывают на позицию после конца того же контейнера. В противном случае они не равны.
Таблица 3.6. Стандартные операции с итераторами контейнера
*iter Возвращает ссылку на элемент, обозначенный итератором iter iter->mem Обращение к значению итератора iter и выборка члена mem основного элемента. Эквивалент (*iter).mem ++iter Инкремент итератора iter для обращения к следующему элементу контейнера --iter Декремент итератора iter для обращения к предыдущему элементу контейнера iter1 == iter2 iter1 != iter2 Сравнивает два итератора на равенство (неравенство). Два итератора равны, если они указывают на тот же элемент или на следующий элемент после конца того же контейнераПодобно указателям, к значению итератора можно обратиться, чтобы получить элемент, на который он ссылается. Кроме того, подобно указателям, можно обратиться к значению только допустимого итератора, который обозначает некий элемент (см. раздел 2.3.2). Результат обращения к значению недопустимого итератора или итератора после конца непредсказуем.
Перепишем программу из раздела 3.2.3, преобразующую строчные символы строки в прописные, с использованием итератора вместо индексирования:
string s("some string");
if (s.begin() != s.end()) { // удостовериться, что строка s не пуста
auto it = s.begin(); // it указывает на первый символ строки s
*it = toupper(*it); // текущий символ в верхний регистр
}
Как и в первоначальной программе, сначала удостоверимся, что строка s не пуста. В данном случае для этого сравниваются итераторы, возвращенные функциями begin() и end(). Эти итераторы равны, если строка пуста. Если они не равны, то в строке s есть по крайней мере один символ.
В теле оператора if функция begin() возвращает итератор на первый символ, который присваивается переменной it. Обращение к значению этого итератора и передача его функции toupper() позволяет перевести данный символ в верхний регистр. Кроме того, обращение к значению итератора it слева от оператора присвоения позволяет присвоить символ, возвращенный функцией toupper(), первому символу строки s. Как и в первоначальной программе, вывод будет таким:
Some string
Перемещение итератора с одного элемента на другойИтераторы используют оператор инкремента (оператор ++) (см. раздел 1.4.1) для перемещения с одного элемента на следующий. Операция приращения итератора логически подобна приращению целого числа. В случае целых чисел результатом будет целочисленное значение на единицу больше 1. В случае итераторов результатом будет перемещение итератора на одну позицию.
Поскольку итератор, возвращенный функцией end(), не указывает на элемент, он не допускает ни приращения, ни обращения к значению.
Перепишем программу, изменяющую регистр первого слова в строке, с использованием итератора.
// обрабатывать символы, пока они не исчерпаются,