Давайте рассмотрим вот такой пример. У нас есть вектор целых чисел, и мы с вами знаем, что чтобы по нему проитерироваться, мы можем написать простой и удобный цикл, который называется range-based-for. Ну и давайте, например, просто выведем элементы нашего вектора. Все, код компилируется, работает, все нормально. Мы это с вами кучу раз делали. Но если мы захотим проитерироваться не по всему вектору, а, например, по первым его трем элементам, то мы уже не можем воспользоваться удобным циклом range-based-for, и нам приходится вместо этого писать обычный цикл со счетчиком. Вот таким образом. Он у нас, конечно, компилируется и работает, но это не так удобно, как в первом случае. Кроме того, в данном случае мы можем допустить ошибку: если в нашем векторе будет меньше трех элементов, то наш цикл просто выйдет за границы вектора, и программа может вести себя совсем не так, как мы от нее ожидаем. Ну и вообще тут получается, что нет какого-то универсализма, какой-то универсальности нету. Мы по целому вектору можем удобным циклом итерироваться, а по части вектора — нет. И поэтому давайте мы попробуем исправить этот недостаток. Давайте поставим перед собой задачу написать функцию Head, которой мы будем пользоваться вот таким образом. Эта запись означает, что я хочу с помощью удобного цикла range-based-for проитерироваться только по первым трем элементам нашего вектора. Кроме того, эта функция должна корректно работать, и когда в векторе меньше чем три элемента. Собственно, у вас может возникнуть вопрос: «Ну а зачем нам эта функция, потому что удобнее печать? Но, может быть, в ней есть еще какой-то смысл?». Смотрите, у меня есть такой необычный пример. Это немножко обработанная в графическом редакторе выдача Яндекса по запросу «язык программирования c++». И что мы видим? Мы видим, что по этому запросу нашлось 59 миллионов результатов. Но на первой странице поисковой выдачи представлены только десять из них. И вот мы можем представить, чисто теоретически, что у нас есть вектор из 59 миллионов релевантных документов, и только первые десять из них нам нужно обработать, нам нужно по ним пройтись, и сформировать из них поисковую выдачу, первую страницу поисковой выдачи. И когда вы доберетесь до финальной задачи этого курса, вы поймете, что я не просто так приводил этот пример. Поэтому давайте подумаем, как мы, пользуясь теми знаниями, которые у нас есть сейчас, могли бы написать функцию Head. Что мы можем сделать? Мы можем, например, самый простой вариант, возвращать из функции Head копию префикса вектора, по которому мы можем пройтись. Как это будет выглядеть? Мы напишем шаблон функции в данном случае. У нас Head — это функция, которая будет... Функция Head, она возвращает vector<T>, принимает vector<T> по ссылке. [ЗВУК] И вот такая у нее сигнатура. То есть она по ссылке принимает вектор, и второй параметр — top, он задает, собственно, какой размер префикса мы хотим, по какому размеру префикса мы хотим проитерироваться. И что мы делаем? Мы просто возвращаем вектор из двух итераторов. Первый итератор — это, собственно, begin, а второй — это next (v.begin(), min(top, v.size())). То есть первый итератор — это начало вектора, а второй — это begin, который мы с помощью функции next продвигаем на минимум из значения параметра top и размера вектора. Это, собственно... Минимум мы берем для того случая, когда мы хотим три элемента, а в векторе у нас их всего два, чтобы вернуть только два элемента. И наша функция, наша функция и наш код весь целиком компилируется, работает. Действительно, мы вот здесь попросили три элемента, и в консоль вывелись элементы 1, 2 и 3. Вроде бы все хорошо, но, я уверен, вы уже сами заметили недостатки в текущей реализации функции Head. Во-первых, конечно же, она создает копию вектора. И с точки зрения производительности, с точки зрения эффективности кода, это совершенно непрактично, потому что мы всего лишь хотим проитерироваться по вектору, а для этого мы создаем его полную копию. Представьте, что мы хотим проитерироваться по всем элементам, кроме последнего. И мы могли бы это делать с помощью функции Head, но тогда мы создавали бы копию почти всего вектора. Поэтому то, что она создает копию — это очень плохо с точки зрения производительности. Кроме того, мы, вообще говоря, можем захотеть сделать вот что. Мы можем захотеть пройтись по префиксу нашего вектора и, например, увеличить каждый его элемент на единицу. Тогда, если мы после этого пройдемся по самому вектору целиком, мы будем ожидать, что первые его три элемента увеличатся на единицу, а четвертый и пятый не изменятся. Запускаем наш код и видим, что сам вектор не изменился. То есть у нас все компилируется, действительно у нас здесь x принимается по ссылке и изменяется но сам этот исходный вектор не изменяется. Хотя вот из этой записи логично ожидать, что мы действительно изменяем сам вектор. Но это не так. И это второй недостаток текущей реализации функции Head. Поэтому создание копии вектора нам не подходит. И нам нужен какой-то другой способ, который бы позволил нам из функции Head вернуть диапазон внутри исходного вектора, и с помощью этого диапазона обращаться к элементам самого вектора. И что бы нам тут могло помочь? Мы можем возвращать пару итераторов и итерироваться по ним. Собственно, из функции Head вместо копии вектора возвращать лишь пару итераторов на вектор v и итерироваться в цикле по ним. Как мы это можем сделать? Как раз с помощью шаблонов классов, с которыми мы с вами познакомились в предыдущем видео. Смотрите, что мы делаем. У нас функция Head — шаблонная. Она принимает вектор любого типа. И поэтому должна, собственно, возвращать и пару итераторов для вектора любого типа. Поэтому пару итераторов мы тоже представляем в виде шаблонного в данном случае уже класса. Делаем вот что. Пишем: template <typename Iterator>. Можно здесь написать что угодно, просто я более, более развернутое имя для параметра выбрал. struct IteratorRange, диапазон итераторов. И объявляем поля first и last типа Iterator — собственно, этого самого нашего шаблонного типа. Теперь нам нужно этот шаблон класса применить внутри функции Head. Смотрите, вместо вектора мы будем возвращать IteratorRange от vector<T>:: iterator. Так, немножечко переформатируем код, чтобы все помещалось. Компилируем, и у нас не компилируется. На самом деле, здесь задумано было, что не будет компилироваться, потому что здесь еще нужно написать слово typename. Мы не будем рассматривать, почему его здесь нужно писать, потому что это очень неочевидный аспект языка C++, на самом деле встречается нечасто, поэтому мы оставим за скобками, вы сможете пользоваться шаблонами классов, и не зная эту особенность. Давайте еще раз скомпилируем и обратим внимание, на самом деле, на другую, более важную вещь, что сейчас у нас код все равно не компилируется. Почему? Потому что мы из функции Head возвращаем IteratorRange — это структура из двух итераторов, и, естественно, по ней нельзя пройтись с помощью цикла for. Давайте посмотрим, что же нам тут пишет компилятор. Давайте прямо увеличим окно с ошибками и посмотрим на сообщение об ошибке. Смотрите, здесь написано: "has no member named 'begin'", "has no member named 'end'". И если мы его тут пролистаем, то здесь упоминается наш шаблон IteratorRange, но после него идут угловые скобки, то есть это конкретный класс, который сгенерировался из этого шаблона. И компилятор нам жалуется, что у этого класса нет методов begin и end. И это на самом деле подсказка, что нам нужно сделать, чтобы по классу, созданному из шаблона IteratorRange, можно было бы итерироваться циклом for. Собственно, смотрите: цикл for, range-based-for, он итерируется по коллекции. Чтобы по ней проитерироваться, ему нужно каким-то образом узнать, где она начинается, и где она заканчивается. И цикл range-based-for это делает, вызывая у переданного вот сюда вот объекта методы begin и end, которые должны возвращать итератор. Поэтому, чтобы разрешить итерацию с помощью range-based-for по классу, созданному из шаблона IteratorRange, мы просто должны добавить ему методы begin и end. Давайте сделаем это. Добавим метод begin, который будет возвращать поле first, и добавим метод end, который будет возвращать поле last. Мы сделали два этих действия, добавили два этих метода. Компилируем — и оно компилируется. Запускаем — и оно работает. Давайте еще раз посмотрим. Что мы делаем? Мы вызываем функцию Head для первых трех элементов нашего вектора, обращаемся к этим элементам по ссылке, изменяем их, увеличиваем на единицу, а потом идем по всему нашему вектору и выводим его элементы. И у нас в консоли появилось: 2, 3, 4, 4, 5. То есть действительно первые три элемента вектора с помощью нашей функции Head были увеличены на единицу, и потом мы смогли увидеть результат этого изменения. Таким образом, давайте подведем итоги этого видео. Мы с вами узнали, что, чтобы по объекту класса можно было итерироваться с помощью цикла for, этот класс должен иметь методы begin и end. И при этом эти методы должны возвращать итераторы на начало и конец той коллекции, которую объект этого класса представляет.