[БЕЗ_ЗВУКА] В этом блоке мы узнаем про новые типы данных языка C++, про которые мы не знали раньше, про пары и кортежи. Опять же мы начнём с проблемы. У нас была явная проблема в курсовом проекте первого курса. Мы там должны были хранить даты в виде структур, и в датах у нас были месяц, точнее, год, месяц и день. И мы должны были не просто их хранить, а хранить их в ключах словарей. Чтобы использовать какой-то тип в ключе словаря, поскольку словарь хранит свои ключи отсортировано, нам нужно для этого типа определить оператор <. И вы, наверняка, когда решали курсовой проект, определяли оператор <. Давайте я определю. bool operator< Принимаем одну дату lhs, принимаем другую дату rhs, и сравниваем их. И вот как же эти даты сравнивать? Был такой способ. Как бы мы хотим сравнивать лексикографически, наверное, даже хронологически. Соответственно, если у вас у дат разные года, то меньше та из них, у которой год меньше. return lhs.year < rhs.year Если у дат одинаковые года, но у них разные месяцы, то... если у них разные месяцы, то левое меньше правого, если у него месяц меньше. Довольно муторное занятие писать такой оператор сравнения. Давайте уже допишем, раз мы начали. И, наконец, сравниваем дни, если всё остальное одинаковое, если даты в рамках одного месяца одного года. Здесь, конечно, очень много мест, где можно ошибиться, просто безумно много для такой простой задачи. Мы могли, например, здесь захотеть скопировать вот эту часть и вставить её сюда. И заменить year на month, и забыть в каком-то месте заменить. Или мы могли написать здесь ≤ вместо < случайно, или что-то ещё, или здесь > написать случайно. Очень много мест, где можно ошибиться. Эту проблему мы решили в эталонном решении курсового проекта, которое мы опубликовали. Мы решили следующим образом. Мы создали вектор из тех значений даты, по которым надо сравнивать. Соответственно, левую дату нужно сначала сравнивать по году, затем по месяцу и, наконец, по дню. Ну и то же самое с правой датой. Соответственно, мы писали return один вектор созданный из элементов левой даты, меньше ли он, чем вектор, созданный из элементов правой даты? Здесь, конечно, тоже можно случайно опечататься, но уже гораздо меньше вероятность того, что мы опечатаемся. И здесь бонус в том, что для векторов уже определён оператор сравнения, тот самый лексикографический оператор сравнения, который здесь нам и нужен. Давайте проверим, что такой код работает, просто сравним две даты. Скажем, cout сравним, например, восьмое июня 2017 года и меньше ли оно, чем 26 января 2017 года. Вот, мы сравниваем две даты. Наверное, мы ожидаем, что первая из них больше, чем вторая. Давайте проверим, как раз у них год одинаковый, месяц разный. Неплохой довольно тест для нашей функции сравнения. Запускаем и видим ноль. Действительно, первая дата больше второй, а не меньше. Наш оператор сравнения работает, но есть здесь проблемы, конечно же, иначе бы мы не начинали этот блок лекций. Проблема следующая. Во-первых, вы используете тип вектор. А тип вектор он довольно мощный, он позволяет делать push back себя, удалять из середины, что-то ещё. Нам этот тип не нужен в данном случае. Нам нужно всего лишь объединить три значения в одно, для левой даты и для правой даты, объединить их в одну и сравнить. Здесь не нужен такой мощный тип как вектор. Более того, что, если у вас одно из полей даты, например, месяц, это строка? Давайте я даже вот здесь напишу. Адаптирую свой пример. Вот здесь у меня будет июнь, а здесь у меня будет январь. И аккуратнее это всё отформатирую. Вы уже, наверное, представляете, в чём у нас будет проблема. Проблема будет в том, что вектор может хранить значения строго одного типа. И я просто не могу придумать, какой мне тип здесь написать. Если я ставлю здесь тип int, у меня просто вектор не сможет создаться. Давайте попробуем. Вот куча ошибок у компилятора. В общем, вектор создаться не может, потому что у вас lhs.month и rhs.month это строки. Что же делать? Как вам объединить три вот таких значения каких-то возможно разных типов воедино, да ещё и сравнить при этом? Оказывается, это можно сделать довольно просто. Для этого нужно подключить, во-первых, библиотеку tuple. А здесь вместо вектора вызывать функцию tie от тех значений, которые вы хотите связать. Tie, как раз, по-английски «связать». Вы хотите связать год, месяц и день левой даты и год, месяц и день правой даты, связать их и сравнить. Давайте я скомпилирую код, запущу — и он компилируется, и у меня результат ноль. Кстати, ноль только благодаря тому, что у нас июнь лексикографически больше чем январь. Но тем не менее, у нас код компилируется и делает ровно то, что нужно. Давайте разбираться, что же такое tie. tie — это, на самом деле, некоторая функция, а вот что она возвращает, это гораздо более интересный вопрос. Давайте узнаем. Давайте сохраним во временную переменную lhs_key результат первого вызова tie, и во временную переменную rhs_key результат вызова tie для правой даты. Теперь можем написать return lhs_key < rhs_key. И такой код также будет компилироваться. Но для чего мы так написали? Мы написали для того, чтобы разобраться, что за типы имеют lhs_key и rhs_key, то есть результаты вызова функции tie. Как мы это узнаем? Например, уже с помощью известного нам странного метода — это породить ошибку компиляции. Например, сложив эти две переменные — их нельзя складывать, их можно только сравнивать. И компилятор нам об этом должен сказать. Давайте попробуем. Вот у нас много ошибок, замечательно. О! Вот, кажется, мы что-то узнали. Итак, мы видим, что оператор + не определён. А для чего он не определён? Для std tuple от const int& const basic string& const int&. Итак, lhs_key и rhs_key имеют тип tuple от const int& const string&. То что вы видили в ошибке, вот это вот basic string и что-то дальше длинное в скобках, это просто более подробное название типа string. И const int&. Очень громоздкое название типа. Слава Богу, мы использовали авто. Итак, вот такой тип у этой переменной и, на самом деле, такой же тип у той переменной, давайте, я не буду показывать. Что такое tuple? tuple — это по сути структура, которую вам не надо отдельно объявлять. Эта структура из трёх полей. В данном случае, const int&, const int&, const int&. Замечательно. Даже очень удобно. Смотрите. Мы, получается, когда мы хотели связать целое число, строку и целое число, мы хотели сложить их в какие-то временные переменные, кортежи — tuple это кортеж — и затем их сравнить. Заметьте, мы предусмотрительно создали кортеж, в котором все поля — это ссылки, и поэтому строка lhs.month и rhs.month не скопировалась внутрь этих кортежей. Конечно, если строки большие, их копировать дорого, мы это знаем и те, кто написали функцию tie, об этом знают, и поэтому мы создали кортеж из ссылки, никакого копирования здесь строки не случилось. Хорошо. Итак, мы узнали более простой способ написать оператор сравнения для своих структур. Далее мы познакомимся подробнее с тем типом tuple (кортеж), про который мы только что узнали, и который помог нам решить нашу задачу.