Функции встроенные и невстроенные — КиберПедия 

Археология об основании Рима: Новые раскопки проясняют и такой острый дискуссионный вопрос, как дата самого возникновения Рима...

История развития пистолетов-пулеметов: Предпосылкой для возникновения пистолетов-пулеметов послужила давняя тенденция тяготения винтовок...

Функции встроенные и невстроенные

2021-12-07 44
Функции встроенные и невстроенные 0.00 из 5.00 0 оценок
Заказать работу

Лабораторный практикум

«Основы объектно-ориентированного программирования»

Москва 2008


УДК 32.973.1

ББК 681.3

Ф59

 

 

Финогенов К.Г. Лабораторный практикум «Основы объектно-ориентированного программирования». Уч. пособие.

М.:МИФИ, 2008. 92 с.

 

 

Пособие предназначено для широкого круга читателей, приступающих к изучению современной идеологии разработки программных продуктов – методов объектно-ориентированного программирования (ООП). В первой части описываются основные понятия и концепции ООП, рассматривается аппарат ООП, изучается техника составления прикладных объектно-ориентированных программ.

Вторая часть пособия – описание лабораторного практикума по освоению разработки объектно-ориентированных приложений Windows.

Предназначено для обучения студентов кафедры компьютерных медицинских систем факультета автоматики и электроники МИФИ по курсам “Языки программирования и операционные системы” и “Компьютерный практикум”. Пособие может быть также полезно студентам, аспирантам и преподавателям, знакомым с языком С++ и программированием в системе Windows и желающим самостоятельно освоить современные методы разработки программных средств.

Рецензент В.В. Комаров

Рекомендовано редсоветом МИФИ в качестве учебного пособия

© Московский инженерно-физический институт
(государственный университет), 2008

Редактор Н.В. Шумакова

 

 

Подписано в печать 00.00.2008 г.                                  Формат 60 ´ 84 1/16

Печ. л. 5,75   Уч.-изд. л. 5,75                                     Тираж 120 экз.

Изд. № 000-0                                                                   Заказ

 

Московский инженерно-физический институт (государственный университет)

Типография МИФИ. 115409, Москва, Каширское шоссе, 31


С о д е р ж а н и е

Часть 1. Теоретические сведения................................. 5

1. Классы и объекты............................................................... 5

   Объектно-ориентированный подход к программированию 5

   Понятия класса и объекта...................................................... 8

   Доступ к членам класса....................................................... 11

   Функции встроенные и невстроенные................................ 12

   Статические переменные-члены класса............................. 13

2. Конструкторы и деструкторы........................................ 15

   Конструкторы...................................................................... 15

   Конструктор с инициализацией членов класса по умолчанию      17

   Деструкторы......................................................................... 18

3. Перегрузка........................................................................... 19

   Перегрузка функций............................................................ 20

   Перегрузка конструкторов.................................................. 21

   Перегрузка операторов........................................................ 23

4. Производные классы и наследование........................ 28

   Объявление производного класса....................................... 28

   Состав производного класса............................................... 30

   Защищенные члены класса.................................................. 31

   Конструкторы и производные классы................................ 33

5. Виртуальные функции.................................................... 34

   Понятие виртуальной функции........................................... 34

   Обслуживание множества объектов класса........................ 35

   Использование виртуальных функций................................ 41

   Абстрактные базовые классы.............................................. 42

6. Потоки ввода-вывода....................................................... 44

   Иерархия потоковых классов ввода-вывода в С++............ 44

   Базовые операции с файловыми потоками......................... 45

   Перегрузка операторов вставки и извлечения.................... 47

7. Живучесть объектов......................................................... 52

   Проблемы хранения объектов на диске.............................. 52

   Библиотечный класса string................................................. 53

   Живучие объекты................................................................. 56


Часть 2. Лабораторный практикум........................ 63

Работы лабораторного практикума................................ 63

   Работа №1. Понятия класса и объекта

          (индивидуальное задание А)........................................ 63

   Работа №2. Встроенные и невстроенные функции-члены. 63

   Работа №3. Конструкторы................................................... 64

   Работа №4. Деструкторы..................................................... 64

   Работа №5. Конструктор с инициализацией по умолчанию 65

   Работа №6. Статическая переменная в составе класса...... 65

   Работа №7. Перегрузка функций......................................... 66

   Работа №8. Перегрузка конструкторов

          (индивидуальное задание B)........................................ 66

   Работа №9. Перегрузка операторов

          (индивидуальное задание C)........................................ 67

   Работа №10. Базовые и производные классы..................... 68

   Работа №11. Виртуальные функции................................... 69

   Работа №12. Потоки ввода-вывода..................................... 70

   Работа №13. Перегрузка в прикладном классе операторов

          вставки и извлечения.................................................... 71

   Работа №14. Библиотечный класс string............................. 72

   Работа №15. Создание живучих объектов и запись их

          на диск........................................................................... 72

   Работа №16. Чтение с диска живучих объектов................. 73

Индивидуальные задания лабораторного
практикума
.............................................................................. 75

Список литературы............................................................... 92


Часть 1
Теоретические сведения

1. Классы и объекты

Объектно-ориентированный подход
к программированию

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

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

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

В дальнейшем на основе разработанных ранее классов можно создавать новые, производные, в которые будут входить все средства базовых классов плюс какие-то дополнения. Например, от описанного выше класса Image может быть образован производный класс Filter, в котором предусмотрены функции цифровой фильтрации изображения по тем или иным алгоритмам с записью на диск получаемых отфильтрованных данных, а также класс Retuoch, позволяющий ретушировать изображения, выводимые на экран, вручную с помощью мыши. Разрабатывая эти производные классы, мы уже не должны заниматься средствами ввода и вывода изображения, поскольку эти функции были разработаны ранее, имеются в базовом классе Image и доступны во всех производных от него классах; для класса Filter следует только разработать, например, функцию Gray () для преобразования цветного изобра­жения в полутоновое и функцию Sharp () для более отчетливого выделения границ опухоли, а для класса Retouch – функцию Draw () графического редактирования изображения на экране.

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

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

Можно предложить два основных стиля использования ООП в прикладных задачах. Первый заключается в самостоятельной разработке необходимой иерархии и состава классов, позволяющих реализовать тот или иной программный проект достаточной сложности. Достоинство такого подхода проявляется в том, что после определения состава и поведения объектов разработчик может забыть о деталях конкретных реализаций и сосредоточиться на более высоком уровне взаимодействия объектов друг с другом и с внешним миром. Важно подчеркнуть, что разработанные для решения конкретной задачи классы часто носят абстрактный характер, описывая такие объекты, как списки, деревья, структуры и проч. Это дает возможность использовать разработанный инструментарий для решения совсем иных задач. Например, класс, описывающий поведение связного списка некоторых объектов, и включающий полный набор средств обслуживания этого списка (добавление и уничтожение элементов списка, упорядочение списка по различным критериям, вывод содержимого списка на печать и т. д.), с равным успехом можно использовать при разработке карточных игр (со списками – колодами карт), автоматизации отдела кадров предприятия (со списками сотрудников), создания гипертекстового справочника (со списками связанных терминов) и в других, столь же различающихся предметных областях.

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

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

Понятия класса и объекта

Класс (определение которого начинается с ключевого слова class) является естественным развитием понятия структуры и отличается от последней тем, что помимо данных, включает еще и функции для их обработки. Вспомним основные правила объявления и обслуживания структур на примере простой структуры с именем Men, включающей в себя информацию об имени и возрасте человека:

struct Men{

char* name;// Указатель на имя индивидуума

int age;// Его возраст

};// Завершающая скобка и точка с запятой

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

Men m1;// Объявляем структурную переменную типа Men с именем m 1

m1.name = ”Иванов”;// Инициализация члена name

m1.age = 19;// Инициализация члена age

Men* pm2;// Объявляем указатель типа Men *

pm2 = new Men// Выделяем память под структурную переменную

pm2->name = ”Петров”;// Инициализация члена name

pm2->age = 20;// Инициализация члена age

Напомним, что имена переменных-указателей часто начинают с буквы p (от pointer, указатель). Такой стиль обозначения удобен, так как наглядно выделяет в программе указатели среди прочих переменных, хотя и необязателен.

Определим теперь класс Men с теми же данными (данные, входящие в состав класса, называют данными-членами) и базовым набором функций (функций-членов, или методов) для инициализации и чтения данных, принадлежащих этому классу. Заметим, что имя класса принято начинать с прописной буквы, хотя это и необязательно. Часто также это имя начинают с буквы C (от Class) или T (от Type).

class Men{

private: // Закрытые данные

char* name;

int age;

public: // Открытые функции

void SetName(char* n){name=n;}// Инициализация имени

void SetAge(int a){age=a;} // Инициализация возраста

char* GetName(){return name;} // Чтение имени

int GetAge(){return age;} // Чтение возраста

};// Завершающая скобка и точка с запятой

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

Функции объявлены и описаны в открытой части класса после описателя public. Это делает их доступными из программы, т. е. из главной функции WinM ain (), а также и из прикладных функций, вызываемых по ходу выполнения программы. В классе обязательно должны быть открытые функции, иначе к элементам класса просто нельзя будет обратиться.

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

/*Создание экземпляра класса по имени*/

Men m1;

m1.SetName("Храмов");

m1.SetAge(54);

char txt[50];// Символьный массив для формирования вывода

wsprintf(txt,”Имя: %s\nВозраст: %d”, m1.GetName(),

                                     m1.GetAge());

MessageBox(NULL,txt,”Индивидуум 1”,MB_OK);// Вывод в окно

                      // соощения данных-членов класса

/*Создание экземпляра класса с помощью указателя*/

Men* pm2=new Men;

pm2->SetName("Хромов");

pm2->SetAge(23);

char txt[50];// Символьный массив для формирования вывода

wsprintf(txt,”Имя: %s\nВозраст: %d”, pm1->GetName(),

                                    pm2->GetAge());

MessageBox(NULL,txt,”Индивидуум 2”,MB_OK); // Вывод в окно

                      // соощения данных-членов класса

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

SetName("Громов");

так как неизвестно, к какому объекту оно относится (в приведенном выше примере m1 или p m2). Если объект класса создан по имени, то, как и для структур, имя объекта и имя функции разделяются точкой; если обращение к объекту выполняется с помощью указателя на объект, то указатель и имя функции разделяются оператором ->.

Доступ к членам класса

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

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

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

Данные или функции, описанные после ключевого слова protected, являются защищенными. Такие члены класса доступны только данному классу и классам, производным от него. Таким образом, защищенные данные имеют смысл только при наличии иерархии производных классов. О производных классах речь будет идти ниже; отметим только, что закрытые (private) члены класса в производных классах недоступны, и если к каким-то данным надо открыть доступ из производного класса, оставив их закрытыми от внешнего мира, их следует объявить защищенными (protected).

Ключевые слова public, private и protected могут встречаться в описании класса в любом порядке и в любом количестве. По умолчанию (при отсутствии ключевого слова-спецификатора доступа) данные-члены считаются закрытыми (private). Приведенные ниже описания классов вполне равнозначны:

class A{

int x;// Закрытое по умолчанию данное-член

int y;// То же

void func();// Закрытая по умолчанию функция-член

public:

void SetX(int);// Открытая функция

void SetY(int;// То же

};

class A{

public:

void SetX(int);// Открытая функция

void SetY(int;// То же

private:

int x;// Закрытое данное

int y;// То же

void func();// Закрытая функция

};

Конструкторы и деструкторы

Конструкторы

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

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

Men(параметры){ тело конструктора }

Конструктор может иметь произвольное число параметров, или не иметь их совсем, хотя часто в конструкторе предусматривается столько параметров, сколько данных-членов класса необходимо инициализировать при создании нового объекта. Конструктор не может возвращать никакого значения (даже void).

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

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

Поскольку назначение конструкторов – создание экземпляров класса (объектов), они, как правило, объявляются в открытой части класса, чтобы иметь к ним доступ из программы или из других классов.

Вернемся к нашему примеру с классом Men и включим в его состав конструктор, который, как ему и положено, будет служить для инициализации данных-членов класса задаваемыми в программе значениями (именем и возрастом индивидуума):

class Men{

char* name;

int age;

public:

Men(char* n,int a){// Конструктор

name=n;     // с двумя

age=a;      // параметрами

}

char* GetName(){return name;}// Встроенные

int GetAge(){return age;} // функции - члены

};


Поскольку инициализация членов класса теперь осуществляется конструктором, мы удалили из класса функции SetName () и SetAge (), которые выполняли ту же операцию.

Конструктор класса Men имеет два параметра и, следовательно, при создании объектов класса необходимо указывать два аргумента. Создать объект класса без указания начальных значений имени и возраста не удастся, так как в данном классе не предусмотрен конструктор без параметров. Его можно было объявить (наряду с конструктором с двумя параметрами); такая методика (перегрузка конструкторов) будет рассмотрена в последующих разделах.

Создание объектов класса Men при наличии конструктора будет выглядеть следующим образом:

Men m1("Freston",17);

Men* pm2=new Men("Young",23);

Деструкторы

Деструктор, как и конструктор, имеет имя, совпадающее с именем класса, но перед ним ставится знак “~”. Для класса A деструктор должен иметь вид

~A(){ тело деструктора }

Деструктор не может иметь ни параметров, ни возвращаемого значения. Он автоматически вызывается при уничтожении объекта класса.

Основное назначение деструктора – выполнение завершающих действий (например, закрытие файлов или уничтожение таймеров). Однако чаще всего деструктор используется для освобождения памяти, динамически выделенной в процессе создания или использования объекта. Если, например, в конструкторе предусмотрено выделение памяти под массив данных с помощью оператора new (что может потребоваться, если размер этого массива заранее неизвестен и передается в конструктор через один из его параметров), то в деструкторе необходимо эту память освободить с помощью оператора delete []:

class A{

char* array;

public:

A(int size){

array=new char[size];

}

~A(){

delete[] array;

}

};

При таком определении класса A объект a 1, созданный предложением

A a1(10);

будет содержать в себе символьный массив длиной 10 байтов, а предложение

A a2(1000)

создаст объект a 2 с массивом длиной 1000 байтов.

При создании объекта класса A автоматически вызываемый конструктор выделит память под массив; при уничтожении объекта (оператором delete или при завершении программы) автоматически вызываемый деструктор освободит память массива, после чего будет уничтожена оставшаяся часть объекта.

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

Перегрузка

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

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

Перегрузка функций

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

void Func(int);

void Func(int,int);

void Func(char*);

являются перегруженными; при вызове функции Func () с одним целочисленным аргументом будет вызвана первая функция, при вызове функции с тем же именем, но с двумя целочисленными аргументами – вторая, а при указании в качестве аргумента адреса символьной строки – третья. При этом возвращаемый функцией тип роли не играет; функции

void Func(int);

int Func(int);

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

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

void Func(int);       @MyClass@Func$qi

void Func(int,int);   @MyClass@Func$qii

void Func(char*);     @MyClass@Func$qpzc

В результате функции, имеющие с нашей точки зрения одно и то же имя, для компилятора представляются различными.

Рассмотрим пример перегрузки функций-членов класса, воспользовавшись нашим простым классом Men:

class Men{

char* name;

int age;

public:

Men(char n,int a){name=n;age=a;}// Конструктор

void Age(int a){age=a;}// Запись возраста

int Age(){return age;}// Чтение возраста

};

Теперь и для инициализации, и для чтения переменной age мы используем один идентификатор Age (), хотя, в зависимости от состава используемых аргументов, он обозначает разные функции:

Men* pm = new Men("Орлов",18);// Вызов конструктора

pm->Age(19);// Коррекция возраста

int a = pm->Age();// Чтение возраста

Во втором предложении этого фрагмента функция Age () вызывается с целочисленным аргументом (типа int), и компилятор обращается к функции для записи возраста; в третьем предложении функция Age () вызывается без аргумента, и компилятор подставляет в программу вызов функции для чтения возраста.

Перегрузка конструкторов

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

Рассмотрим пример класса с перегруженными конструкторами. Пусть класс с именем Rect описывает прямоугольник, для которого можно задавать координаты (x и y) и длины сторон (w и h). Таким классом можно воспользоваться в приложении, формирующем на экране блок-схему программы или процесса. Координаты и размеры можно задавать парами целых чисел, однако в некоторых случаях может оказаться удобнее использовать структурные переменные типа POINT. Для реализации этой возможности в класс следует включить перегруженные конструкторы:

class Rect{

int x,y,w,h;// координаты и размеры

public:

Rect(int xx,int yy,int ww,int hh){// 4 аргумента типа int

x=xx; y=yy; w=ww; h=hh;

}

Rect(POINT ptCoords,POINT ptDims){// 2 аргумента типа POINT

x=ptCoords.x; y=ptCoords.y; w=ptDims.x; h=ptDims.y;

}

Rect(int xx,int yy){// Только координаты; размеры фиксированы

x=xx;y=yy;w=50;h=30;

}

};

Первый конструктор требует четыре целочисленных аргумента; при вызове второго конструктора необходимо указать две структурных переменных типа POINT; третий конструктор создает объект с задаваемыми координатами (в виде двух целых чисел), но с фиксированными размерами:

Rect r1(20,10,200,100);// x =20, y =10, w =200, h =100

POINT pc={250,50};

POINT pd={180,60};

Rect r2(pc,pd);// x =250, y =50, w =180, h =60

Rect r3(5,300);// x =5, y =300, w =50, h =30

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

class Men{

char* name;

int age;

public:

Men(char*n,int a){name=n;age=a;}// Обычный конструктор -

                             // инициализатор

Men(){}// Конструктор по умолчанию, создающий “пустой” объект

void SetName(char*n) {name=n;}

void SetAge(int a) {age=a;}

};

Заметьте, что при наличии конструктора по умолчанию в классе должна быть предусмотрена возможность инициализировать данные уже после создания объекта. В приведенном выше примере для этого в класс включены функции SetName () и SetAge (). Теперь создавать объекты класса можно двояко:

Men m1(”Громыко”,25);// Объект создается и сразу же

     // инициализируется конструктором-инициализатором

Men m2;// Пустой объект (создается конструктором по умолчанию)

m2.SetName(”Крымов”);// Инициализация пустого объекта

m2.SetAge(38);// вызовом соответствующих функций

Следует заметить, что реальные классы (как прикладные, так библиотечные) практически всегда содержат несколько, а иногда и много (до 10 – 15) перегруженных конструкторов.

Перегрузка операторов

Перегрузку операторов приходится применять во всех случаях, когда смысл привычных для нас операций, таких как сложение, умножение или присваивание, оказывается для объектов конкретного класса отличным от общепринятого. Например, сложение комплексных чисел заключается в почленном сложении как действительных, так и мнимых составляющих чисел-слагаемых. Однако обычный (глобальный) оператор “ + ” умеет складывать только два числа, а не две пары чисел. Таким образом, для класса комплексных чисел этому оператору следует придать другой смысл или, лучше сказать, другую реализацию, для чего и используется перегрузка. Часто перегрузка изменяет реализацию оператора, но его интуитивный смысл остается без изменения (как в приведенном примере с оператором “+”); в других случаях перегрузка совершенно изменяет назначение оператора. Например, глобальные операторы языка C++ << и >> служат для сдвига операнда влево или вправо на заданное число битов; однако при использовании с потоковыми объектами эти операторы приобретают смысл “вставить в поток” и “извлечь из потока”, о чем будет идти речь в одном из последующих разделов пособия.

Перегрузить можно почти любой оператор языка (=, +, *, ^, <, << и т. д.); как видно из этого перечня, перегружаются как одноместные операторы, применяемые к одному объекту (например, ++ или!), так и двуместные, требующие двух операндов (операторы сложения или умножения, логических операций и другие).

Перегрузка операторов осуществляется с помощью специальной своеобразной функции, имеющей имя operator (ее называют операторной функцией). Рассмотрим ее применение на примере перегрузки нескольких операторов для класса Point 3 D, описывающего координаты точки в трехмерном пространстве.

Будем (довольно естественно) считать, что равенство двух точек обозначает равенство их соответствующих координат, а сложение заключается в образовании точки, у которой каждая координата вычисляется как сумма соответствующих координат точек-операндов. В простейшем случае класс Point 3 D выглядит следующим образом:

class Point3D{

int x,y,z;// Данные-члены (координаты точки)

public:

Point3D(int xx,int yy,int zz){// Конструктор-инициализатор

x=xx; y=yy; z=zz;

}

Point3D(){};// Конструктор для создания “пустого” объекта

bool operator == (Point3D&);// Прототип функции перегрузки ==

Point3D operator+ (Point3D&);// Прототип функции перегрузки +

Point3D operator= (Point3D&);// Прототип функции перегрузки =

};

Как видно из приведенного фрагмента, все функции operator принимают в качестве аргумента объект класса; при этом функции перегрузки операторов + и =, которые должны образовывать новые объекты, возвращают объекты класса, а функция перегрузки оператора сравнения возвращает результат сравнения, т. е. булево значение.

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

bool operator == (Point3D);// Аргумент передается по значению

Определение функции operator для операции сравнения на равенство выглядит следующим образом:

bool Point3D::operator == (Point3D obj&){

if((x==obj.x)&&(y==obj.y)&&(z==obj.z)) return true;

else return false;

}

Для выяснения того, как работает эта функция, рассмотрим сначала строки программы, в которых используется оператор сравнения:

Point3D p1(5,10,15), p2(5,10,15);// Два равных объекта

if(p1==p2)...

Оператор == сравнивает об


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

Поперечные профили набережных и береговой полосы: На городских территориях берегоукрепление проектируют с учетом технических и экономических требований, но особое значение придают эстетическим...

Организация стока поверхностных вод: Наибольшее количество влаги на земном шаре испаряется с поверхности морей и океанов (88‰)...

Археология об основании Рима: Новые раскопки проясняют и такой острый дискуссионный вопрос, как дата самого возникновения Рима...

Историки об Елизавете Петровне: Елизавета попала между двумя встречными культурными течениями, воспитывалась среди новых европейских веяний и преданий...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.014 с.