📚 Hub Books: Онлайн-чтение книгРазная литератураC++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

Шрифт:

-
+

Интервал:

-
+
1 ... 78 79 80 81 82 83 84 85 86 ... 121
Перейти на страницу:
class="empty-line"/>

Здесь мы сначала преобразуем экземпляр типа chrono::time_point<chrono::system_clock> к типу std::time_t. Значения этого типа можно преобразовать в локальное время, что мы делаем с помощью функции std::localtime. Она возвращает указатель на преобразованное значение (не волнуйтесь об управлении памятью, лежащей за данным указателем; это статический объект, и для него память в куче не выделяется), которое мы наконец можем вывести на экран.

Функция std::put_time принимает такой объект и строку формата. Строка "%c" отображает стандартную строку даты-времени, например "Sun Mar 12 11:33:40 2017".

Мы также могли бы указать строку "%m/%d/%y", и тогда программа вывела бы на экран дату в формате 03/12/17. Весь список существующих форматов времени слишком длинный, он хорошо задокументирован в онлайн-справочнике по С++.

Помимо вывода на экран мы добавили смещения к нашему моменту времени. Это было просто, поскольку можно выразить промежутки времени, такие как 12 часов 15 минут, в виде 12h+15min. Пространство имен chrono_literals предоставляет удобные литералы типов для часов (h), минут (min), секунд (s), миллисекунд (ms), микросекунд (us) и наносекунд (ns).

Добавление подобной продолжительности к значению момента времени создает новое значение момента времени, поскольку типы имеют соответствующие перегруженные версии операторов + и –, именно поэтому так легко добавлять и отображать смещения времени.

Безопасно извещаем о сбое с помощью std::optional

Когда программа общается с внешним миром и полагается на значения, получаемые извне, могут происходить всевозможные сбои.

Это означает вот что: когда мы пишем функцию, которая должна возвращать значение, но также может дать сбой, это нужно отразить с помощью неких изменений в интерфейсе функции. У нас есть несколько вариантов. Посмотрим, как разработать интерфейс функции, которая возвращает строку, но способна дать сбой:

□ использовать возвращаемое значение, указывающее на успех, и выходные параметры: bool get_string(string&);;

□ возвращать указатель (или умный указатель), значение которого можно установить на nullptr в случае сбоя: string* get_string();;

□ генерировать исключение в случае сбоя и оставить сигнатуру функции очень простой: string get_string();.

Все эти подходы имеют свои преимущества и недостатки. Начиная с C++17, существует новый тип, который можно применять для решения такой задачи другим способом: std::optional. Идея необязательных значений происходит из чисто функциональных языков программирования (там они иногда называются типами Maybe (может быть)) и позволяет писать очень изящный код.

Мы можем обернуть в тип optional наши собственные типы, чтобы указать на пустые или ошибочные значения. Ниже мы узнаем, как это делается.

Как это делается

В этом примере мы реализуем программу, которая считывает целые числа, поступающие от пользователя, и складывает их. Поскольку пользователь всегда может ввести случайные символы вместо чисел, мы увидим, как тип optional способен улучшить обработку ошибок.

1. Сначала включим все необходимые заголовочные файлы и объявим об использовании пространства имен std:

#include <iostream>

#include <optional>

using namespace std;

2. Определим целочисленный тип, который, может быть, содержит значение. Нам идеально подойдет тип std::optional. Обернув любой тип в тип optional, задаем ему еще одно возможное состояние, которое отражает тот факт, что тип не имеет значения:

using oint = optional<int>;

3. Определив необязательный целочисленный тип, можем выразить тот факт, что функция, которая возвращает целое число, тоже способна дать сбой. Если мы возьмем целое число из пользовательских входных данных, то существует вероятность сбоя, поскольку пользователь может ввести какой-то другой символ, несмотря на наше приглашение. Возврат целого числа, обернутого в тип optional, идеально решает эту проблему. Если нам удалось считать целое число, то передадим его конструктору типа optional<int>. В противном случае вернем экземпляр типа optional, созданный с помощью конструктора по умолчанию, что говорит о сбое или пустоте.

oint read_int()

{

  int i;

  if (cin >> i) { return {i}; }

  return {};

}

4. Наши возможности не ограничиваются возвратом целых чисел из функций, способных дать сбой. Что если мы подсчитаем сумму двух целых чисел, которые могут не содержать значений? Мы получим реальную численную сумму только в том случае, если оба операнда содержат значения. В любом другом нужно вернуть пустую переменную типа optional. Эту функцию следует немного пояснить: неявно преобразуя переменные типа optional<int>, a и b, к булевым выражениям (с помощью конструкций !a и !b), мы узнаем, содержат ли они реальные значения. Если да, то можно получить к ним доступ как к указателям или итераторам, просто разыменовав их с использованием конструкций *a и *b:

oint operator+(oint a, oint b)

{

  if (!a || !b) { return {}; }

  return {*a + *b};

}

5. Сложение обычного целого числа и целого числа, которое может не содержать значений, следует той же логике:

oint operator+(oint a, int b)

{

  if (!a) { return {}; }

  return {*a + b};

}

6. Теперь напишем программу, совершающую некие действия с целыми числами, которые могут не содержать значений. Пригласим пользователя ввести два числа:

int main()

{

  cout << "Please enter 2 integers.n> ";

  auto a {read_int()};

  auto b {read_int()};

7. Сложим эти числа, а затем добавим к полученной сумме значение 10. Поскольку a и b могут не иметь значений, sum также будет необязательной целочисленной переменной:

  auto sum (a + b + 10);

8. Если переменные a и/или b не содержат значений, то sum не может иметь значения. Положительный момент заключается в том, что не нужно явно проверять значения переменных a и b. Сложение пустых экземпляров типа optional — полностью корректное поведение, поскольку мы определили безопасный оператор + для этих типов. Таким образом можно произвольно сложить несколько пустых необязательных экземпляров, а проверять нужно только полученный экземпляр. Если он содержит значение, то мы можем безопасно получить к нему доступ и вывести его на экран:

  if (sum) {

    cout << *a << " + " << *b << " + 10 = "

         << *sum << 'n';

9. Если пользователь вводит не числа, то мы сообщаем об ошибке:

1 ... 78 79 80 81 82 83 84 85 86 ... 121
Перейти на страницу:

Комментарии

Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!

Никто еще не прокомментировал. Хотите быть первым, кто выскажется?