cout << today << '\n'; // использовать объект today
// ...
init_day(today,2008,3,30);
// ...
Date tomorrow;
tomorrow.y = today.y;
tomorrow.m = today.m;
tomorrow.d = today.d+1; // добавляем единицу к объекту today
cout << tomorrow << '\n'; // используем объект tomorrow
}
Здесь мы “забыли” немедленно инициализировать объект today
, и до вызова функции init_day()
этот объект будет иметь неопределенное значение. Кроме того, “кто-то” решил, что вызывать функцию add_day()
лишняя потеря времени (или просто не знал о ее существовании), и создал объект tomorrow
вручную. Это плохой и даже очень плохой код. Вероятно, в большинстве случае эта программа будет работать, но даже самые небольшие изменения приведут к серьезным ошибкам. Например, отсутствие инициализации объекта типа Date
приведет к выводу на экран так называемого “мусора”, а прибавление единицы к члену d
вообще представляет собой мину с часовым механизмом: когда объект today
окажется последним днем месяца, его увеличение на единицу приведет к появлению неправильной даты. Хуже всего в этом очень плохом коде то, что он не выглядит плохим.
Такие размышления приводят нас к мысли о необходимости функции инициализации, которую нельзя забыть, и об операциях, которые невозможно пропустить. Основным инструментом в этом механизме являются
// простая структура Date,
// гарантирующая инициализацию с помощью конструктора
// и обеспечивающая удобство обозначений
struct Date {
int y, m, d; // год, месяц, день
Date(int y, int m, int d); // проверяем корректность даты
// и выполняем инициализацию
void add_day(int n); // увеличиваем объект типа Date на n дней
};
Функция-член, имя которой совпадает с именем класса, является особой. Она называется
Date my_birthday; // ошибка: объект my_birthday не инициализирован
Date today(12,24,2007); // Ой! Ошибка на этапе выполнения
Date last(2000, 12, 31); // OK (разговорный стиль)
Date christmas = Date(1976,12,24); // также OK (многословный стиль)
Попытка объявить объект my_birthday
провалится, поскольку мы не указали требуемое начальное значение. Попытку объявить объект today
компилятор пропустит, но проверочный код в конструкторе на этапе выполнения программы обнаружит неправильную дату ((12,24,2007
) — 2007-й день 24-го месяца 12-го года).
Определение объекта last
содержит в скобках сразу после имени переменной начальное значение — аргументы, требуемые конструктором класса Date
. Этот стиль инициализации переменных класса, имеющего конструктор с аргументами, является наиболее распространенным. Кроме того, можно использовать более многословный стиль, который позволяет явно продемонстрировать создание объекта (в данном случае Date(1976,12,24)
) с последующей инициализацией с помощью синтаксиса инициализации =
. Если вы действительно пишете в таком стиле, то скоро устанете от него.
Теперь можно попробовать использовать вновь определенные переменные.
last.add_day(1);
add_day(2); // ошибка: какой объект типа Date?
Обратите внимание на то, что функция-член add_day()
вызывается из конкретного объекта типа Date
с помощью точки, означающей обращение к члену класса. Как определить функцию-член класса, показано в разделе 9.4.4.
9.4.3. Скрываем детали
Остается одна проблема: что произойдет, если мы забудем использовать функцию-член add_day()
? Что произойдет, если кто-то решит непосредственно изменить месяц? Оказывается, мы забыли предусмотреть возможности для выполнения этой операции.
Date birthday(1960,12,31); // 31 декабря 1960 года