Литмир - Электронная Библиотека
A
A

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

 

Программирование. Принципы и практика использования C++ Исправленное издание - _003.png
 Поскольку мы хотим сделать представление типа
Date
доступным для всех, кто-нибудь — вольно или невольно — может сделать ошибку; иначе говоря, сделать нечто, что приведет к созданию неправильной даты. В данном случае мы создали объект типа
Date
со значением, которое не соответствует календарю. Такие неправильные объекты являются минами с часовым механизмом; через какое-то время кто-нибудь, не ведая того, обязательно воспользуется некорректным значением и получит сообщение об ошибке на этапе выполнения программы или — что еще хуже — получит неверные результаты. Все это лишь вопрос времени.

Такие размышления приводят к выводу, что представление типа

Date
, за исключением открытых функций-членов, должно быть недоступным для пользователей. Итак, получаем первое сокращение.

// простой типа Date (управление доступом)

126
{"b":"847443","o":1}