суббота, 5 декабря 2009 г.

6.5. Использование абстрактного типа данных Time с помощью класса

Классы предоставляют программисту возможность моделировать объек­ты, которые имеют атрибуты (представленные как данные-элементы) и ва­рианты поведения или операции (представленные как функции-элементы). Типы, содержащие данные-элементы и функции-элементы, обычно опреде­ляются в С++ с помощью ключевого слова class.
Функции-элементы иногда в других объектно-ориентированных языках называют методами, они вызываются в ответ на сообщения, посылаемые объекту. Сообщение соответствует вызову функции-элемента.
Когда класс определен, имя класса может быть использовано для объ­явления объекта этого класса. Рис. 6,2 содержит простое определение класса Time.
Определение нашего класса Time начинается с ключевого слова class. Тело определения класса заключается в фигурные скобки ({ }). Определение класса заканчивается точкой с запятой. Определение нашего класса Time, как и нашей структуры Time, содержит три целых элемента hour, minute и second.
Забывается точка с запятой в конце определения класса (или структуры).
class Time { public: Time();
void setTime(int, int, int) ; void printMilitary(); void printStandatd() ; private:
int hour; // 0-23 int minute; // 0 -59 int second; // 0-59 } ;
Рис. 6.2. Простое определение классЗЧте
Остальные части определения класса — новые. Метки public: (открытая) и private: (закрытая ) называются спецификаторами доступа к элементам. Любые данные-элементы и функции-элементы, объявленные после специфи­катора доступа к элементам public: (и до следующего спецификатора доступа к элементам), доступны при любом обращении программы к объекту класса Time. Любые данные-элементы и функции-элементы, объявленные после спе­цификатора доступа к элементам private: (и до следующего спецификатора доступа к элементам), доступны только функциям-элементам этого класса. Спецификаторы доступа к элементам всегда заканчиваются двоеточием (:) и могут появляться в определении класса много раз и в любом порядке. В дальнейшем в тексте нашей книги мы будем использовать записи специфи­каторов доступа к элементам в виде public и private (без двоеточия).
Используйте при определении класса каждый спецификатор доступа к элементам только один раз, что сделает программу более ясной и простой для чтения. Разме­щайте первыми элементы public, являющиеся общедоступными.
Определение класса в нашей программе содержит после спецификатора доступа к элементам public прототипы следующих четырех функций-элемен­тов: Time, setTime, printMilitary и printStandard. Это — открытые функ­ции-элементы или открытый интерфейс услуг класса. Эти функции будут использоваться клиентами класса (т.е. частями программы, играющими роль пользователей) для манипуляций с данными этого класса.
Обратите внимание на функцию-элемент с тем же именем, что и класс. Она называется конструктором этого класса. Конструктор — это специаль­ная функция-элемент, которая инициализирует данные-элементы объекта этого класса. Конструктор класса вызывается автоматически при создании объекта этого класса. Мы увидим, что обычно класс имеет несколько кон­структоров; это достигается посредством перегрузки функции.
После спецификатора доступа к элементам private следуют три целых элемента. Это говорит о том, что эти данные-элементы класса являются до­ступными только функциям-элементам класса и, как мы увидим в следующей главе, «друзьям» класса. Таким образом, данные-элементы могут быть до­ступны только четырем функциям, прототипы которых включены в опреде­ление этого класса (или друзей этого класса). Обычно данные -элементы пере­числяются в части private, а функции-элементы — в части public. Как мы увидим далее, можно иметь функции-элементы private и данные public; пос­леднее не типично и считается в программировании дурным вкусом.
Когда класс определен, его можно использовать в качестве типа в объ­явлениях, например, следующим образом:
Time sunset,             // объект типа Time
arrayOfTimes[5],         // массив объектов типа Time
*pointerToTime,          // указатель на объект типа Time
SdinnerTime = sunset;    //ссылка на объект типа Time
Имя класса становится новым спецификатором типа. Может существо­вать множество объектов класса как и множество переменных типа, напри­мер, такого, как int. Программист по мере необходимости может создавать новые типы классов. Это одна из многих причин, по которым С++ является расширяемым языком.
Программа на рис. 6.3 использует класс Time. Эта программа создает единственный объект класса Time, названный t. Когда объект создается, авто­матически вызывается конструктор Time, который явно присваивает нулевые начальные значения всем данным-элементам закрытой части private. Затем печатается время в военном и стандартном форматах, чтобы подтвердить, что элементы получили правильные начальные значения. После этого с по­мощью функции-элемента setTime устанавливается время и оно снова печа­тается в обоих форматах. Затем функция-элемент setTime пытается дать дан­ным-элементам неправильные значения и время снова печатается в обоих форматах.
Снова отметим, что данные-элементы hour, minute и second предваряются спецификатором доступа к элементам private. Эти закрытые данные-элемен­ты класса обычно недоступны вне класса (но, как мы увидим в главе 7, друзья класса могут иметь доступ к закрытым элементам класса.) Глубокий смысл такого подхода заключается в том, что истинное представление данных внутри класса не касается клиентов класса. Например, было бы вполне воз­можно изменить внутреннее представление и представлять, например, время внутри класса как число секунд после полуночи. Клиенты могли бы исполь­зовать те же самые открытые функции-элементы и получать те же самые результаты, даже не осознавая произведенных изменений. В этом смысле, говорят, что реализация класса скрыта от клиентов. Такое скрытие инфор­мации способствует модифицируемости программ и упрощает восприятие класса клиентами.

II FIG6_3.CPP // Класс Time, iinclude

// Определение абстрактного типа данных (АТД) Time class Tlme{ public:
Timet);              // конструктор
void setTime(int, int, int)
// установка часов, минут //и секунд
void printMilitary(); void printStandard();
// времени в военном формате
// печать времени
//в стандартном формате
private:
int
hour;
//
0 -
- 23
int
minute;
//
0 -
- 59
int
second;
II
0 -
- 59

// Конструктор Time присваивает нулевые начальные значения // каждому элементу данных. Обеспечивает согласованное // начальное состояние всех объектов Time Time::Time О     { hour = minute = second =0; }
II Задание нового значения Time в виде военного времени. // Проверка правильности значений данных. // Обнуление неверных значений, void Time::setTime(int h, int m, int s)
{
hour = (h >= 0 && h < 24) ? h : 0; minute = (m >= 0 && m < 60) ? m : 0; second = (s >= 0 && s < 60) ?s : 0;
}
// Печать времени в военном формате
void Time:: printMilitary ( ) {
cout « (hour < 10 ? "0" :"") « hour« ":"
« (minute < 10 ? "0": "") « minute « ":" « (second < 10 ? "0" : "") « second;
}
// Печать времени в стандартном формате
void Time:: printStandard() {
cout « ( (hour == 0 || hour ==12) ? 12 : hour % 12) «":" « (minute < 10 ? "0": "") « minute <<":" « (second < 10 ? "0" : "") « second « (hour < 12 ? " AM" : " PM");
}
Рис. 6.3. Использование абстрактного типа данных Time как класса (часть 1 из 2)

II Формирование проверки простого класса Time
main() {
Time t;     // определение экземпляра объекта t класса Time
cout « "Начальное значение военного времени равно "; t.printMilitary(); cout << endl
<< "Начальное значение стандартного времени равно "; t.printStandard();
t.setTime(13, 27, 6);
cout « endl << endl << "Военное время после setTime равно ' t.printMilitary();
Л
cout << endl << "Стандартное время после setTime равно "; t.printStandard();
t.setTime(99, 99, 99);      // попытка установить
// неправильные значения
cout << endl << endl
<< "После попытки неправильной установки: " « endl « "Военное время: "; t.printMilitary();
cout << endl << "Стандартное время: "; t.printStandard(); cout << endl; return 0;
}
Начальное значение военного времени равно 00:00:00 Начальное значение стандартного времени равно 12:00:00 AM
Военное время после setTime равно 13:27:06 Стандартное время после setTime равно 1:27:06 РМ
После попытки неправильной установки: Военное время: 00:00:00 Стандартное время: 12:00:00 AM
Рис. 6.3. Использование абстрактного типа данных Time как класса (часть 2 из 2)

Замечание по технике программирования 6.2
Клиенты класса используют класс, не зная внутренних деталей его реализации. Если реализация класса изменяется (например, с целью улучшения производительности), интерфейс класса остается неизменным и исходный код клиента класса не требует изменений. Это значительно упрощает модификацию систем.
В нашей программе конструктор Time просто присваивает начальные зна­чение, равные 0, данным-элементам, (т.е. задает военное время, эквивалент­ное 12 AM). Это гарантирует, что объект при его создании находится в из­вестном состоянии. Неправильные значения не могут храниться в данных-элементах объекта типа Time, поскольку конструктор автоматически вызывается при создании объекта типа Time, а все последующие попытки изменить данные-элементы тщательно рассматриваются функцией setTime.

Замечание по технике программирования 6.3
Функции-элементы обычно короче, чем обычные функции в программах без объ­ектной ориентации, потому что достоверность данных, хранимых в данных-элементах, идеально проверена конструктором и функциями-элементами, которые сохраняют новые данные.
Отметим, что данные-элементы класса не могут получать начальные зна­чения в теле класса, где они объявляются. Эти данные-элементы должны получать начальные значения с помощью конструктора класса или им можно присваивать значения через функции.
Типичная ошибка программирования 6.2
Попытка явно присвоить начальное значение данным-элементам в определении класса.
Функция с тем же именем, что и класс, но со стоящим перед ней сим­волом тильда (~), называется деструктором этого класса (наш пример не включает деструктор). Деструктор производит «завершающие служебные дей­ствия» над каждым объектом класса перед тем, как память, отведенная под этот объект, будет повторно использована системой. Подробнее мы обсудим конструкторы и деструкторы позже в этой главе и в главе 7.
Заметим, что функции, которыми класс снабжает внешний мир, пред­варяются меткой public. Открытые функции реализуют все возможности класса, необходимые для его клиентов. Открытые функции класса называют интерфейсом класса или открытым интерфейсом.
Замечание по технике программирования 6.4
Клиенты имеют доступ к интерфейсу класса, но не имеют доступа к реализации класса.
Объявление класса содержит объявления данных-элементов и функций- элементов класса. Объявления функций-элементов являются прототипами функций, которые мы обсуждали в предыдущих главах. Функции-элементы могут быть описаны внутри класса, но хороший стиль программирования заключается в описании функций вне определения класса.
Замечание по технике программирования 6.5
Объявление функций-элементов внутри определения класса и описание функций- элементов, вне этого определения отделяет интерфейс класса от его реализации. Это способствует высокому качеству разработки программного обеспечения.

Отметим использование бинарной операции разрешения области дейст­вия (::) в каждом определении функции-элемента, следующем за определе­нием класса на рис. 6.3. После того, как класс определен и его функции- элементы объявлены, эти функции-элементы должны быть описаны. Каждая функция-элемент может быть описана прямо в теле класса (вместо включение прототипа функции класса) или после тела класса. Когда функция-элемент описывается после соответствующего определения класса, имя функции пред­варяется именем класса и бинарной операцией разрешения области дейст- вия (::).Поскольку разные классы могут иметь элементы с одинаковыми име­нами, операция разрешения области действия «привязывает» имя элемента к имени класса, чтобы однозначно идентифицировать функции-элементы дан­ного класса.
Несмотря на то, что функция-элемент, объявленная в определении класса, может быть описана вне этого определения, эта функция-элемент все равно имеет областью действия класс, т.е. ее имя известно только другим эле­ментам класса пока к ней обращаются посредством объекта класса, ссылки на объект класса или указателя на объект класса. Об области действия класса мы более подробно еще поговорим позднее.
Если функция-элемент описана в определении класса, она автоматически встраивается inline. Функция-элемент, описанная вне определения класса, может быть сделана встраиваемой посредством явного использования клю­чевого слова inline. Напомним, что компилятор резервирует за собой право не встраивать никаких функций.
Совет по повышению эффективности 6.3
Описание небольших функций-элементов внутри определения класса автоматически встраивает функцию-элемент inline (если компилятор решит делать это). Это может улучшить производительность, но не способствует улучшению качества проектиро­вания программного обеспечения.
Интересно, что функции-элементы printMilitary и printStandard не по­лучают никаких аргументов. Это происходит потому, что функции-элементы неявно знают, что они печатают данные-элементы определенного объекта типа Time, для которого они активизированы. Это делает вызовы функций-эле­ментов более краткими, чем соответствующие вызовы функций в процедур­ном программировании. Это уменьшает также вероятность передачи неверных аргументов, неверных типов аргументов или неверного количества аргумен­тов.
Замечание по технике программирования 6.6
Использование принципов объектно-ориентированного программирования часто может упростить вызовы функции за счет уменьшения числа передаваемых пара­метров. Это достоинство объектно-ориентированного программирования проистекает из того факта, что инкапсуляция данных-элементов и функций-элементов внутри объекта дает функциям-элементам право прямого доступа к данным-элементам.
Классы упрощают программирование, потому что клиент (или пользова­тель объекта класса) имеет дело только с операциями, инкапсулированными или встроенными в объект. Такие операции обычно проектируются ориен­тированными именно на клиента, а не на удобную реализацию. Клиентам нет необходимости касаться реализации класса. Интерфейсы меняются, но не так часто, как реализации. При изменении реализации соответственно должны изменяться ориентированные на реализацию коды. Л путем скрытия реализации мы исключаем возможность для других частей программы ока­заться зависимыми от особенностей реализации класса.
Часто классы не создаются «на пустом месте». Обычно они являются производными от других классов, обеспечивающих новые классы необходи­мыми им операциями. Или классы могут включать объекты других классов как элементы. Такое повторное использование программного обеспечения зна­чительно увеличивает производительность программиста. Создание новых классов на основе уже существующих классов называется наследованием и подробно обсуждается в главе 9. Включение классов как элементов других классов называется композицией и обсуждается в главе 7.

Комментариев нет:

Отправить комментарий