[БЕЗ_ЗВУКА] Еще
одна область применения кортежей — это,
в первую очередь, возврат нескольких значений из функции.
Давайте покажем такой вот пример.
Представьте себе, что у вас есть какой-то класс,
в котором будет храниться информация о городах и странах.
Назовем его Cities, и в нем будет публичный метод,
который будет возвращать, назовем его FindCountry.
Метод, который будет искать по названию города
страну.
Он, наверное, должен быть константным.
Хотя для простоты давайте пока не будем.
Хотя нет, давайте сделаем метод константным.
Что же он должен вернуть?
По идее, он должен вернуть страну, которую он нашел.
А что он должен вернуть, если он страну не нашел?
А что, если есть два города, у которых в разных странах?
Что же мне вернуть?
Давайте мы решим, что мы вернем два значения из этой функции.
Во-первых, булевский признак того, успешно ли у меня нашлась единственная
страна для этого города, и, во-вторых, какое-то строковое сообщения.
Если страна нашлась, то это название этой страны, если страна не нашлась,
то объяснение, что именно пошло не так.
Либо она не нашлась, либо стран несколько.
Итак, я хочу написать вот такой метод, и он будет возвращать кортеж.
Давайте реализуем метод FindCountry, не особенно вдаваясь в подробности того,
как у нас будет реализован сам класс.
Итак, давайте мы создадим приватное поле со словарем,
который будет по названию города хранить название страны.
Скажем, city_to_country.
И, например,
создадим множество тех городов,
которые принадлежат нескольким странам.
Назовем их ambiguous_cities.
И теперь как мы будем реализовывать такой метод.
Наверное, напишем что-нибудь такое.
Если у нас города нет в нашем словаре city_to_country, даже,
наверное, давайте, если он там есть,
[БЕЗ_ЗВУКА] если там
есть ровно один такой город, то мы вернем признак того,
что город успешно нашелся, то есть true, и строчку с названием страны.
city_to_country[city].
Хорошо.
Иначе [БЕЗ_ЗВУКА] возможны какие ситуации?
Иначе возможна ситуация, что наш город есть во множестве ambiguous_cities,
то есть else if (ambiguous_cities .count
(city) == 1), то у нас есть несколько стран,
в которых этот город есть, мы должны вернуть признак того, что поиск не удался,
и вернуть строчку Ambiguous, чтобы сказать, что город как бы есть,
но в нескольких странах, поэтому не понятно, что возвращать.
Иначе получается, что города нет вообще, и нам нужно
вернуть признак того, что поиск не удался, потому что города нет, Not exist.
Отлично.
Давайте попробуем этот код скомпилировать.
И он не скомпилировался.
Давайте смотреть, почему.
Во-первых, у меня не получилось объявить поле типа set,
потому что я не подключил модуль set.
Эту ошибку мы исправляем.
Компилируем код снова.
Теперь у нас другая проблема.
У нас проблема с оператором квадратных скобок для словаря.
Дело в том, что у вас метод константный, у класса есть поле со словарем,
а квадратные скобки для словаря теоретически могут менять сам словарь,
вернее, его элементы, если их там нет.
Поэтому нужно как-то обратиться к ключу словаря, ключу константного словаря.
Квадратные скобки здесь не подходят, оказывается, есть специальный метод at,
про который мы немного говорили в первом курсе, который работает так же,
как квадратные скобки, возвращает соответствующее ключу значение,
но при этом если ключ не нашелся, он его не добавляет, а выбрасывает исключение.
Здесь он нам вполне подходит, потому что мы совершенно уверены,
что этот ключ в словаре есть.
Итак, мы снова компилируем код, и он снова не компилируется,
потому что мы опечатались в названии поля, вот тут.
Компилируем код еще раз.
И теперь всё в порядке.
Итак, мы научились возвращать из метода или из
любой функции несколько значений с помощью кортежа.
Как же нам теперь получить эти значения в месте вызова метода?
Давайте мы создадим объект типа Cities, скажем, по умолчанию там
все словари будут пустыми, то есть никаких городов там не будет на самом деле.
А теперь попробую я там поискать какой-нибудь город, с помощью метода
FindCountry поискать страну для города, например, для Волгограда.
Я поискал, и у меня на выходе получается кортеж.
Мы все-таки попробуем посмотреть,
что в этом кортеже лежит, с помощью метода get.
Но будем помнить, что так делать не стоит.
Нам нужно изучить что-то чуть более простое и понятное.
Тем не менее, давайте выведем первый элемент кортежа, который у нас получился.
Код компилируется, запускается, вводит Not exist.
Действительно, такого города нет.
Как же написать поаккуратнее?
Оказывается, можно заранее объявить переменные,
в которые вы хотите сохранить это булевский флаг и это строковое значение.
Давайте их объявим.
bool success, успех у нас или неуспех, и string message.
И теперь вот здесь вы можете написать так.
Вы можете присвоить результат FindCountry в связанной переменной
success и message.
Связанные кортежем.
И теперь просто выводить значения этих переменных.
Скомпилируем код.
Запустим.
Всё работает.
Мы не будем вдаваться в подробности, как оно работает,
но достаточно знать, что tie создает кортеж, который хранит ссылки.
Благодаря этому мы можем в эти связанные переменные сохранять то,
что нам вернул метод FindCountry.
Но, опять же, есть более прогрессивный метод.
Что нам не нравится в этом методе?
Что мы должны заранее видеть в переменной success и message?
То есть мы тратим три строчки на то, чтобы вызвать эту функцию.
В новом стандарте C++ можно проще.
Можно объявить эти переменные прямо здесь с помощью тех же structured bindings.
Мы напишем auto [success,
message] = вызов функции.
По сути, этот метод нам вернет кортеж, мы его сразу распакуем в эту пару переменных.
Давайте попробуем скомпилировать код,
запустить и увидеть, что и здесь отличный пример structured bindings.
Итак, если вы хотите вернуть несколько значений из функции или из метода,
используйте кортеж.
А если вы хотите сохранить этот кортеж в какой-то набор переменных,
используйте structured bindings или функцию tie.
Итак, давайте в заключение еще раз напомним,
что кортежи и пары нужно использовать аккуратно.
Они часто мешают читаемости кода, если вы начинаете обращаться к их полям.
Например, представьте себе, что вы хотите сохранить словарь из названия
города в его географические координаты.
Вы можете подумать, зачем мне заводить структуру для географических координат,
сохранять там два вещественных поля.
Я лучше пару использую.
Хорошо, это будет выглядеть таким образом.
У вас будет словарь,
у которого в значениях будут пары двух вещественных чисел.
Назовем его cities.
Как же потом вы будете, например, итерироваться по этому словарю?
Вы напишете (const auto& item : cities).
И если вы захотите здесь вывести, например, широту,
которая у вас будет первой координатой, то вы должны будете написать вот
так: cout << item.second — это значение словаря в паре «ключ — значение».
А затем .first.
То есть у вас элемент словаря — это пара из строки и пары.
У вас получилась пара пар.
Вы выводите item.second.first, и совершенно невозможно понять,
не спрашивая автора этого кода, что же здесь имелось в виду.
Поэтому, пожалуйста, используйте структуры с понятными названиями полей,
если только вам не нужно, например, быстренько написать оператор сравнения.
И там использовать tie.
Или вернуть несколько значений из функции.
Итак, мы узнали про специальные типы данных языка C++.
Во-первых, про кортежи,
которые позволяют упростить написание своего operator<, своего компаратора, и,
во-вторых, позволяют вернуть несколько значений из функции.
Кроме того, мы узнали как применяется structured bindings при получении
нескольких значений из функции, а также мы узнали про пары.
Частный случай кортежа, у которых понятные называния полей first и second.
Но все равно, лучше использовать свои структуры с понятными названиями полей,
если есть возможность.