Чтение онлайн

ЖАНРЫ

Программирование. Принципы и практика использования C++ Исправленное издание
Шрифт:

int d; // день месяца

};

Date today; // переменная типа Date (именованный объект)

Объект типа

Date
, например
today
, может просто состоять из трех чисел типа
int
.

В данном случае нет необходимости скрывать данные, на которых основана структура

Date
, — это предположение будет использовано во всех вариантах этой структуры на протяжении всей главы. Итак, теперь у нас есть объекты типа
Date;
что с ними можно делать? Все что угодно, в том смысле, что мы можем получить доступ ко всем членам объекта
today
(и другим объектам типа
Date
), а также читать и записывать их по своему усмотрению. Загвоздка заключается в том, что все это не совсем удобно. Все, что мы хотим делать с объектами типа
Date
, можно выразить через чтение и запись их членов. Рассмотрим пример.

// установить текущую дату 24 декабря 2005 года

today.y = 2005;

today.m = 24;

today.d = 12;

Этот способ утомителен и уязвим для ошибок. Вы заметили ошибку? Все, что является утомительным, уязвимо для ошибок! Например, ответьте, имеет ли смысл следующий код?

Date x;

x.y = –3;

x.m = 13;

x.d = 32;

Вероятно нет, и никто не стал бы писать такую чушь — или стал? А что вы скажете о таком коде?

Date y;

y.y = 2000;

y.m = 2;

y.d = 29;

Был ли двухтысячный год високосным? Вы уверены?

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

Date
к общим операциям относится также увеличение значения объекта
Date
. Итак, напишем следующий код:

// вспомогательные функции:

void init_day(Date& dd, int y, int m, int d)

{

// проверяет, является ли (y,m,d) правильной датой

// если да, то инициализирует объект dd

}

void add_day(Date& dd, int n)

{

// увеличивает объект dd на n дней

}

Попробуем использовать объект типа

Date
.

void f

{

Date today;

init_day(today, 12, 24, 2005); // Ой! (в 12-м году не было

// 2005-го дня)

add_day(today,1);

}

Во-первых, отметим полезность таких “операций” — здесь они реализованы в виде вспомогательных функций. Проверка корректности даты довольно сложна и утомительна, поэтому, если бы мы не написали соответствующую функцию раз и навсегда, то скорее всего пропустили бы этот код и получили неправильную программу. Если мы определяем тип, то всегда хотим выполнять над его объектами какие-то операции. Точное количество и вид этих операций может изменяться. Точный вид реализации этих операций (в виде функций, функций-членов или операторов) также изменяется, но как только мы решили создать собственный тип, мы должны спросить себя: “Какие операции с этим типом можно выполнять?”

9.4.2. Функции-члены и конструкторы

Мы

предусмотрели функцию инициализации для типа
Date
, которая проверяет корректность его объектов. Однако функции проверки приносят мало пользы, если мы не можем их использовать. Например, допустим, что мы определили для типа
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-го года).

Поделиться с друзьями: