Читаем Программирование полностью

Структуры (struct) в основном используются для организации данных, члены которых могут принимать любые значения; иначе говоря, мы не можем определить для них никакого осмысленного инварианта (раздел 9.4.3).

<p id="AutBody_Root158"><strong>9.4. Разработка класса</strong></p>

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

Рассмотрим вполне тривиальную задачу: представить календарную дату (например, 14 августа 1954 года) в программе. Даты нужны во многих программах (для проведения коммерческих операций, описания погодных данных, календаря, рабочих записей, ведомостей и т.д.). Остается только вопрос: как это сделать? 

<p id="AutBody_Root159"><strong>9.4.1. Структуры и функции</strong></p>

 Как можно представить дату? На этот вопрос большинство людей отвечают: “Указать год, месяц и день месяца”. Это не единственный и далеко не лучший ответ, но для наших целей он вполне подходит. Для начала попробуем создать простую структуру.

// простая структура Date (слишком просто?)

struct Date {

  int y; // год

  int m; // месяц года

  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);

}

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

<p id="AutBody_Root160"><strong>9.4.2. Функции-члены и конструкторы</strong></p>

Мы предусмотрели функцию инициализации для типа Date, которая проверяет корректность его объектов. Однако функции проверки приносят мало пользы, если мы не можем их использовать. Например, допустим, что мы определили для типа Date оператор вывода << (раздел 9.8):

void f()

{

  Date today;

  // ...

Перейти на страницу:
Нет соединения с сервером, попробуйте зайти чуть позже