Гл а в а 10 Объекты и классы в этой главе



Скачать 116.96 Kb.
Pdf просмотр
Дата16.09.2017
Размер116.96 Kb.

ГЛ А В А 10
Объекты и классы
В этой главе:
• Процедурное и объектно- ориентированное программирование
• Концепция классов
• Как определить и реализовать класс
• Общедоступный (
public
) и приватный (
private
) доступ к классу
• Данные-члены класса
• Методы класса (также называемые функциями-членами класса)
• Создание и использование объектов класса
• Конструкторы и деструкторы классов
• Функции-члены
const
• Указатель
this
• Создание массивов объектов
• Области видимости классов
• Абстрактные типы данных
О
бъектно-ориентированное программирование (ООП) — это особый кон- цептуальный подход к проектированию программ, и язык C++ расширяет C средствами, облегчающими применение такого подхода. Ниже перечислены наиболее важные средства ООП:
• Абстракция.
• Инкапсуляция и сокрытие данных.
• Полиморфизм.
• Наследование.
• Повторное использование кода.
Класс — это единственное наиболее важное расширение C++, предназначенное для реализации этих средств и связывающее их между собой. В настоящей главе на- чинается объяснение концепции классов. Здесь рассмотрена абстракция, инкапсуля- ция и сокрытие данных, а также показано, как в классах реализуются эти средства.
Обсуждается также определение класса, приводится класс с общедоступным и при- ватным разделами, создаются функции-члены, которые работают с данными класса.
Кроме того, настоящая глава познакомит вас с конструкторами и деструкторами, которые представляют собой специальные функции-члены, предназначенные для создания и уничтожения объектов, относящихся к классу. И, наконец, вы встрети- тесь с указателем this, важным компонентом программирования некоторых классов.
Последующие главы продолжат обсуждение в части перегрузки операций (другой ва- риант полиморфизма) и наследования — основы повторного использования кода.

Глава 10
476
Процедурное и объектно-
ориентированное программирование
Несмотря на то что настоящая книга в основном посвящена вопросам объектно- ориентированного программирования, стоит также более пристально взглянуть на стандартный процедурный подход, присущий таким языкам, как C, Pascal и BASIC.
Давайте рассмотрим пример, демонстрирующий разницу между ООП и процедурным программированием.
Предположим, что вас, как нового члена софтбольной команды “Гиганты Жанра” попросили вести статистику игр команды. Естественно, вы используете для этого свой компьютер. Если вы — процедурный программист, то, вероятно, будете думать следующим образом:
Так, посмотрим… Я хочу вводить имя, количество подач, количество попада- ний, средний уровень успешных подач (для тех, что не следит за бейсболом или софтболом: средний уровень успешных подач — это количество попада- ний, деленное на официальное количество подач, выполненных игроком; по- дачи прекращаются, когда игрок достигает базы либо выбивает мяч за пределы поля, но некоторые события, такие как получение обводки, не засчитываются в официальное количество подач), а также другую существенную статистику по каждому игроку. Минутку, но ведь предполагается, что компьютер должен мне облегчать жизнь, поэтому я хочу, чтобы он вычислял автоматически кое-что из этого, например, средний уровень успешных подач. Кроме того, я хочу, чтобы программа выдавала отчет по результатам. Как мне это организовать? Думаю, это следует делать с помощью функций. Да, я сделаю, чтобы main() вызывала функцию для получения ввода, затем другую функцию — для вычислений, а по- том третью — для вывода результатов. Гм-м-м… А что случится, когда я получу данные о другой игре? Я не хочу начинать все сначала! Ладно, я могу добавить функцию для обновления статистики. Черт возьми, может быть, мне понадо- бится меню в main(), чтобы выбирать между вводом, вычислением, обнов- лением и выводом данных. Гм-м-м … А как мне представлять данные? Можно использовать массив строк для хранения имен игроков, другой массив — для хранения числа подач каждого игрока и еще один массив — для хранения числа попаданий и так далее… Нет, это глупость! Я могу спроектировать структуру, чтобы хранить всю информацию о каждом игроке и затем использовать массив таких структур для представления всей команды.
Короче говоря, при процедурном подходе вы сначала концентрируетесь на про- цедурах, которым должны следовать, а только потом думаете о том, как представить данные. (На заметку: поскольку вам не нужно держать программу работающей в те- чение всего игрового сезона, то, вероятно, вы захотите сохранять данные в файле и читать их оттуда).
Теперь посмотрим, как изменится ваш взгляд, когда вы наденете шляпу ООП (вы- полненную в симпатичном полиморфном стиле). Вы начнете думать о данных. Более того, вы будете думать о данных не только в терминах их представления, но и в тер- минах их использования:

Объекты и классы
477
Посмотрим, что я должен отслеживать? Игроков, конечно. Поэтому мне ну- жен объект, который представляет игрока в целом, а не только его уровень успешных подач или их общее количество. Да, это будет основная единица данных — объект, представляющий имя и статистику игрока. Мне понадобятся некоторые методы для работы с этим объектом. Гм-м-м, думаю, понадобится ме- тод для ввода базовой информации в объект. Некоторые данные должен вычис- лять компьютер, например, средний уровень успешных подач. Я могу добавить метод для выполнения вычислений. И программа должна выполнять вычисле- ния автоматически, чтобы пользователю не нужно было помнить о том, что он должен просить ее об этом. Кроме того, понадобятся методы для обновления и отображения информации. То есть у пользователя должно быть три способа взаимодействия с данными: инициализация, обновление и вывод отчетов. Это и будет пользовательский интерфейс.
Короче говоря, при объектно-ориентированном подходе вы концентрируетесь на объекте, как его представляет пользователь, думая о данных, которые нужны для опи- сания объекта и операциях, описывающих взаимодействие пользователя с данными.
После разработки описания интерфейса вы перейдете к выработке решений о том, как реализовать этот интерфейс и как организовать хранение данных. И, наконец, вы соберете все это вместе в программу, соответствующую вашему новому дизайну.
Абстракции и классы
Жизнь полна сложностей, и единственный способ справится со сложностью — это ограничиться упрощенными абстракциями. Вы сами состоите из более чем окти- льона (10 48
) атомов. Некоторые студенты, изучающие сознание, могут сказать, что ваше сознание представляет собой коллекцию полуавтономных агентов. Но гораздо проще думать о себе, как о едином целом. В компьютерных вычислениях абстрак- ция — это ключевой шаг в представлении информации в терминах его интерфейса с пользователем. То есть вы абстрагируете основные операционные свойства пробле- мы и выражаете решение в этих терминах. В примере с софтбольной статистикой интерфейс описывает, как пользователь инициирует, обновляет и отображает дан- ные. От абстракций легко перейти к определяемым пользователем типам, которые в
C++ представлены классами, реализующими абстрактный интерфейс.
Что такое тип?
Давайте подумаем немного о том, что собой представляет тип. Например, кто та- кой зануда (nerd)? Если следовать популярному стереотипу, вы можете представить его в визуальных образах: толстый, в запотевших очках, карман, полный ручек и так далее. После некоторого размышления вы можете прийти к заключению, что зануду лучше описать присущим ему поведением — например, тем, как он реагирует на за- труднительные ситуации. У вас подобное положение, если вы не имеете в виду “при- тянутые” аналогии, с процедурным языком вроде C. Во-первых, вы думаете о данных в терминах их появления — как они сохраняются в памяти. Например, char занимает
1 байт памяти, а double — обычно 8 байт. Но если немного подумать, то вы придете к заключению, что тип данных также определен в терминах операций, которые до- пустимо выполнять с ним. Например, к типу int можно применять все арифметиче-

Глава 10
478
ские операции. Целые числа можно складывать, вычитать, умножать, делить. Можно также применять операцию вычисления модуля (%).
С другой стороны, рассмотрим указатели. Указатель может требовать для своего хранения столько же памяти, как и int. Он даже может иметь внутреннее представ- ление в виде целого числа. Но указатель не позволяет выполнять над собой те же операции, что целое. Например, вы не можете перемножать два указателя. Эта кон- цепция не имеет смысла, поэтому в C++ она не реализована. Отсюда, когда вы объяв- ляете переменную вроде int или указателя на float, то не просто выделяете память для нее, но также устанавливаете, какие операции допустимы с этой переменной.
Короче говоря, спецификация базового типа выполняет три вещи:
• Определяет, сколько памяти нужно объекту.
• Определяет, как интерпретируются биты памяти (long и float могут занимать одинаковое число бит памяти, но транслируются в числовые значения по-раз- ному).
• Определяет, какие операции, или методы, могут быть применены с использова- нием этого объекта данных.
Для встроенных типов информация об операциях встроена в компилятор. Но ког- да вы определяете пользовательские типы в C++, то должны предоставить эту инфор- мацию самостоятельно. В качестве вознаграждения за эту дополнительную работу вы получаете мощь и гибкость новых типов данных, соответствующих требованиям ре- ального мира.
Классы в C++
Класс — это двигатель C++, предназначенный для трансляции абстракций в поль- зовательские типы. Он комбинирует представление данных и методов для манипули- рования этими данными в пределах одного аккуратного пакета. Давайте взглянем на класс, представляющий акционерный капитал.
Во-первых, нужно немного подумать, как представлять акции. Вы можете взять один пакет акций в качестве базовой единицы и определить класс, представляющий этот пакет. Однако это потребует создания 100 объектов для представления 100 па- кетов, что явно не практично. Вместо этого в качестве базовой единицы вы можете представить персональную долю владельца в определенном пакете. Количество акций, находящихся во владении, будут частью представления данных. Реалистичный подход должен позволять поддерживать хранение информации о таких вещах, как начальная стоимость и дата покупки; это необходимо для налогообложения. Кроме того, он дол- жен предусматривать обработку таких событий, как разделение пакетов. Это может показаться довольно амбициозным для первой попытки определения класса, поэтому вы можете ограничиться неким идеализированным, упрощенным взглядом на пред- мет. В частности, можно ограничить реализуемые операции следующим перечнем:
• Приобретение пакета акций компании.
• Приобретение дополнительных акций в имеющийся пакет.
• Продажа пакета.
• Обновление объема доли в пакете акций.
• Отображения информации о пакетах, находящихся во владении.

Объекты и классы
479
Вы можете использовать этот список для определения общедоступного интерфей- са класса (и при необходимости добавлять дополнительные средства позднее). Чтобы поддержать этот интерфейс, вам нужна некоторая информация. Опять-таки, вы мо- жете воспользоваться упрощенным подходом. Например, вам не нужно беспокоить- ся о принятой в США практике оперировать пакетами акций в объемах, кратных 8 долларам. (Очевидно, что Нью-Йоркская фондовая биржа в курсе такого упрощения, имевшего место в предыдущем издании книги, потому что уже решила внести изме- нения в систему, описанную здесь.) Вам нужно хранить следующую информацию:
• Наименование компании.
• Количество акций, находящихся во владении.
• Объем каждой доли.
• Общий объем всех долей.
Далее вы можете определить класс. Обычно спецификация класса состоит из двух частей:
Объявление класса, описывающее компоненты данных в терминах членов дан- ных, а также общедоступный интерфейс в терминах функций-членов, называе- мых методами.
Определение методов класса, описывающее, как реализованы определенные функ- ции-члены.
Грубо говоря, объявление класса представляет общий обзор класса, в то время как определение методов сосредоточено на его деталях.
Что такое интерфейс?
Интерфейс — это совместно используемая часть, предназначенная для взаимодействия двух си- стем — например, между компьютером и принтером, или между пользователем и компьютерной программой. Например, пользователем можете быть вы, а программой — текстовый процессор.
Когда вы используете текстовый процессор, то не переносите слова напрямую из своего созна- ния в память компьютера. Вместо этого вы взаимодействуете с интерфейсом, предложенным программой. Вы нажимаете клавишу, и компьютер отображает символ на экране. Вы перемеща- ете мышь, и компьютер перемещает курсор на экране. Вы случайно щелкаете кнопкой мышки, и что-то происходит с абзацем, в котором вы печатаете. Программный интерфейс управляет пре- образованием ваших намерений в специфическую информацию, сохраняемую в компьютере.
В отношении классов мы говорим об общедоступном интерфейсе. В этом случае потребителем его является программа, использующая класс, система взаимодействия состоит из объектов клас- са, а интерфейс состоит из методов, предоставленных тем, кто написал этот класс. Интерфейс позволяет вам, как программисту, написать код, взаимодействующий с объектами класса, и таким образом, позволяет программе взаимодействовать с объектами класса. Например, что- бы определить количество символов в объекте string
, вам не нужно открывать этот объект и смотреть что у него внутри. Вы просто используете метод size()
класса, предоставленный его разработчиком. Таким образом, метод size()
является частью общедоступного интерфей- са между пользователем и объектом класса string
. Аналогичным образом метод getline()
является частью общедоступного интерфейса класса istream
. Программа, использующая cin
, не обращается напрямую к внутренностям объекта cin для чтения строки ввода, вместо этого всю работу выполняет getline()
. Если вам нужны более доверительные отношения, то вместо того, чтобы думать о программе, использующей класс, как о внешнем пользователе, вы можете думать об авторе программы, использующей этот класс, как о внешнем пользователе. Но в лю- бом случае, чтобы использовать класс, вы должны знать его общедоступный интерфейс, а чтобы написать класс, должны создать ему такой интерфейс.

Глава 10
480
Разработка классов и программ, использующих их, требует выполнения опреде- ленных шагов. Вместо того чтобы взять на вооружение их целиком, давайте разделим процесс разработки на небольшие стадии: позднее код, из которых они состоят, бу- дет объединен в листинге 10.3. В листинге 10.1 представлена первая стадия, пробное объявление класса под именем Stock. (Чтобы упростить идентификацию классов, в настоящей книге используется общий, но не универсальный метод — соглашение о написании имен классов с заглавной буквы.) Обратите внимание, что листинг 10.1 выглядит как объявление структуры с некоторыми небольшими дополнениями, таки- ми как функции-члены, а также разделы public и private. Вскоре вы разберетесь в этом объявлении (поэтому не используйте его в качестве модели), но вначале давай- те посмотрим, как это объявление работает.
Листинг 10.1. Первая часть
stocks.cpp
// Начало файла stocks.cpp
#include
#include
class Stock // объявление класса
{
private:
char company[30];
int shares;
double share_val;
double total_val;
void set_tot() { total_val = shares * share_val; }
public:
void acquire(const char * co, int n, double pr);
void buy(int num, double price);
void sell(int num, double price);
void update(double price);
void show();
}; // обратите внимание на точку с запятой в конце
На детали реализации класса мы взглянем позднее, а сейчас проанализируем наи- более общие средства. Для начала, ключевое слово C++ class идентифицирует код в листинге 10.1 как определение дизайна класса. Этот синтаксис идентифицирует
Stock как имя типа для нового класса. Это позволяет объявлять переменные, назы- ваемые объектами, или экземплярами типа Stock. Каждый индивидуальный объект это- го типа представляет отдельный пакет акций, находящийся во владении. Например, следующее объявление:
Stock sally;
Stock solly;
создает два объекта с именами sally и solly. Объект sally, например, может пред- ставлять пакет акций определенной компании, принадлежащий Салли.
Далее, отметим, что информация, которую вы решили сохранять, появляется в форме данных-членов класса, таких как компания и размер доли. Переменная-член company объекта sally, например, хранит наименование компании, переменная- член share содержит количество долей общего пакета акций компании, которыми владеет Салли, переменная-член share_val хранит объем каждой доли, а перемен-

Объекты и классы
481
ная-член total_val — общий объем всех долей. Аналогично операции, которые вы хотите иметь в виде функций-членов (или методов), называются sell() и update().
Функции-члены могут быть определены на месте — как, например, set_tot(), либо могут быть представлены прототипами — как остальные функции-члены класса.
Полные определения остальных функций-членов появятся позже, но прототип уже позволяет определить их интерфейс. Связка данных и методов в единое целое — наи- более замечательное свойство класса. При таком дизайне создание объекта типа Stock автоматически устанавливает правила, определяющие использование объекта.
Вы уже видели, как классы istream и ostream владеют функциями-членами вроде get()
и getline(). Прототипы функций в определении класса Stock демонстриру- ют, как функции-члены устанавливаются. Заголовочный файл iostream, например, содержит прототип getline() в объявлении класса iostream.
Новыми также являются ключевые слова private и public. Эти метки описыва- ют управление доступом к членам класса. Любая программа, которая использует объ- ект определенного класса, может иметь непосредственный доступ к членам из раз- дела public. Доступ к членам класса из раздела private программа может получить
только через общедоступные функции-члены из раздела public
(или же, как будет показано в главе 11, через дружественные функции). Например, единственный спо- соб изменить переменную shares класса Stock — это воспользоваться одной из функ- ций-членов класса Stock. Таким образом, общедоступные функции-члены действуют в качестве посредников между программой и приватными членами объекта. Они предоставляют интерфейс между объектом и программой. Эта изоляция данных от прямого доступа со стороны программы называется сокрытием данных (C++ предла- гает третье ключевое слово для управления доступом — protected, которое мы по- ясним, когда будем говорить о наследовании в главе 13). (См. рис. 10.1.) В то время как сокрытие данных может быть недобросовестным действием, когда мы говорим в общепринятом контексте о доступе к информации инвестиционных фондов, это яв- ляется хорошей практикой в компьютерных вычислениях, поскольку предохраняет целостность данных.
Дизайн класса пытается отделить общедоступный интерфейс от специфики реа- лизации. Общедоступный интерфейс представляет абстрактный компонент дизайна.
Собрание деталей реализации в одном месте и отделение их от абстракции называ- ется инкапсуляцией. Сокрытие данных (размещение данных в разделе private клас- са) представляет собой способ инкапсуляции, и поэтому скрывает функциональные детали реализации в разделе private, как это сделано в классе Stock с функцией set_tot()
. Другой пример инкапсуляции — обычная практика помещения определе- ния функций класса в файл, отдельный от объявления класса.
Объектно-ориентированное программирование и C++
Объектно-ориентированное программирование — это стиль программирования, который в опре- деленной степени можно применять в любом языке. Безусловно, вы можете реализовать многие идеи ООП в обычной программе на языке C. Например, в главе 9 представлен пример (см. ли- стинги 9.1, 9.2, 9.3), в котором заголовочный файл содержит прототип структуры вместе с прото- типами функций, предназначенных для манипулирования этой структурой. Функция main()
про- сто определяет переменные этого типа и использует ассоциированные функции для управления этими переменными. main()
не имеет непосредственного доступа к членам структур. По сути, этот пример объявляет абстрактный тип, который помещает формат хранения и прототипы функ- ций в заголовочный файл, скрывая реальное представление данных от main()

Глава 10
482
C++ включает средства, специально предназначенные для реализации подхода ООП, что позво- ляет продвинуться в этом направлении на несколько шагов дальше, чем в языке C. Во-первых, размещение прототипов функций в едином объявлении класса вместо того, чтобы держать их раздельно, унифицирует описание за счет размещения его в одном месте. Во-вторых, объявле- ние данных с приватным доступом разрешает доступ к ним только для авторизованных функций.
Если в примере на C функция main()
имеет непосредственный доступ к членам структуры, что нарушает дух ООП, это не нарушает никаких правил языка C. Однако, при попытке прямого до- ступа, скажем, к члену shares объекта
Stock будут нарушены правила языка C++ и компилятор перехватит это.
Отметим, что сокрытие данных не только предотвращает прямой доступ к дан- ным, но также избавит вас (в роли пользователя этого класса) от необходимости знать то, как представлены данные. Например, член show() отображает, помимо прочего, общую сумму пакета акций, находящегося во владении. Это значение мо- жет быть сохранено в виде части объекта, как это делает код в листинге 10.1, либо может быть при необходимости вычислено. С точки зрения пользователя нет разни- цы, какой подход применяется. Необходимо знать только то, что делают различные функции-члены — то есть какие аргументы они принимают, и какие типы значений возвращают. Принцип состоит в том, чтобы отделить детали реализации от дизайна интерфейса. Если позже вы найдете лучший способ реализации представления дан- ных или деталей внутреннего устройства функций-членов, то сможете изменить их без изменения программного интерфейса, что значительно облегчает поддержку и сопровождение программ.
class Stock
{
private:
char company[30];
int shares;
double share_val;
double total_val;
void set_tot() { total_val = shares * share_val; }
public:
void acquire(const char * co, int n, double pr);
void buy(int num, double price);
void sell(int num, double price);
void update(double price);
void show();
};
Ключевое слово private идентифицирует члены класса, которые могут быть доступны только через общедоступные функции члены (сокрытие данных).
Ключевое слово class идентифицирует объявление класса.
Имя класса, которое становится именем определенного пользователем типа.
Члены класса могут быть типами данных или функциями
Ключевое слово public идентифицирует члены класса, которые образуют общедоступный интерфейс класса.
Рис. 10.1. Класс Stock

Объекты и классы
483
Управление доступом к членам:
public
или
private?
Вы можете объявлять члены класса — будь они единицами данных или функци- ями-членами — как в общедоступном (public), так и в приватном (private) разде- лах объявления класса. Но поскольку одним из основных принципов ООП являет- ся сокрытие данных, то единицы данных обычно размещаются в разделе private.
Функции-члены, которые образуют интерфейс класса, размещаются в разделе public.
В противном случае вы не сможете вызывать эти функции из программы. Как пока- зывает объявление класса Stock, вы все же можете поместить функции-члены в раз- дел private. Вы не сможете вызвать такие функции из программы непосредственно, но общедоступные (public) методы могут использовать их. Как правило, вы исполь- зуете приватные функции-члены для управления деталями реализации, которые не формируют собой часть общедоступного интерфейса.
Вы не должны использовать ключевое слово private в объявлении класса, по- скольку это спецификатор доступа к объектам класса по умолчанию:
class World
{
float mass;
// приватный по умолчанию char name[20]; // приватный по умолчанию public:
void tellall(void);
}
Однако в этой книге используется явное указание метки private для того, чтобы подчеркнуть концепцию сокрытия данных.
Классы и структуры
Объявление класса выглядит похожим на объявление структуры с дополнениями в виде функ- ций-членов и меток видимости private и public
. Фактически C++ расширяет и на структуры свойства, которые есть у классов. Единственная разница состоит в том, что типом доступа по умолчанию у структур является public
, в то время как у классов — private
. Программисты на
C++ обычно используют классы для реализации дескрипторов классов, тогда как ограниченные структуры применяются для простых объектов данных или же классов, не имеющих компонентов private
Реализация функций-членов класса
Вы по-прежнему обязаны определять вторую часть спецификации класса, то есть предоставлять код для тех функций-членов, которые описаны прототипами в объяв- лении класса. Определения функций-членов очень похожи на обычные определения функций. Каждая из них имеет заголовок и тело. Определения функций-членов могут иметь тип возврата и аргументы. Но, кроме того, с ними связаны две специфических характеристики:
• Когда вы определяете функцию-член, то используете операцию разрешения контекста (::) для идентификации класса, которому принадлежит функция.
• Методы класса имеют доступ к private-компонентам класса.

Глава 10
484
Рассмотрим это подробнее.
Во-первых, заголовок для функции-члена использует операцию разрешения кон- текста (::) для идентификации класса, которому она принадлежит. Например, заго- ловок для функции-члена update() выглядит так:
void Stock::update(double price)
Эта нотация означает, что вы определяете функцию update(), которая является членом класса Stock. Но это означает не только то, что функция update() является функцией-членом, но также и то, что вы можете использовать то же имя для функ- ций-членов другого класса. Например, функция update() для класса Button будет иметь следующий заголовок:
void Button::update(double price)
Таким образом, операция разрешения контекста идентифицирует класс, к кото- рому данный метод относится. Говорят, что идентификатор update() имеет область видимости класса. Другие функции-члены класса Stock могут при необходимости ис- пользовать метод update() без операции разрешения контекста. Это связано с тем, что они принадлежат одному классу, а, следовательно, имеют общую область видимо- сти. Использование update() за пределами объявления класса и определений мето- дов, однако, требует применения специальных мер, которые мы рассмотрим далее.
Единственный способ однозначного разрешения имен методов — использовать полное имя, включающее имя класса. Stock::update() называется квалифицирован-
ным именем функции. Простое имя update()
, с другой стороны, является аббревиату- рой (неквалифицированным именем) полного имени и может применяться только в контексте класса.
Следующей специальной характеристикой методов является то, что метод может иметь доступ к приватным членам класса. Например, метод show() может использо- вать код вроде следующего:
cout << "Company: " << company
<< " Shares: " << shares << endl
<< " Share Price: $" << share_val
<< " Total Worth: $" << total_val << endl;
Здесь company, shares и так далее являются приватными членами данных класса
Stock
. Если вы попытаетесь воспользоваться функцией, не являющейся членом, для доступа к этим данным-членам, то компилятор остановит вас. (Однако дружествен- ные функции, описанные в главе 11, представляют собой исключение.)
Памятуя об этих двух обстоятельствах, вы можете реализовать методы класса, как показано в листинге 10.2. Эти определения методов могут быть помещены в отдель- ный файл либо в тот же, где находится объявление класса. Это самый простой, хотя и не наилучший способ сделать объявление класса доступным определениям методов.
(Лучший способ, который мы применим далее в настоящей главе, заключается в том, чтобы использовать объявление класса и отдельный файл исходного кода с опреде- лением функций.) Для предоставления возможности работы с областями имен, в не- которых методах используется квалификатор std::, а в других — объявления using.

Объекты и классы
485
Листинг 10.2. Продолжение
stocks.cpp
// Продолжение stocks.cpp -- реализация функций-членов класса void Stock::acquire(const char * co, int n, double pr)
{
std::strncpy(company, co, 29); // усечь co для помещения в company company[29] = '\0';
if (n < 0)
{
std::cerr << "Количество пакетов не может быть отрицательным; для "
<< company << " установлено в 0.\n";
shares = 0;
}
else shares = n;
share_val = pr;
set_tot();
}
void Stock::buy(int num, double price)
{
if (num < 0)
{
std::cerr << "Количество приобретаемых пакетов не может быть отрицательным. "
<< "Транзакция прервана.\n";
}
else
{
shares += num;
share_val = price;
set_tot();
}
}
void Stock::sell(int num, double price)
{
using std::cerr;
if (num < 0)
{
cerr << "Количество продаваемых пакетов не может быть отрицательным. "
<< "Транзакция прервана.\n";
}
else if (num > shares)
{
cerr << "Вы не можете продать больше того, чем владеете! "
<< "Транзакция прервана.\n";
}
else
{
shares -= num;
share_val = price;
set_tot();
}
}

Глава 10
486
void Stock::update(double price)
{
share_val = price;
set_tot();
}
void Stock::show()
{
using std::cout;
using std::endl;
cout << "Компания: " << company
<< " Пакетов: " << shares << endl
<< " Цена пакета: $" << share_val
<< " Общая стоимость: $" << total_val << endl;
}
Замечания о функциях-членах
Функция acquire() управляет первоначальной покупкой пакета акций заданной компании, в то время как buy() и sell() — дополнительной покупкой и продажей акций для существующего пакета. Методы buy() и sell() гарантируют, что коли- чество купленных или проданных акций не будет отрицательным. Кроме того, если пользователь пытается продать больше акций, чем у него есть, функция sell() отме- нит транзакцию. Техника объявления данных приватными (private) и ограничения доступа к общедоступным (public) функциям дает вам контроль над использованием данных. В данном случае это позволяет создать “предохранитель”, защищающий от некорректных транзакций.
Четыре из функций-членов устанавливают или сбрасывают числовое значение члена класса total_val. Вместо того чтобы повторять в коде вычисление этого значения четыре раза, каждая из общедоступных функций-членов вызывает функ- цию set_tot(). Поскольку эта функция представляет собой просто реализацию вну- треннего кода, а не является частью общедоступного интерфейса, класс объявляет set_tot()
как приватную функцию-член. (То есть set_tot() представляет собой функцию-член, используемую автором класса, но не используемую теми, кто пишет код, использующий класс.) Если вычисления, выполняемые функцией, сложные, это может уменьшить общий объем исходного кода. Однако здесь главное в том, что, ис- пользуя вызов этой функции вместо того, чтобы каждый раз повторять код вычис- лений, вы гарантируете, что всегда будет применяться абсолютно идентичный алго- ритм. Кроме того, если вы захотите изменить его (что в данном конкретном случае маловероятно), то должны будете сделать это только в одном месте.
Метод acquire() использует strncopy() для копирования строк. Если вы забы- ли, напомним, что вызов strncopy(s2,
s1,
n)
копирует либо s1 в s2 целиком (если длина s1 менее n символов) либо только первые n символов s1, если длина s1 пре- вышает n. Если s1 содержит менее n символов, то функция strncopy() дополняет s2
нулевыми байтами до длины n. То есть strncopy(firstname,
"Tim",
6)
копи- рует символы T, i и m в firstname и добавляет три нулевых символа с тем, чтобы общее количество скопированных было равно 6. Но если длина s1 больше шести, то никакие нулевые символы не добавляются. То есть вызов strncopy(firstname,

Объекты и классы
487
"Priscilla",
4)
просто копирует символы P, r, i и s в firstname, создавая из них символьный массив, но поскольку пропущен завершающий нулевой символ, это не будет являться строкой. Поэтому acquire() помещает в конец массива нулевой сим- вол, тем самым гарантируя, что это будет строка.
Объект
cerr
cerr
, как и cout
, является объектом класса ostream
. Разница в том, что перенаправление вы- вода операционной системы касается только cout
, но не cerr
. Объект cerr применяется для вывода сообщений об ошибках. Поэтому если вы переадресуете вывод программы в файл, и при ее выполнении происходит ошибка, то вы по-прежнему получите сообщение о ней на экране.
(В Unix можно переадресовывать вывод cout и cerr независимо друг от друга Операция ко- мандной строки
>
переадресует вывод cout
, а операция
2>
— cerr
.)
Встроенные методы
Любая функция с определением внутри объявления класса автоматически ста- новится встроенной. То есть Stock::set_tot() является встроенной функцией.
Объявления класса часто используют встроенные функции для сокращения функций- членов и set_tot() — пример такого случая.
Если хотите, вы можете определить функцию-член вне объявления класса и, тем не менее, сделать ее встроенной. Чтобы это сделать, просто используйте квалифика- тор inline при определении функции в разделе реализации класса:
class Stock
{
private:
void set_tot();
// определение оставлено отдельным public:
};
inline void Stock::set_tot()
// использование inline в определении
{
total_val = shares * share_val;
}
Специальные правила для встроенных функций требуют, чтобы они были опреде- лены в каждом файле, где они используются. Самый простой способ гарантировать, что встроенные определения доступны всем файлам в многофайловой программе — это включить определение inline в тот же заголовочный файл, где объявлен класс.
(Некоторые системы разработки снабжены интеллектуальными компоновщиками, допускающими inline-определения в отдельном файле реализации.)
Кстати, согласно правилу перезаписи, определение метода в объявлении класса эк- вивалентно замене определения метода прототипом и последующей перезаписью определения в виде встроенной функции — немедленно после объявления класса. То есть исходное определение set_tot() в листинге 10.2 эквивалентно только что по- казанному, где определение следует за объявлением класса.

Каталог: PDF
PDF -> Российская академия медицинских наук научные советы по комплексным проблемам медицины
PDF -> Министерство здравоохранения кыргызской республики острая сердечная недостаточность
PDF -> Остеохондроз и его предупреждение На вопрос: «что такое остеохондроз?»
PDF -> Свч инновационные технологии
PDF -> Мёд башкирский 2 Иммунная система и мёд 2
PDF -> Программа конференции с международным участием «современные аспекты диагностической и лечебной эндоскопии»
PDF -> Методическая разработка Формирование представления о здоровом образе жизни среди студентов общежития
PDF -> Медикаментозное лечение острой сердечной недостаточности: позиции левосимендана


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


База данных защищена авторским правом ©zodorov.ru 2017
обратиться к администрации

    Главная страница