Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Конспект_ООП_КЛАССЫ.doc
Скачиваний:
9
Добавлен:
12.02.2016
Размер:
10.12 Mб
Скачать

Министерство образования и науки Украины

Национальная металлургическая академия Украины

Кафедра автоматизации производственных процессов

КОНСПЕКТ ЛЕКЦИЙ

по дисциплине «Объектно-ориентированное программирование»

ЧАСТЬ 2

«Основы ООП: классы, объекты, перегрузка операций,

наследование, виртуальные функции, полиморфизм»

для студентов направления 6.0925 – “Автоматизация и компьютерно-интегрированные технологии”

Днепропетровск 2008

ОГЛАВЛЕНИЕ

1. КЛАССЫ И АБСТРАГИРОВАНИЕ ДАННЫХ 3

1.1. Принципы объектной ориентации 3

1.2. Определения структур 4

1.3. Доступ к элементам структуры 4

1.4. Использование определенного пользователем типа Time с помощью Struct 5

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

1.6. Область действия класс и доступ к элементам класса 12

1.7. Отделение интерфейса от реализации 14

1.8. Управление доступом к элементам 17

1.9. Функции доступа и обслуживающие функции-утилиты 19

1.10. Инициализация объектов класса: конструкторы 21

1.11. Использование конструкторов с аргументами по умолчанию 22

1.12. Использование деструкторов 25

Вопросы для самопроверки 26

2. КЛАССЫ: КОМПОЗИЦИЯ И ДИНАМИЧЕСКОЕ УПРАВЛЕНИЕ ОБЪЕКТАМИ 27

2.1. Константные объекты и функции-элементы 27

2.2. Композиция: классы как элементы других классов 32

2.3. Дружественные функции и дружественные классы 36

2.4. Использование указателя this 37

2.5. Динамическое распределение памяти с помощью операций new и delete 42

2.6. Статические элементы класса 43

2.7. Абстракция данных и скрытие информации 47

2.8. Классы контейнеры и итераторы 49

Вопросы для самопроверки 49

3. ПЕРЕГРУЗКА ОПЕРАЦИЙ 51

3.1. Основы перегрузки операций 51

3.2. Ограничения на перегрузку операций 52

3.3. Функции-операции как элементы класса и как дружественные функции 53

3.4. Перегрузка операций поместить в поток и взять из потока 54

3.5. Перегрузка унарных операций 57

3.6. Перегрузка бинарных операций 57

3.7. Пример: класс массив 58

3.8. Преобразования типов 69

1. КЛАССЫ И АБСТРАГИРОВАНИЕ ДАННЫХ

1.1. Принципы объектной ориентации

Начнем знакомство с объектной ориентацией в С++. Техника объектно-ориентированного проектирования предполагает следующие основные этапы:

  • выполнение постановки задачи для построения модели или системы;

  • определение объектов, необходимых для реализации системы;

  • определение атрибутов для каждого объекта;

  • определение вариантов поведения каждого объекта;

  • указание способов взаимодействия объектов друг с другом для достижения глобальной цели системы.

Ключевым принципом объектной ориентации является инкапсуляция данных (атрибутов) и функций (вариантов поведения), в совокупности называемых объектами. Объекты обладают свойством скрытия информации. Это значит, что хотя объекты могут знать, как связываться друг с другом посредством хорошо определенного интерфейса, им обычно не позволено знать, как реализуются другие объекты, – детали реализации спрятаны внутри самих объектов. Так, можно ездить на автомобиле, не зная технических деталей его внутреннего функционирования – трансмиссии, выхлопной трубы и др.

В С и других процедурно-ориентированных языках программирование стремится быть ориентированным на действия, тогда как в идеале программирование на С++ объектно-ориентированное. В С единицей программирования является функция. В С++ единицей программирования является класс, на основе которого, в конечном счете, создаются объекты.

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

Программисты на С++ основное внимание уделяют созданию своих соб­ственных определяемых пользователем типов, называемых классами. Клас­сы –это типы, определяемые программистом. Каждый класс содержит дан­ные и набор функций, манипулирующих с этими данными. Компоненты-данные класса называются данными-элементами. Компоненты-функции класса называются функциями-элементами. Подобно тому, как сущ­ность встроенного типа, такого, как int, называется переменной, сущность определяемого пользователем типа (т.е. класса) называется объектом. Цент­ром внимания в С++ являются не функции, а объекты. Имена существи­тельные в описании проектируемой системы помогают программисту на С++ определить множество классов. Эти классы используются для создания объ­ектов, которые будут совместно работать для реализации системы.

Классы в С++ являются естественным продолжением структуры struct в С. Прежде чем рассматривать специфику разработки классов на С++, мы обсудим структуры и построим определенный пользователем тип, основанный на структуре. Слабости, которые мы выявим в этом подходе, помогут объяснить запись класса.

1.2. Определения структур

Структуры – это составные типы данных, построенные с использованием других типов. Рассмотрим следующее определение структуры:

Ключевое слово struct начинает определение структуры. Идентификатор Time – тег (обозначение, имя-этикетка) структуры. Тэг структуры исполь­зуется при объявлении переменных структур данного типа. В этом примере имя нового типа — Time. Имена, объявленные в фигурных скобках описания структуры – это элементы структуры. Элементы одной и той же структуры должны иметь уникальные имена, но две разные структуры могут содержать не конфликтующие элементы с одинаковыми именами. Каждое определение структуры должно заканчиваться точкой с запятой. Приведенное объяснение верно и для классов.

Определение Time содержит три элемента типа inthour, minute и second (часы, минуты и секунды). Элементы структуры могут быть любого типа и одна структура может содержать элементы многих разных типов. Структура не может, однако, содержать экземпляры самой себя. Например, элемент типа Time не может быть объявлен в определении структуры Time. Однако, может быть включен указатель на другую структуру Time. Структура, содержащая элемент, который является указателем на такой же структурный тип, называ­ется структурой с самоадресацией. Структуры с самоадресацией полезны для формирования связных структур данных.

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

объявляет timeObject переменной типа Time, timeArray – массивом с 10 элементами типа Time, а timePtr – указателем на объект типа Time.

1.3. Доступ к элементам структуры

Для доступа к элементам структуры (или класса) используются операции доступа к элементам — операция точка (.) и операция стрелка (->). Операция точка обращается к элементу структуры (или класса) по имени переменной объекта или по ссылке на объект. Например, чтобы напечатать элемент hour структуры timeObiect используется оператор

cout << timeObject.hour;

Операция стрелка, состоящая из знака минус (-) и знака больше (>), записанных без пробела, обеспечивает доступ к элементу структуры (или класса) через указатель на объект. Допустим, что указатель timePtr был уже объявлен как указывающий на объект типа Time и что адрес структуры timeObject был уже присвоен timePtr. Тогда, чтобы напечатать элемент hour структуры timeObject с указателем timePtr, можно использовать оператор

cout << timePtr->hour;

Выражение timePtr->hour; эквивалентно (*timePtr).hour, которое разы­меновывает указатель и делает доступным элемент hour через операцию точка. Скобки нужны здесь потому, что операция точка имеет более высокий приоритет, чем операция разыменования указателя (*). Операции стрелка и точка наряду с круглыми и квадратными скобками имеют второй наивысший приоритет (после операции разрешения области действия) и ассоциативность слева направо.

1.4. Использование определенного пользователем типа Time с помощью Struct

Программа на рис. 1.1 создает определенный пользователем тип структуры Time с тремя целыми элементами: hour, minute и second. Программа опреде­ляет единственную структуру типа Time, названную dinnerTime, и использует операцию точка для присвоения элементам структуры начальных значений 18 для hour, 30 для minute и 0 для second. Затем программа печатает время в военном (24-часовом) и стандартном (12-часовом) форматах. Заметим, что функции печати принимают ссылки на постоянные структуры типа Time. Это является причиной того, что структуры Time передаются печатающим функ­циям по ссылке – этим исключаются накладные расходы на копирование, связанные с передачей структур функциям по значению, а использование const предотвращает изменение структуры типа Time функциями печати.

Существуют препятствия созданию новых типов данных указанным спо­собом с помощью структур. Поскольку инициализация структур специально не требуется, можно иметь данные без начальных значений и вытекающие отсюда проблемы. Даже если данные получили начальные значения, воз­можно, это было сделано неверно. Неправильные значения могут быть при­своены элементам структуры (как мы сделали на рис.1.1), потому что про­грамма имеет прямой доступ к данным. Программа присвоила плохие значения всем трем элементам объекта dinnerTime типа Time. Если реали­зация struct изменится (например, время теперь будет представляется как число секунд после полуночи), то все программы, которые используют struct, нужно будет изменить. Не существует никакого «интерфейса», гарантирую­щего, что программист правильно использует тип данных, и что данные яв­ляются непротиворечивыми.

Результат выполнения программы:

Рисунок 1.1. Создание структуры, задание и печать ее элементов

Используем далее уже известную структуру Time, но уже как класс, и продемонстрируем некоторые преимущества создания таких так называемых абстрактных типов данных, как классы. Классы и структуры в С++ можно использовать почти одинаково. Различие между ними состоит в доступности по умолчанию элементов каждого из этих типов.

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

Классы предоставляют программисту возможность моделировать объек­ты, которые имеют атрибуты (представленные как данные-элементы) и варианты поведения или операции (представленные как функции-элементы). Типы, содержащие данные-элементы и функции-элементы, обычно опреде­ляются в C++ с помощью ключевого слова class.

Функции-элементы иногда в других объектно-ориентированных языках называют методами, они вызываются в ответ на сообщения, посылаемые объекту. Сообщение соответствует вызову функции-элемента.

Когда класс определен, имя класса может быть использовано для объ­явления объекта этого класса. Рис.1.2 содержит простое определение класса Time.

Определение нашего класса Time начинается с ключевого слова class. Тело определения класса заключается в фигурные скобки ({ }). Определение класса заканчивается точкой с запятой. Определение нашего класса Time, как и нашей структуры Time, содержит три целых элемента hour, minute и second.

Рисунок 1.2. Простое определение класса Time

Остальные части определения класса – новые. Метки public: (открытая) и private: (закрытая) называются спецификаторами доступа к элементам. Любые данные-элементы и функции-элементы, объявленные после специфи­катора доступа к элементам public: (и до следующего спецификатора доступа к элементам), доступны при любом обращении программы к объекту класса Time. Любые данные-элементы и функции-элементы, объявленные после спе­цификатора доступа к элементам private: (и до следующего спецификатора доступа к элементам), доступны только функциям-элементам этого класса. Спецификаторы доступа к элементам всегда заканчиваются двоеточием (:) и могут появляться в определении класса много раз и в любом порядке.

Определение класса в нашей программе содержит после спецификатора доступа к элементам public прототипы следующих четырех функций-элемен­тов: Time, setTime, printMilitary и printStandard. Это – открытые функ­ции-элементы или открытый интерфейс услуг класса. Эти функции будут использоваться клиентами класса (т.е. частями программы, играющими роль пользователей) для манипуляций с данными этого класса.

Функция-элемент с тем же именем, что и класс называется конструктором этого класса. Конструктор – это специаль­ная функция-элемент, которая инициализирует данные-элементы объекта этого класса. Конструктор класса вызывается автоматически при создании объекта этого класса. Обычно класс имеет несколько кон­структоров; это достигается посредством перегрузки функции.

После спецификатора доступа к элементам private следуют три целых элемента. Это говорит о том, что эти данные-элементы класса являются до­ступными только функциям-элементам класса и «друзьям» класса. Таким образом, данные-элементы могут быть до­ступны только четырем функциям, прототипы которых включены в опреде­ление этого класса (или друзей этого класса). Обычно данные-элементы пере­числяются в части private, а функции-элементы – в части public. Как мы увидим далее, можно иметь функции-элементы private и данные public; пос­леднее не типично и считается в программировании дурным вкусом.

Когда класс определен, его можно использовать в качестве типа в объ­явлениях, например, следующим образом:

Имя класса становится новым спецификатором типа. Может существо­вать множество объектов класса как и множество переменных типа, напри­мер, такого, как int. Программист по мере необходимости может создавать новые тины классов. Это одна из многих причин, по которым C++ является расширяемым языком.

Программа на рис.1.3 использует класс Time. Эта программа создает единственный объект класса Time, названный t. Когда объект создается, авто­матически вызывается конструктор Time, который явно присваивает нулевые начальные значения всем данным-элементам закрытой части private. Затем печатается время в военном и стандартном форматах, чтобы подтвердить, что элементы получили правильные начальные значения. После этого с по­мощью функции-элемента setTime устанавливается время, и оно снова печа­тается в обоих форматах. Затем функция-элемент setTime пытается дать дан­ным-элементам неправильные значения, и время снова печатается в обоих форматах.

Данные-элементы hour, minute и second предваряются спецификатором доступа к элементам private. Эти закрытые данные-элемен­ты класса обычно недоступны вне класса (друзья класса могут иметь доступ к закрытым элементам класса.) Глубокий смысл такого подхода заключается в том, что истинное представление данных внутри класса не касается клиентов класса. Например, было бы вполне воз­можно изменить внутреннее представление и представлять, например, время внутри класса как число секунд после полуночи. Клиенты могли бы исполь­зовать те же самые открытые функции-элементы и получать те же самые результаты, даже не осознавая произведенных изменений. В этом смысле, говорят, что реализация класса скрыта от клиентов. Такое скрытие информации способствует модифицируемости многих программ и упрощает восприятие класса клиентами.

Результат выполнения программы:

Рисунок 1.3. Использование АТД Timeкак класса

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

Данные-элементы класса не могут получать начальные зна­чения в теле класса, где они объявляются. Эти данные-элементы должны получать начальные значения с помощью конструктора класса или им можно присваивать значения через функции.

Функция с тем же именем, что и класс, но со стоящим перед ней сим­волом тильда (~) называется деструктором этого класса (пример на рис.1.3 не включает деструктор). Деструктор производит «завершающие служебные дей­ствия» над каждым объектом класса перед тем, как память, отведенная под этот объект, будет повторно использована системой.

Функции, которыми класс снабжает внешний мир, пред­варяются меткой public. Открытые функции реализуют все возможности класса, необходимые для его клиентов. Открытые функции класса называют интерфейсом класса или открытым интерфейсом.

Отметим использование бинарной операции разрешения области дейст­вия (::) в каждом определении функции-элемента, следующем за определе­нием класса на рис.1.3. После того, как класс определен, и его функции-элементы объявлены, эти функции-элементы должны быть описаны. Каждая функция-элемент может быть описана прямо в теле класса (вместо включения прототипа функции класса) или после тела класса. Когда функция-элемент описывается после соответствующего определения класса, имя функции пред­варяется именем класса и бинарной операцией разрешения области действия (::) Поскольку разные классы могут иметь элементы с одинаковыми име­нами, операция разрешения области действия «привязывает» имя элемента к имени класса, чтобы однозначно идентифицировать функции-элементы дан­ного класса.

Несмотря на то, что функция-элемент, объявленная в определении класса, может быть описана вне этого определения, эта функция-элемент все равно имеет областью действия класс, т.е. ее имя известно только другим эле­ментам класса пока к ней обращаются посредством объекта класса, ссылки на объект класса или указателя на объект класса.

Если функция-элемент описана в определении класса, она автоматически встраивается inline. Функция-элемент, описанная вне определения класса, может быть сделана встраиваемой посредством явного использования клю­чевого слова inline. Напомним, что компилятор резервирует за собой право не встраивать никаких функций.

Функции-элементы printMilitary и printStandard не по­лучают никаких аргументов. Это происходит потому, что функции-элементы неявно знают, что они печатают данные-элементы определенного объекта типа Time, для которого они активизированы. Это делает вызовы функций-эле­ментов более краткими, чем соответствующие вызовы функций в процедур­ном программировании. Это уменьшает также вероятность передачи неверных аргументов, неверных типов аргументов или неверного количества аргументов.

Классы упрощают программирование, потому что клиент (или пользова­тель объекта класса) имеет дело только с операциями, инкапсулированными или встроенными в объект. Такие операции обычно проектируются ориен­тированными именно на клиента, а не на удобную реализацию. Клиентам нет необходимости касаться реализации класса. Интерфейсы меняются, но не так часто, как реализации. При изменении реализации соответственно должны изменяться ориентированные на реализацию коды. А путем скрытия реализации мы исключаем возможность для других частей программы ока­заться зависимыми от особенностей реализации класса.

Часто классы не создаются «на пустом месте». Обычно они являются производными от других классов, обеспечивающих новые классы необходи­мыми им операциями. Или классы могут включать объекты других классов как элементы. Такое повторное использование программного обеспечения зна­чительно увеличивает производительность программиста. Создание новых классов на основе уже существующих классов называется наследованием. Включение классов как элементов других классов называется композицией.

1.6. Область действия класс и доступ к элементам класса

Данные-элементы класса (переменные, объявленные в определении клас­са) и функции-элементы (функции, объявленные в определении класса) имеют областью действия класс. Функции, не являющиеся элементом класса, имеют областью действия файл.

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

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

Функции-элементы имеют внутри класса область действия функцию: переменные, определенные в функции-элементе, известны только этой функ­ции. Если функция-элемент определяет переменную с тем же именем, что и переменная в области действия класс, последняя делается невидимой в области действия функция. Такая скрытая переменная может быть доступна посредством операции разрешения области действия с предшествующим этой операции именем класса. Невидимые глобальные переменные могут быть до­ступны с помощью унарной операции разрешения области действия.

Операции, использованные для доступа к элементам класса, аналогичны операциям, используемым для доступа к элементам структуры. Операция выбора элемента точка (.) комбинируется для доступа к элементам объекта с именем объекта или со ссылкой на объект. Операция выбора элемента стрелка (->) комбинируется для доступа к элементам объекта с указателем на объект.

Программа на рис.1.4 использует простой класс, названный Count, с открытым элементом данных х типа int и открытой функцией-элементом print, чтобы проиллюстрировать доступ к элементам класса с помощью опе­раций выбора элемента. Программа создает три экземпляра переменных типа Countcounter, counterRef (ссылка на объект типа Count) и counterPtr (указатель на объект типа Count). Переменная counterRef объявлена, чтобы ссылаться на counter, переменная counterPtr объявлена, чтобы указывать на counter. Важно отметить, что здесь элемент данных х сделан открытым просто для того, чтобы продемонстрировать способы доступа к открытым элементам.

Результат выполнения программы:

Рисунок 1.4. Доступ к данным-элементам объекта и функциям-элементам посредством имени объекта, ссылки и указателя на объект

1.7. Отделение интерфейса от реализации

Один из наиболее фундаментальных принципов разработки хорошего про­граммного обеспечения состоит в отделении интерфейса от реализации. Это облегчает модификацию программ. Что касается клиентов класса, то изме­нения в реализации класса не влияют на клиента до тех пор, пока интерфейс класса, изначально предназначенный для клиента, остается неизменным (функции класса можно расширять за пределы исходного интерфейса).

Для отделения интерфейса от реализации помещайте объявление класса в заголовочный файл, чтобы оно было доступно любому клиенту, который захочет использовать класс. Это формирует открытый ин­терфейс класса. Помещайте определения функций-элементов класса в исходный файл. Это формирует реализацию класса.

Это стимулирует независимых продавцов программного обеспечения по­ставлять библиотеки классов для продажи или лицензирования. Продавцы поставляют в своих продуктах только заголовочные файлы и объектные мо­дули. Не выдается никакой оригинальной, патентоспособной информации, как это было бы в случае поставки исходных кодов. Сообщество пользова­телей C++ извлекает выгоду, имея в своем распоряжении большинство биб­лиотек классов, поставляемых независимыми продавцами.

На самом деле все выглядит не в таком розовом свете. Заголовочные файлы содержат некоторую часть реализации и краткие сведения о других частях реализации. Встраиваемые функции-элементы, например, должны на­ходиться в заголовочном файле, так что когда компилятор компилирует кли­ента, клиент может включить определение встраиваемой функции inline. Закрытые элементы перечисляются в определении класса в заголовочном файле, так что эти элементы видимы для клиентов, несмотря на то, что клиенты не могут иметь к ним доступа.

Рисунок 5 разбивает программу на рис.1.3 на ряд файлов. При по­строении программы на C++ каждое определение класса обычно помещается в заголовочный файл, а определения функций-элементов этого класса поме­щаются в файлы исходных кодов с теми же базовыми именами. Заголовочные файлы включаются (посредством #include) в каждый файл, в котором ис­пользуется класс, а файлы с исходными кодами компилируются и компонуются с файлом, содержащим главную программу. Документация на используемый компилятор определяет как компилировать и компоновать программы, содержащие множество исходных файлов.

Программа на рис.1.5 состоит из заголовочного файла timel.h, в котором объявляется класс Time, файла timel.cpp, в котором описываются функции-элементы класса Time, и файла fig6_5.cpp, в котором описывается функция main. Выходные данные этой программы идентичны выходным данным про­граммы на рис.1.3.

//Заголовочный файл класса Time

//Исходный файл определений функций-элементов класса Time

//Программа драйвера класса Time

Результат выполнения программы:

Рисунок 1.5. Отделение интерфейса от реализации

Объявление класса заключено в следующие директивы пре­процессора:

// Предотвращение многократного включения заголовочного файла

#ifndef TIME1_H

#define TIME1_H

#endif

При построении больших программ в заголовочные файлы будут поме­щаться также и другие определения и объявления. Приведенные выше ди­рективы препроцессора предотвращают включение кода между #ifndef и #endif, если определено имя ТIМЕ1_Н. Если заголовок еще не включался в файл, то имя ТIМЕ1_Н определяется директивой #define и операторы заго­ловочного файла включаются в результирующий файл. Если же заголовок уже был включен ранее, ТIМЕ1_Н уже определен и операторы заголовочного файла повторно не включается. Попытки многократного включения заголо­вочного файла обычно случаются в больших программах с множеством за­головочных файлов, которые могут сами включать другие заголовочные файлы. Замечание: по негласному соглашению в приведенных выше дирек­тивах используется имя символической константы, представляющее собой просто имя заголовочного файла с символом подчеркивания вместо точки.

1.8. Управление доступом к элементам

Спецификаторы доступа к элементу public и private (а также, protected – защищенные) используются для управления доступом к данным-элементам класса и функциям-элемен­там. По умолчанию режим доступа для классов — private (закрытый), так что все элементы после заголовка класса и до первого спецификатора доступа являются закрытыми. После каждого спецификатора режим доступа, опре­деленный им, действует до следующего спецификатора или до завершающей правой скобки (}) определения класса. Спецификаторы private, public и pro­tected могут быть повторены, но такое употребление редко и может привести к беспорядку.

Закрытые элементы класса могут быть доступны только для функций-элементов (и дружественных функций) этого класса. Открытые элементы класса могут быть доступны для любых функций в программе.

Основная задача открытых элементов состоит в том, чтобы дать клиентам класса представление о возможностях (услугах), которые обеспечивает класс. Этот набор услуг составляет открытый интерфейс класса. Клиентов класса не должно касаться, каким образом класс выполняет их задачи. Закрытые элементы класса и описания открытых функций-элементов недоступны для клиентов класса. Эти компоненты составляют реализацию (implementation) класса.

Клиент класса может быть функцией-элементом другого класса или гло­бальной функцией. По умолчанию доступ к элементам класса — private. Доступ к элементам класса можно явно установить как public, protected или private. В отличие от этого доступ к элементам структуры struct по умолчанию — public. Доступ к элементам структуры struct также может быть установлен явно как public, protected или private.

Поскольку элементы класса по умолчанию являются закрытыми, никогда не возникает необходимости явно использовать спецификатор доступа к элементам private. Однако, многие программисты предпочитают сначала описывать интерфейс класса (т.е. открытые элементы класса), а затем перечислять закрытые элементы, откуда вытекает необходимость явного ис­пользования в определении класса спецификатора доступа к элементам pri­vate.

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

Доступ к закрытым данным класса должен тщательно контролироваться использованием функций-элементов, называемых функциями доступа. На­пример, чтобы разрешить клиентам прочитать закрытое значение данных, класс может иметь функцию «получить» («get»). Чтобы дать клиентам воз­можность изменять закрытые данные, класс может иметь функцию «уста­новить» («set»). Казалось бы, подобные изменения противоречат смыслу закрытых данных. Но функция-элемент set (установить) может обеспечить проверку правильности данных (например, проверку диапазона) и дать уверенность в том, что данные установлены верно. Функция set может также быть переводчиком между формой данных, используемой в интерфейсе, и формой, используемой в реализации. Функция get (получить) не требует представления данных в «сыром», необработанном виде; функция get может редактировать данные и ограничивать область данных, видимых клиенту.

1.9. Функции доступа и обслуживающие функции-утилиты

He все функции-элементы необходимо делать открытыми как часть интер­фейса класса. Некоторые функции-элементы оставляются закрытыми и слу­жат обслуживающими функциями-утилитами для других функций класса.

Функции доступа могут читать или отображать данные. Другим типич­ным применением функций доступа является проверка истинности или лож­ности условий — такие функции часто называют предикатными функциями. Примером предикатной функции могла бы быть функция isEmpty для любого класса контейнера – класса, способного содержать внутри себя много объ­ектов, например, связного списка, стека или очереди. Программа проверяла бы функцию isEmpty прежде, чем пытаться прочесть очередной элемент из объекта контейнера. Предикатная функция isFull могла бы проверять объект класса контейнер, чтобы выяснить, имеется ли в нем дополнительное пространство.

Рис.1.6 демонстрирует запись функции-утилиты. Функция-утилита не является частью интерфейса класса. Она является закрытой функцией-эле­ментом, которая поддерживает работу открытых функций-элементов класса. Функции-утилиты не предназначены для использования клиентами класса.

Класс Salesperson имеет массив, содержащий 12 сведений о месячных продажах, которым с помощью конструктора присвоены нулевые начальные значения и которым значения, задаваемые пользователем, устанавливаются с помощью функции setSales. Открытая функция-элемент printAnnualSales печатает сумму продаж за последние 12 месяцев. Функция-утилита totalAnnualSales суммирует сведения о продажах за 12 месяцев, обеспечивая работу printAnnualSales. Функция-элемент printAnnualSales редактирует сведения о продажах и переводит их в формат суммы долларов.

Результат выполнения программы:

Рисунок 1.6. Использование функции-утилиты

1.10. Инициализация объектов класса: конструкторы

После создания объекта его элементы могут быть инициализированы с помощью функции конструктор. Конструктор — это функция-элемент клас­са с тем же именем, что и класс. Программист предусматривает конструктор, который затем автоматически вызывается при создании объекта (создании экземпляра класса). Данные-элементы класса не могут получать начальные значения в определении класса. Они либо должны получить эти значения в конструкторе класса, либо их значения можно установить позже, после со­здания объекта. Конструкторы не могут указывать типы возвращаемых зна­чений или возвращать какие-то значения. Конструкторы можно перегружать, чтобы обеспечить множество начальных значений объектов класса.

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

1.11. Использование конструкторов с аргументами по умолчанию

Конструктор из timel.cpp (рис.1.5) присваивает нулевые (т.е. соответ­ствующие 12 часам по полуночи в военном формате времени) начальные значения переменным hour, minute и second. Конструктор может содержать значения аргументов по умолчанию. Программа на рис.1.7 переопределяет функцию конструктор Time так, чтобы она включала нулевые значения ар­гументов по умолчанию для каждой переменной. Задание в конструкторе аргументов по умолчанию позволяет гарантировать, что объект будет нахо­диться в непротиворечивом состоянии, даже если в вызове конструктора не указаны никакие значения. Созданный программистом конструктор, у ко­торого все аргументы — аргументы по умолчанию (или который не требует никаких аргументов), называется конструктором с умолчанием, т.е. кон­структором, который можно вызывать без указания каких-либо аргументов.

Рисунок 1.7. Использование конструктора с аргументами по умолчанию

Для каждого класса может существовать только один конструктор с умол­чанием. В этой программе конструктор вызывает функцию-элемент setTime со значениями, передаваемыми конструктору (или значениями по умолча­нию), чтобы гарантировать, что значение, предназначенное для hour, нахо­дится в диапазоне от 0 до 23, а значения для minute и second — в диапазоне от 0 до 59. Если значение выходит за пределы диапазона, оно устанавливается равным нулю с помощью setTime (это пример гарантии того, что данные-элементы будут в непротиворечивом состоянии).

Результат выполнения программы:

Программа на рис.1.7 создает 5 экземпляров объектов Time и задает им начальные значения; одному — со всеми тремя аргументами по умолчанию в вызове конструктора, второму — с одним указанным аргументам, третьему — с двумя указанными аргументами, четвертому — с тремя указанными аргументами и пятому — с тремя неверно указанными аргументами. Ото­бражается содержание данных каждого объекта после его создания и задания начальных значений.

Если для класса не определено никакого конструктора, компилятор со­здает конструктор с умолчанием. Такой конструктор не задает никаких на­чальных значений, так что после создания объекта нет никакой гарантии, что он находится в непротиворечивом состоянии.

1.12. Использование деструкторов

Деструктор — это специальная функция-элемент класса. Имя деструк­тора совпадает с именем класса, но перед ним ставится символ тильда (~). Это соглашение о наименовании появилось интуитивно, потому что операция тильда является поразрядной опе­рацией дополнения, а по смыслу деструктор является дополнением конструк­тора.

Деструктор класса вызывается при уничтожении объекта — например, когда выполняемая программа покидает область действия, в которой был создан объект этого класса. На самом деле деструктор сам не уничтожает объект — он выполняет подготовку завершения перед тем, как система ос­вобождает область памяти, в которой хранился объект, чтобы использовать ее для размещения новых объектов.

Деструктор не принимает никаких параметров и не возвращает никаких значений. Класс может иметь только один деструктор — перегрузка деструк­тора не разрешается.

Деструкторы редко используются с простыми классами. Они имеют смысл в классах, использующих динамическое распределение памяти под объекты (например, для массивов и строк).

Вопросы для самопроверки

1. Заполните пробелы в следующих утверждениях:

a) Ключевое слово ______ начинает определение структуры.

b) Элементы класса доступны посредством операции ______ в со­четании с объектом класса или посредством операции ____ в со­четании с указателем на объект класса.

c) Элементы класса, указанные как ___ , доступны только функ­циям-элементам класса и друзьям класса.

d) ____ является специальной функцией-элементом, используемой для задания начальных значений элементам данных класса-

e) По умолчанию доступ к элементам класса — _____.

f) Функция _____ используется для присваивания значений за­крытым данным-элементам класса.

g) _____ можно использовать для присваивания объекта класса другому объекту того же класса.

h) Функции-элементы класса обычно делаются ______ типа, а

данные-элементы — _______ типа.

i) Функция ______ используется для получения значений закры­тых данных класса.

j) Набор открытых функций-элементов класса рассматривается как ________ класса.

k) Говорят, что реализация класса скрыта от его клиентов или ________________.

l) Для введения определения класса можно использовать ключевые слова ____ и ___.

m) Элементы класса, указанные как ______ , доступны везде в области действия объекта класса.

2. Найдите ошибку (или ошибки) в каждом из следующих пунктов и объясните, как их исправить.

a) Допустим, что в классе Time объявлен следующий прототип,

void ~Time (int);

b) Следующий фрагмент является частью определения класса Time.

class Time {

public;

// прототипы функций

private:

int hour = 0;

int. minute = 0;

int second = 0;

};

c) Допустим, что в классе Employee объявлен следующий прототип.

Int Employee(const char *, const *);

2. КЛАССЫ: КОМПОЗИЦИЯ И ДИНАМИЧЕСКОЕ УПРАВЛЕНИЕ ОБЪЕКТАМИ

2.1. Константные объекты и функции-элементы

Одним из наиболее фундаментальных принципов создания хорошего программного обес­печения является принцип наименьших привилегий. Рассмотрим один из способов применения этого принципа к объектам.

Некоторые объекты должны допускать изменения, другие – нет. Про­граммист может использовать ключевое слово const для указания на то, что объект неизменяем – является константным и что любая попытка изменить объект является ошибкой. Например, в строке

const Time noon(12, 0, 0);

объявляется константный объект noon класса Time и присваивается ему начальное значение 12 часов пополудни.

Объявление константного объекта помогает провести в жизнь принцип наименьших привилегий. Случайные попытки изменить объект отлавливаются во время компи­ляции и не вызывают ошибок во время выполнения.

Компиляторы C++ воспринимают объявления const настолько неукосни­тельно, что в итоге не допускают никаких вызовов функций-элементов кон­стантных объектов (некоторые компиляторы дают в этих случаях только предупреждения). Это жестоко, поскольку клиенты объектов, возможно, за­хотят использовать различные функции-элементы чтения «get», а они, ко­нечно, не изменяют объект. Чтобы обойти это, программист может объявить константные функции-элементы; только они могут оперировать константными объектами. Конечно, константные функции-элементы не могут изменять объект – это не позволит компилятор.

Константная функция указывается как const и в объявлении, и в описании с помощью ключевого слова const после списка параметров функции, но перед левой фигурной скобкой, которая начинает тело функции. Например, в приведенном ниже предложении объявлена как константная функция-эле­мент некоторого класса А