[БЕЗ_ЗВУКА] В данной лекции мы рассмотрим, как еще можно использовать шаблоны функций в языке C++. В курсах ранее мы часто печатали содержимое наших контейнеров, будь то вектор или map, на экран. Для этого мы определяли специальную функцию, либо переключали оператор вывода в поток. Давайте это сделаем и сейчас. Объявим vector<int>. Пусть он состоит из элементов 1, 2 и 3. Что мы хотим? Хотим просто вывести его на экран, в идеале вот таким вот синтаксисом, просто выводом в поток. Что мы уже умеем? Умеем мы взять и написать свой оператор вывода в поток. На вход он возвращает ссылку на поток, оператор вывода. На вход он принимает поток, в который мы будем выводить данные, и также сам вектор Обратите внимание, мы здесь явно пишем, что выводить мы умеем только вектор целых типов. Вектор дробных типов типа double мы выводить уже не умеем. Окей. Передали вектор как аргумент. Дальше с помощью цикла range-based for мы проитерируемся по нашему вектору. Выведем все его элементы в поток и не забудем вернуть ссылку на сам поток. Классно! Определили оператор вывода, выводим с помощью него вектор. Соберем наш код, запустим. Видим, что на экране появилось «1, 2 и 3». Отлично. Все так, как мы и хотели. Давайте поменяем тип нашего вектора на double, соберем наш код еще раз и видим, что появились ошибки компиляции. Давайте посмотрим на них подробнее. И эта ошибка компиляции нам гласит, что оператор вывода не определен для вектора типа double. Правильно, потому что мы его не написали. Что мы можем сделать? Мы можем взять, скопировать вот этот оператор вывода и определить его для вектора типа double, потом для вектора bar, для вектора векторов и так далее, дублировать каждый раз код. И если вдруг там где-то есть ошибка, то исправлять ее придется во всех копиях этой функции, что, конечно же, не очень здорово. Но теперь мы знаем про шаблоны функций в языке C++. Давайте здесь будем использовать именно их. Что нам надо сделать? Шаблонный параметр-тип у нас на самом деле один — вот этот вот тип int. Мы хотим, чтобы вместо него внутрь этой функции мы могли передавать любой вектор любого типа. То есть опять-таки здесь мы бы хотели иметь тип T, ну и с помощью ключевых слов мы объявили тип T для этой функции, шаблонный тип T. Давайте попробуем собрать наш код. Видим, что ошибки компиляции пропали. Запустим его, видим, что вектор действительно вывелся. Давайте одно число сделаем дробным. Видим, что действительно на экран вывелась 1,4, 2 и 3. Классно. Мы научились универсальным способом решать задачу для вектора. Давайте таким же универсальным способом научимся решать эту задачу и для других контейнеров в языке C++. Вот, предположим, у нас есть не вектор, а map. Давайте данный код я закомментирую. Объявим map. Например, из целого типа в целый тип. И заполним его. Пусть у нас единичка уходит в двойку, а тройке будет соответствовать четверка. Соберем наш код. Отлично. И попробуем вывести map на экран, опять-таки любимым нами способом просто cout. Видим, что опять появилось очень много ошибок компиляции. Посмотрим на них подробнее. И что мы здесь видим? Во-первых, видим, что оператор вывода не определен для map. Давайте его определим. Этот оператор вывода будет очень похож на оператор вывода вектора, за одним исключением. Map состоит из пар, где первый элемент в паре — это ключ, второй элемент — это значение. То есть на самом деле у map два шаблонных аргумента. На самом деле их чуть больше, но нам сейчас достаточно двух. First, Second. На самом деле map для нас выглядит как словарь, поэтому давайте их назовем Key и Value. [ЗВУК] И объявим их: Key. Соберем наш код, видим, что появились снова ошибки компиляции. Давайте взглянем на них поподробнее. Видим, что снова не определен оператор вывода, но для кого? Видим, что в этот раз он не определен для пары. При этом смотрите, какой интересный тип у пары. Первый элемент — const int, второй — int. Правильно, в словаре у нас ключ, его нельзя модифицировать, а значение, которое лежит по ключу — можно, поэтому у пары здесь такой интересный тип. Исходя из этой ошибки, нам надо написать оператор вывода в поток для пары. И, по идее, код вывода map в поток заработает. Давайте попробуем это сделать. Объявим еще один оператор вывода. Пара состоит из двух элементов, First и Second. Возвращать мы будем поток, принимать мы будем тоже поток. И объявляем пару. Будет ссылка, пара из элементов типа First, Second, p. Выводить будем просто p.first, запятая, p.second. И не cout, а out, return out. Попробуем собрать наш код. Видим, что он собрался, ошибки компиляции исчезли. Давайте же его запустим. Видим что? Что действительно элементы пары, элементы нашего словаря, они вывелись. Вот они: 1, 2 и 3, 4. При этом мы теперь легко можем заменить один из типов и здесь, например, создавать значение 2,5. Запустим наш код. Видим, что действительно он отработал корректно. Давайте еще раскомментируем наш код с вектором, чтобы убедиться, что ничего не сломалось, и запустим его еще раз. Отлично! У нас вывелся и вектор, и map. Давайте еще раз внимательно посмотрим на то, что мы сделали. Мы хотим выводить наши контейнеры в поток простым и удобным способом: сразу в cout. Для этого мы умеем перегружать операторы вывода в поток. Чтобы это сделать универсально, — ведь наши контейнеры бывают разных типов: и словари, и векторы — мы написали сразу шаблонный вывод, шаблонный оператор вывода в поток для вектора и для map. Вот они определены. При этом для их элементов мы требуем простого, мы требуем, просто чтобы они выводились в поток, чтобы они умели выводиться. Собственно, когда мы выводили map в поток, мы увидели новую ошибку компиляции. Там был сказано, что пара не умеет выводиться в поток, а пара — это элемент типа map. Мы написали оператор вывода в поток для пар, и после этого map тоже стал выводиться. Данный код можно улучшить. Во-первых, у нас здесь есть довольно сильное дублирование кода, то есть мы и там, и там итерируемся по контейнеру и делаем практически одни и те же вещи. То есть смотрите: мы даже в операторе вывода для map не переименовали аргумент, которым передаем map, то есть мы скопировали функцию вектора и прямо ничего не меняли в этом коде. И это, конечно же, не очень хорошо. В идеале такого большого дублирования быть не должно. Также наш вывод, если на него посмотреть еще раз, он немного не интуитивен. Например, из этого вывода сложно понять, на самом деле, что мы выводили. Это два вектора мы вывели или... На самом деле, непонятно, или вектор пар или словарь. Поэтому этот код еще можно улучшить, просто сделав его вывод более читаемым. Например, когда мы выводим map, обрамлять его в фигурные скобки, когда вектор — в квадратные, а когда мы выводим пару, обрамлять ее круглыми скобками. Все эти улучшения мы сделаем в следующем видео.