0:00
Итак, мы подробно изучили классы.
Мы изучили, что в классах бывают поля, бывают методы, конструкторы и деструкторы.
Давайте наконец рассмотрим какой-нибудь реальный и полезный пример класса.
Например, часто приходится иметь дело с датами.
Дата — это по сути что?
Это день, месяц и год — три целых числа.
Логично просто взять и написать структуру.
Напишем структуру.
Структура Date, в ней будет поле day,
поле month и поле year — день, месяц и год.
Как мы будем эту структуру использовать?
Наверное, очень просто, вот так вот: создаем объект типа Date,
называем его date и пишем с помощью фигурных скобок какую-нибудь дату.
Например, 1 января 2017 года.
Пробуем скомпилировать этот код.
Код компилируется, скомпилировался.
Хорошо.
Давайте, наверное, как-нибудь эту дату выведем.
Напишем функцию PrintDate, которая примет на вход дату по константной ссылке,
чтобы лишний раз не копировать, и здесь ее выведет.
date.day — наверное, логично вывести их через точку, —
date.month, опять же точка, и date.year.
Перевод строки, и здесь эту дату выведем.
[ЗВУК] Давайте этот код
запустим и увидим нашу дату — 1 января.
Казалось бы, все нормально, но представьте теперь, что вы смотрите
на этот код спустя несколько лет, или кто-то другой смотрит на этот код,
причем он на него смотрит так, что не видит само определение структуры Date.
Вот, скажем, вот так вот.
И здесь написано не {1, 1, 2017}, а что-то вот такое, например, {10, 11, 12}.
Можете ли вы спустя несколько лет после того, как вы написали этот код,
или смотря на чужой вот такой вот код, сказать, какая это дата?
Может быть, это код русского человека, который привык сначала писать день,
потом месяц, потом год.
Тогда это 10-е ноября 12-го года.
Может быть, это код американца, и тогда это будет 11-е октября 12-го года.
А может быть, этот код писал программист,
и тогда у вас получается 12-е ноября 10-го года.
То есть нельзя вот по такой записи сразу легко сказать, где здесь день, где месяц,
а где год.
Как же мы решим эту проблему?
Мы напишем для нашей структуры конструктор.
Но если конструктор будет принимать три целых числа, то легче нам не станет.
Мы все так же будем видеть вот здесь три числа и не будем понимать, где день,
где месяц, а где год.
Поэтому конструктор будет принимать не целые числа, а некоторые обертки над ними.
А именно он будет принимать объект типа Day, new_day,
объект типа Month, который мы назовем new_month,
и объект типа Year, который мы назовем new_year.
Что это за типы?
Это на самом деле простые структуры с одним полем.
Сейчас мы увидим, зачем это нужно, как это нам поможет.
Итак, структура Day имеет одно единственное целочисленное поле,
назовем его value, например.
Аналогично выглядит структура Month,
тоже с единственным полем value,
и структура Year, то же самое, int value.
И конструктор принимает,
собственно, объекты этих типов и инициализирует поля этой структуры.
day = new_day.value,
month = new_month.value,
year = new_year.value.
Скомпилируется ли наш текущий код теперь?
Давайте посмотрим.
Что-то пошло не так.
Да, компилятор, видя запись {10, 11, 12}, не знает,
как по ним получить объекты day, month и year.
Хорошо.
Это вынуждает нас написать вот такой
код: Day{10},
Month{11}, Year{12}.
По сути, я сказал компилятору: «Вот здесь создай
объект типа Day со значением 10, здесь создай объект типа Month со значением 11,
здесь создай объект типа Year со значением 12».
Поскольку это структура, то конструкторы мне не нужны,
я просто использую фигурные скобки.
Давайте попробуем этот код скомпилировать.
Код не компилируется.
Почему?
Потому что компилятор смотрит на старый файл, я его не сохранил.
Теперь я его сохранил, и код компилируется.
Отлично, 10.11.12.
Код работает.
Во-первых, у нас получилось, что мы явно по этому коду видим,
что здесь день, здесь месяц, а здесь год.
Во-вторых, если вдруг я перепутаю, и у меня сначала будет месяц,
а потом день, то такой код не скомпилируется.
Ну просто потому, что компилятор ожидает сначала день, потом месяц,
а у меня сначала месяц, потом день.
Месяц нельзя привести ко дню, и наоборот.
Хорошо.
Все ли сейчас в порядке?
На самом деле, да, уже можно писать читаемый код.
Но тем не менее, опять же, представьте себя через несколько лет.
Или представьте, что кто-то другой пришел менять ваш код.
Точнее, пришел его посмотреть, заодно решил что-нибудь улучшить.
И вот он видит, что вы явно пишете Day{10}, Month{11}, Year{12}.
А он знает, ну или вы, собственно, уже знаете,
что если вы хотите вызвать конструктор от каких-то вот таких объектов,
и компилятор уже знает, что, например,
первый параметр конструктора имеет тип Day, вам не нужно явно указывать этот тип.
Вы можете просто оставить пустые фигурные скобки с 10,
а здесь просто фигурные скобки с 11, а здесь фигурные скобки с 12.
То есть вы, возможно, забыли о том, что это все делалось для лучшей
читаемости кода, и решили код сделать более коротким.
И при всем при этом он продолжает компилироваться.
Собственно, мы этот вопрос уже изучали.
Если компилятор явно видит,
какую переменную какого типа принимает функция или конструктор,
можно просто указать фигурные скобки со списком аргументов конструктора.
Вот, код работает.
Как же нам сделать его более устойчивым к таким вот оптимизациям,
к таким вот изменениям?
Давайте сделаем следующее.
Давайте все-таки напишем конструкторы этим структурам, простейшие конструкторы,
от одного целого числа.
Конструктор в структуре Day,
давайте здесь напишем new_value, value = new_value.
Простейший конструктор.
То же самое для месяца, [ЗВУК]
new_value, value = new_value.
И то же самое для года.
[ЗВУК] На самом деле,
сейчас лучше не стало.
Мы даже можем это увидеть, снова запустив этот код.
Да, код работает.
На самом деле, стало даже хуже.
Давайте увидим это, вернувшись к первоначальному варианту создания даты.
Мы снова можем не указывать фигурные скобки, мы снова можем писать,
как в самом начале: {10, 11, 12}.
Давайте это увидим.
Вот наш код успешно компилируется и запускается.
В чем же дело?
Мы, написав вот такие вот конструкторы, разрешили компилятору
неявно преобразовывать целое число в объект типа Day,
неявно преобразовывать целое число в объект типа Month и неявно преобразовывать
целое число в объект типа Year, что компилятор и сделал.
Он смог спокойно по аргументам 10, 11, 12 вызвать, получить Day, Month и Year.
Чтобы такого не происходило, мы должны компилятору сказать: «Не делай так больше.
Не осуществляй неявное преобразование целого числа вот к этим моим замечательным
структуркам».
Как мы это сделаем?
Мы напишем ключевое слово explicit.
Explicit означает «явный»,
мы сделаем эти конструкторы явными и запретим их вызывать без нашего ведома.
Все три конструктора, Day, Month и Year сделаем explicit.
Теперь попробуем этот код
запустить.
Видим, что у нас есть какие-то ошибки.
Собственно, компилятор снова не может по числам 10, 11 и 12 получить день,
месяц и год.
Более того, мы не можем не указывать типы, даже если мы используем фигурные скобки.
Мы все еще не сказали компилятору явно вызвать нужные конструкторы,
поэтому код не компилируется.
Вот компилятор пишет, что он не может по десятке в фигурных скобках получить Day,
потому что будет использован explicit constructor, явный конструктор.
Та же проблема с месяцем и с годом, везде мы пытаемся вызвать явный конструктор вот
с помощью такого синтаксиса.
Так делать нельзя, и поэтому нам придется написать вот так,
как мы и написали вначале.
Day{10}, Month{11} и Year{12}.
Компилируем этот код, запускаем, видим, что все хорошо.
Более того, теперь, поскольку у нас явно написаны конструкторы для структур Day,
Month и Year, мы можем вместо фигурных скобок использовать круглые,
если вдруг кому-то так привычнее.
Здесь круглая скобка, здесь круглая скобка,
и здесь тоже круглые скобки.
Код успешно компилируется и запускается.
Итак, что мы сейчас сделали?
По сути, мы с помощью механизма конструкторов, с помощью ключевого слова
explicit научились писать более читаемые структуры.
Точнее, структуры, у которых более читаемый способ создания.
Мы явно должны указать...
По сути, мы повторяем синтаксис именованных полей,
которые есть в некоторых языках программирования.
В C++ этого нет, поэтому можем сделать вот такой вот,
такой вот способ использовать с помощью трех вспомогательных структур.
Делаем три вспомогательные структуры, пишем для них explicit конструкторы,
явные конструкторы, и после этого можем использовать вот такой вот синтаксис
создания даты, явно указывая, где день, где месяц, а где год.