[БЕЗ_ЗВУКА] В
предыдущем видео мы добавили в наш вектор методы begin() и end(),
что позволило использовать его в цикле for.
Теперь давайте же напишем для нашего вектора
функцию print() которая будет его печатать на консоль.
Почему бы нам этого не сделать?
Это у нас будет шаблонная функция,
Print, и так как мы собираемся только читать наш вектор,
печатать его, мы будем принимать его по константной ссылке.
И здесь...
x : v
cout << x << ' ' — мы
выводим наш вектор на экран.
Компилируем.
И у нас компилируется, потому что мы, вообще говоря, нашу функцию не вызываем.
А вот давайте мы теперь вызовем.
Запустим компиляцию, и у нас не компилируется.
Причём вот с таким сообщением:
«Передача 'const SimpleVector<int>' ...
discards qualifiers».
Вы, конечно, вряд ли помните, но мы это сообщение об ошибке
рассматривали в нашем первом курсе «Белый пояс по C++».
И оно означало определённые проблемы с константностью.
Давайте посмотрим внимательно.
Давайте мы развернём вот этот range based for,
развернём в то, во что он раскрывается,
то есть перепишем его так: auto i = v.begin();
i != v.end(); ++i.
Снова запустим компиляцию.
У нас снова не скомпилируется, правда тут есть ещё про x сообщение.
У нас не скомпилируется с тем же самым сообщением.
Но теперь становится понятна причина.
Смотрите.
Вектор v мы передаём по константной ссылке.
Раз ссылка константная,
то мы можем вызывать только константные методы у объекта v.
Но методы begin() и end() не являются константными,
то есть мы не сказали компилятору, что они никак не изменяют внутреннее
устройство вектора, а значит, компилятор думает, что вызов метода begin() может
изменить внутреннее состояние вектора, и поэтому он нам запрещает.
Таким образом, у нас здесь не компилируется из-за того,
что методы begin(), end() не константные.
Давайте сделаем их константными.
Мы же всего лишь читаем наш вектор.
Никак его не изменяем.
Сделали константными, запускаем компиляцию,
и отлично у нас всё компилируется и работает.
Вот у нас вектор запомнил значения 5, 3, 4, −1.
И печать его выполняется.
Вроде бы всё хорошо, всё прекрасно.
Но есть одна проблема.
В текущем виде мы можем функцией Print случайно наш вектор поменять.
Например, мы можем, ну вот как-то так вот у нас получилось,
что мы в нашей функции Print случайно присвоили очередной элемент цикла 42.
Наша программа продолжает компилироваться.
А теперь смотрите, что мы делаем: мы наш вектор, давайте тут вот для
удобства будем выводить перевод строки, мы наш вектор выводим два раза подряд.
Берём вектор, печатаем его два раза подряд.
Функция принимает его по константной ссылке,
поэтому мы должны увидеть две одинаковые строчки.
Запускаем нашу программу и видим странную вещь.
В начале у нас вывелось 5, 3, 4, −1,
а второй раз четыре раза вывелось число 42.
То есть у нас функция Print(), которая принимает вектор по константной ссылке и
обещает, что никогда его не будет менять, на самом деле его меняет.
Так что в данном случае мы не до конца решили проблему.
Недостаточно пометить методы begin() и end() константными.
Нужно сделать что-то ещё.
И сделать нужно вот что.
Нам нужно возвращать из методов begin() и end() не
просто указатели, а указатели на константу.
Мы добавляем в начало возвращаемого типа слово const,
и возвращаемый тип у методов begin() и end() теперь становятся const T*.
Компилируем нашу программу.
И она не компилируется.
Нам компилятор говорит,
что мы осуществляем
запись в объект *I, который является объектом только для чтения.
То есть теперь компилятор нас защищает от случайного изменения вектора,
принимаемого по константной ссылке.
Удаляем эту строчку, компилируем, запускаем.
И отлично.
Теперь мы видим, что оба вызова функции Print() вывели, что надо,
и к тому же мы знаем, что компилятор нас защищает от случайного изменения вектора.
Вроде бы всё хорошо.
Но есть другая проблема.
Давайте уберём один из вызовов функции Print() и
попробуем наш вектор отсортировать.
sv.begin(), sv.end() Здесь же у нас не константный, его можно менять.
Запускаем компиляцию.
И что-то пошло не так.
Тут целый ворох ошибок, и опять что-то
там про «assignment of read-only location».
Дело в том, что теперь после наших изменений
методы begin() и end() возвращают указатели на константу.
То есть указатели, запрещающие изменять объекты, на которые они указывают.
И мы не можем через эти итераторы, которые возвращаются, менять наш вектор.
Мы не можем его, например, отсортировать.
Чтобы решить и эту проблему, нам нужно в нашем векторе завести две пары методов.
Одни для константного вектора, а другие для неконстантного.
То есть у нас появляются две пары методов begin() и end().
У одних из них, у одной пары этих методов есть
модификатор const, и они возвращают указатель на константу,
а у другой пары модификатора const нет, и они возвращают просто указатель.
Компилируем наш код, он компилируется, запускаем, он работает и выводит.
Мы видим, что у нас сортировка успешно выполняется, печать выполняется,
и выводится отсортированный вектор.
Что означает вот эта пара методов с незнакомыми названиями?
Методы begin() и end() неконстантные будут вызываться
для объектов нашего класса SimpleVector, которые не являются константными, то есть
если мы передаём по неконстантной ссылке его или просто обращаемся к переменной.
В данном случае вот здесь, sv — это переменная неконстантная, поэтому там
будет вызываться begin() и end(), неконстантные версии begin() и end().
Когда же у нас вектор передан по константной ссылке или переменная
константная, будут вызываться вот эти методы,
которые константные и возвращают указатели,
запрещающие менять объект, на который они указывают.
Вот таким образом мы смогли
научить наш вектор передаваться функции по константной ссылке,
быть безопасным и, в то же время, разрешать свою модификацию.