, которая проверяет корректность его объектов. Однако функции проверки приносят мало пользы, если мы не можем их использовать. Например, допустим, что мы определили для типа
Date
оператор вывода
<<
(раздел 9.8):
void f
{
Date today;
// ...
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 дней
};
Функция-член, имя которой совпадает с именем класса, является особой. Она называется конструктором (constructor) и используется для инициализации (конструирования) объектов класса. Если программист забудет проинициализировать объект класса, имеющего конструктор с аргументом, то компилятор выдаст сообщение об ошибке. Для такой инициализации существует специальная синтаксическая конструкция.
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 года
++birthday.d; // Ой! Неправильная дата
Date today(1970,2,3);
today.m = 14; // Ой! Неправильная дата
// today.m == 14
Поскольку мы хотим сделать представление типа
Date
доступным для всех, кто-нибудь — вольно или невольно — может сделать ошибку; иначе говоря, сделать нечто, что приведет к созданию неправильной даты. В данном случае мы создали объект типа
Date
со значением, которое не соответствует календарю. Такие неправильные объекты являются минами с часовым механизмом; через какое-то время кто-нибудь, не ведая того, обязательно воспользуется некорректным значением и получит сообщение об ошибке на этапе выполнения программы или — что еще хуже — получит неверные результаты. Все это лишь вопрос времени.