[БЕЗ_ЗВУКА] Продолжим изучать, в чем польза от применения константности в C++. И давайте рассмотрим вот такой пример. У нас есть вектор numbers, это вектор целых чисел, который проинициализирован каким-то набором чисел. Дальше мы в этом векторе с помощью алгоритма find находим позицию, элемент в которой равен 4. Сохраняем эту позицию в итератор и затем через этот итератор мы свой вектор меняем. И выводим все элементы вектора. Запустим эту программу и увидим, что она отработала, как и ожидалось. У нас вот здесь была 4, мы ее нашли и через итератор поменяли — сделали 21. Смотрите: когда мы через итератор поменяли 4 на 21, то на вот этот вектор, он остался вектором в том смысле, что он остался упорядоченной последовательностью целых чисел. А теперь давайте заменим вектор на set. Запустим компиляцию и увидим, что наша программа не компилируется — она не компилируется в том месте, где мы по итератору пытаемся поменять теперь уже элемент множества. И сообщение компилятора такое: assignment of read-only location. То есть мы не можем поменять по итератору элемент множества. Почему так происходит? Давайте вспомним, как устроено стандартное множество внутри? Внутри оно представляет из себя двоичное дерево поиска, и, как мы помним, основным свойством двоичного дерева поиска является то, что все узлы в поддереве слева, они меньше, чем значение в текущем узле, а все значения в правом поддереве больше, чем значения текущего узла. И вот теперь давайте представим, что у нас есть итератор, который указывает на узел со значением 4. И мы через вот этот итератор меняем значение в узле на 21. Что происходит? Мы полностью разрушаем наше свойство, свойство нашего двоичного дерева поиска, и получившееся двоичное дерево перестает быть деревом поиска, потому что для него больше не выполняется основное свойство. Значит, мы нарушаем инвариант, который хранится внутри класса set, и дальнейшие операции с этим множеством могут работать некорректно. Каким же образом реализация стандартного множества гарантирует нам, что мы не можем поменять элемент через итератор. Очень просто: разименование итератора множества возвращает константную ссылку на элемент. То есть мы можем написать const int ссылка p, например, = *it, и это будет компилироваться. Но если же мы попробуем присвоить неконстантные ссылки, то у нас компиляция не удастся. Таким образом, константность позволяет нам поддерживать инвариант внутри класса set. Давайте рассмотрим другой пример, в котором константность позволяет нам сохранить инвариант. Пример вот какой: у нас есть вектор целых чисел sorted_numbers, который создается вызовом функции Sorted. Сама функция предельно проста — она принимает вектор по значению, сортирует его и возвращает. Дальше у нас выводятся все элементы вектора, и смотрим, что у нас здесь в функции main еще есть. Есть вот этот комментарий: «Какой-то код». Это означает, что у нас здесь много кода, который что-то делает, просто мы в качестве однострочного комментария это оформили, но давайте представим, что здесь много разнообразного кода. Дальше у нас вызываются функции ProcessNumbersOne, ProcessNumbersTwo, ProcessNumbersThree. Во все из них передается наш вектор sorted_numbers. Хорошо. Дальше у нас идет еще какой-то код. И, наконец, в конце функции main мы из входного потока считываем количество чисел, считываем числа по одному и проверяем, есть ли считанное число в нашем векторе sorted_numbers. При этом, так как мы sorted_numbers создали с помощью вызова функции Sorted, то вот здесь внизу в цикле мы для проверки, есть элемент в векторе или нет его, используем двоичный поиск, потому что мы знаем, что вектор отсортирован, поэтому мы можем выполнять не линейный поиск, а более быстрый — двоичный. Однако возникает вопрос. Смотрите, вот мы вектор создали здесь, здесь у нас много кода. Дальше он передается в три какие-то функции. И по вызову этих функций совершенно неочевидно, что они этот вектор не меняют. А нам нужно быть уверенными, что к моменту, когда мы придем вот в этот вот цикл, вектор все так же останется отсортированным, чтобы мы были уверены, что мы можем искать в нем двоичным поиском. В текущей ситуации, чтобы понять, действительно ли вектор остается отсортированным, нам нужно заходить внутрь вот этих функций ProcessNumbersOne, Two and Three, смотреть, как они принимают этот вектор. Если они принимают его по неконстантной ссылке, то нам нужно смотреть их реализацию и понимать, меняется этот вектор или нет. Это довольно сложно и, соответственно, наш код, он со временем еще будет меняться, и нам нужно, получается, каждый раз его весь перечитывать, чтобы убеждаться, что вектор остается отсортированным. Это неудобно и непрактично. Вместо этого мы можем наш вектор sorted_numbers сделать константным, и тогда сам язык гарантирует нам, что вот к этому моменту, когда мы будем выполнять двоичный поиск по вектору, он останется отсортированным, потому что мы объявили его константным и уже не важно, как он передается в функции ProcessNumbersOne, Two и Three — мы знаем, что он не будет изменен, потому что он константный. И тогда вот здесь мы уверены, что мы можем искать по нему с помощью двоичного поиска. Смотрите какая важная здесь возникает особенность использования константности в C++. У нас вектор sorted_numbers — это не просто вектор, это не просто упорядоченная последовательность целых чисел. Это отсортированный вектор. Сортированность — это дополнительное свойство вот этого конкретного объекта класса vector int. И вот это дополнительное свойство, которое присуще только одному объекту, мы поддерживаем и гарантируем с помощью константности. Мы говорим: мы создали вектор, он отсортированный по тому, как он создан. И за счет того что он константный мы гарантируем, что куда бы мы его не передали, это свойство отсортированности никуда не пропадет. Таким образом, мы с вами в этом видео узнали, что константность в C++ позволяет поддерживать инварианты в классах и объектах.