Классы предоставляют программисту возможность моделировать объекты, которые имеют атрибуты (представленные как данные-элементы) и варианты поведения или операции (представленные как функции-элементы). Типы, содержащие данные-элементы и функции-элементы, обычно определяются в С++ с помощью ключевого слова 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:
|
// Конструктор 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.
Комментариев нет:
Отправить комментарий