Конструктор со многими аргументами — КиберПедия 

Адаптации растений и животных к жизни в горах: Большое значение для жизни организмов в горах имеют степень расчленения, крутизна и экспозиционные различия склонов...

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

Конструктор со многими аргументами

2020-05-08 1384
Конструктор со многими аргументами 0.00 из 5.00 0 оценок
Заказать работу

Конструктор класса Туре с двумя аргументами выглядит следующим образом:

Туре (string di. string gr): dimensions (di). grade (gr)

 

Этот конструктор копирует строковые аргументы в поля класса dimensions и grade.

Конструктор класса Distance тот же, что и в предыдущей программе:

Distance (int ft. float in): feet (ft). inches (in) { }

В конструктор класса Lumber включены оба этих конструктора, которые получают значения для аргументов. Кроме того, класс Lumber имеет и свои ар­гументы: количество материала и его цена. Таким образом, конструктор име­ет шесть аргументов. Он вызывает два конструктора, которые имеют по два аргумента, а затем инициализирует два собственных поля. Конструктор класса Lumber будет выглядеть следующим образом:

Lumber (string di. string gr. //  параметры для Type

int ft. float in.    //  параметры для Distance

int qu. float pre): //  наши собственные параметры

Type (di.gr).    //  вызов конструктора lype

Distance (ft. in). //  вызов конструктора Distance

quantity (qu). price (pre) // вызов наших конструкторов

t)

 

Неопределенность при множественном наследовании

В определенных ситуациях могут появиться некоторые проблемы, связанные со множественным наследованием. Здесь мы рассмотрим наиболее общую. Допус­тим, что в обоих базовых классах существуют методы с одинаковыми именами, а в производном классе метода с таким именем нет. Как в этом случае объект производного класса определит, какой из методов базовых классов выбрать? Одного имени метода недостаточно, поскольку компилятор не сможет вычис­лить, какой из двух методов имеется в виду. Эту ситуацию мы разберем в примере AMBIGU:

// ambigu.cpp

//демонстрация неопределенности при множественном наследовании

#include <iostream>

using namespace std;

///////////////////////////////////////////////////////////

class A

{

public:

void show () { cout << "Класс A\n"; }

};

class В {

public:

void show () { cout << "Класс B\n"; }

};

class С: public A, public В {

};

///////////////////////////////////////////////////////////

int main ()

{

С objC;   // объект класса С

// objC.show (); // так делать нельзя - программа не скомпилируется

objC.A::show (); // так можно

objC.A::show (); // так можно

return 0;

}

Проблема решается путем использования оператора разрешения, определя­ющего класс, в котором находится метод. Таким образом,

ObjC.A::show ():

направляет нас к версии метода showQ, принадлежащей классу А, а ObjC.В: -.show ():

направляет нас к методу, принадлежащему классу В. Б. Страуструп (см. прило­жение 3 «Библиография») называет это устранением неоднозначности.

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

// diamond.срр

// демонстрация наследования в форме ромба

#include <iostream>

using namespace std;

///////////////////////////////////////////////////////////

class A

{

public:

void func ();

};

class B: public A { };

class C: public A { };

class D: public B, public C {};

///////////////////////////////////////////////////////////

int main ()

{

D ObjD;

//ObjD.func (); // неоднозначность: программа не скомпилируется

return 0;

}

Классы В и С являются производными класса А, а класс D является произ­водным классов В и С. Трудности начинаются, когда объект класса D пытается воспользоваться методом класса А. В этом примере объект objD использует метод func(). Однако классы В и С содержат в себе копии метода func(), унасле­дованные от класса А. Компилятор не может решить, какой из методов исполь­зовать, и сообщает об ошибке.

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

Порядок выполнения работы

1. Изучите теоретический материал.

2. Напишите программу, согласно заданному в варианте задания условию.

3. Ответьте на контрольные вопросы.

 

ВАРИАНТЫ ЗАДАНИЙ

Вариант. 1

Представьте себе издательскую компанию, которая торгует книгами и аудио-записями этих книг. Создайте класс publication, в котором хранятся название (строка) и цена (типа float) книги. От этого класса наследуются еще два класса: book, который содержит информацию о количестве страниц в книге (типа int), и type, который содержит время записи книги в минутах (тип float). В каждом из этих трех классов должен быть метод getdata(), через который можно получать данные от пользователя с клавиатуры, и putdata(), предназначенный для вывода этих данных.

Напишите функцию main() программы для проверки классов book и type. Создайте их объекты в программе и запросите пользователя ввести и вы­вести данные с использованием методов getdataQ и putdata(). *2. Вспомните пример STRC0NV из главы 8. Класс String в этом примере имеет дефект: у него нет защиты на тот случай, если его объекты будут инициа­лизированы слишком длинной строкой (константа SZ имеет значение 80). Например, определение

String s = "Эта строка имеет очень большую длину и мы можем быть уверены, что она не уместится в отведенный буфер, что приведет к непредсказуемым последствиям.";

будет причиной переполнения массива str строкой s с непредсказуемыми последствиями вплоть до краха системы.

Создадим класс Pstring, производный от класса String, в котором предот­вратим возможность переполнения буфера при определении слишком длинной строковой константы. Новый конструктор производного класса будет копировать в str только SZ-1 символов, если строка окажется слиш­ком длинной, и будет копировать строку полностью, если она будет иметь длину меньшую, чем SZ. Напишите функцию main() программы для про­верки ее работы со строками разной длины.

Вариант. 2

Начните с классов book, type и publication из варианта 1. Добавьте базо­вый класс sales, в котором содержится массив, состоящий из трех значе­ний тина float, куда можно записать общую стоимость проданных книг за последние три месяца. Включите в класс методы getdata() для получения значений стоимости от пользователя и putdata() для вывода этих цифр. Измените классы book и type так, чтобы они стали производными обоих классов: publications и sales. Объекты классов book и type должны вводить и выводить данные о продажах вместе с другими своими данными. Напи­шите функцию main() для создания объектов классов book и type, чтобы протестировать возможности ввода/вывода данных.

Вариант. 3

Предположим, что издатель из вариантов 1 и 2 решил добавить к своей продукции версии книг на компьютерных дисках для тех, кто любит чи­тать книги на своих компьютерах. Добавьте класс disk, который, как book и type, является производным класса publication. Класс disk должен вклю­чать в себя те же функции, что и в других классах. Полем только этого класса будет тип диска: CD или DVD. Для хранения этих данных вы мо­жете ввести тип enum. Пользователь должен выбрать подходящий тип, на­брав на клавиатуре с или d.

Вариант. 4

Создайте производный класс employee2 от базового класса employee из программы EMPLOY этой главы. Добавьте в новый класс поле compensation типа double и поле period типа enum для обозначения периода оплаты ра­боты служащего: почасовая, понедельная или помесячная. Для простоты вы можете изменить классы laborer, manager и scientist так, чтобы они ста­ли производными нового класса employee2. Однако заметим, что во мно­гих случаях создание отдельного класса compensation и трех его производ­ных классов manager2, scientist2 и laborer2 более соответствовало бы духу ООП. Затем можно применить множественное наследование и сделать так, чтобы эти три новых класса стали производными класса compensation и первоначальных классов manager, scientist и laborer. Таким путем можно избежать модификации исходных классов.

Вариант. 5

Вспомним программу ARR0VER3. Сохраним класс safearray таким же и, используя наследование, добавим к нему возможность для пользо­вателя определять верхнюю и нижнюю границы массива в конструкторе.

Вариант. 6

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

Вариант. 7

В некоторых компьютерных языках, таких, как Visual Basic, есть опера­ции, с помощью которых можно выделить часть строки и присвоить ее другой строке. (В стандартном классе string предложены различные под­ходы.) Используя наследование, добавьте такую возможность в класс Pstring из упражнения 2. В новом производном классе Pstring2 разместите три новых функции: left(), mid() и rightQ.

s2.left (si. n) // в строку s2 помещаются n самых левых

// символов строки si s2.mid (si. s. n) // в строку s2 помещаются n символов из строки

// начиная с символа номер s s2.right (si. n) //в строку s2 помещаются n самых правых // символов строки si

Вы можете использовать цикл for для копирования символ за символом подходящих частей строки si во временный объект класса Pstring2, кото­рый затем их возвратит. Для получения лучшего результата используйте в этих функциях возврат по ссылке, чтобы они могли быть использованы с левой стороны знака «равно» для изменения части существующей строки.

Вариант. 8

Вспомним классы publication, book и type из варианта 1. Предположим, что мы хотим добавить в классы book и type дату выхода книги. Создайте новый производный класс publication, который является производным класса publication и включает в себя поле, хранящее эту дату. Затем из­мените классы book и type так, чтобы они стали производными класса publication вместо publication. Сделайте необходимые изменения функций классов так, чтобы пользователь мог вводить и выводить дату выхода книги.

Вариант. 9

В программе EMPMULT этой главы существует только один тип должно­сти менеджера. В любой серьезной компании кроме менеджеров сущест­вуют еще и управляющие. Создадим производный от класса manager класс executive. (Мы предполагаем, что управляющий — это главный менеджер.) Добавочными данными этого класса будут размер годовой премии и ко­личество его акций в компании. Добавьте подходящие методы для этих данных, позволяющие их вводить и выводить.

Вариант. 10

В различных ситуациях иногда требуется работать с двумя числами, объ­единенными в блок. Например, каждая из координат экрана имеет гори­зонтальную составляющую (х) и вертикальную (у). Представьте такой блок чисел в качестве структуры pair, которая содержит две переменные типа int. Теперь предположим, что мы хотим иметь возможность хранить перемен­ные типа pair в стеке. То есть мы хотим иметь возможность положить пе­ременную типа pair в стек путем вызова метода push() с переменной типа pair в качестве аргумента и вынуть ее из стека путем вызова метода рор(), возвращающего переменную типа pair. Начнем с класса Stack2 программы STAKEN из этой главы. Создадим производный от него класс pairStack. В нем будут содержаться два метода: перегружаемый метод push() и перегру­жаемый метод рор(). Метод pairStack::push() должен будет сделать два вы­зова метода Stack2::push(), чтобы сохранить оба числа из пары, а метод pairStack::pop() должен будет сделать два вызова метода Stack2::pop().

Вариант. 11

Рассмотрим старую Британскую платежную систему фунты-стерлинги- пенсы. Пенни в дальнейшем делятся на фартинги и полупенни. Фартинг — это 1/4 пенни. Существо­вали монеты фартинг, полфартинга и пенни. Любые сочетания монет вы­ражались через восьмые части пенни: 1/8 пенни — это полфартинга; 1/4 пенни — это фартинг; 3/8 пенни — это фартинг с половиной; 1/2 пенни — это полпенни; 5/8 пенни — это полфартинга плюс полпенни; 3/4 пенни — это полпенни плюс фартинг; 7/8 пенни — это полпенни плюс фартинг с половиной. Давайте предположим, что мы хотим добавить в класс sterling возмож­ность пользоваться этими дробными частями пенни. Формат ввода/выво­да может быть похожим на £1.1.1-1/4 или £9.19.11-7/8, где дефисы отде­ляют дробные части от пенни.

Создайте новый класс sterfrac, производный от класса sterling. В нем долж­на быть возможность выполнения четырех основных арифметических опе­раций со стерлиш-ами, включая восьмые части пенни. Поле eighths типа int определяет количество восьмых. Вам нужно будет перегрузить методы класса sterling, чтобы они могли работать с восьмыми частями. Пользова­тель должен иметь возможность ввести и вывести дробные части пенни. Не обязательно использовать класс fraction полностью, но вы можете это сделать для большей точности.

Форма отчёта: отчет выполняется в текстовом редакторе в электронном виде.

Содержание отчета:

4. Тема работы.

5. Цель работы.

6. Выполненные задания.

 

Система оценки: пятибальная.

 


Контрольные вопросы

1. Назначение наследования состоит в том, чтобы:

а) создавать более общие классы в более специализированных;

б) передавать аргументы объектам классов;

в) добавлять возможности к существующим классам без их модифика­ции;

г) улучшать сокрытие данных и их инкапсуляцию.

2. Класс-наследник называется_________от базового класса.

3. Преимущество использования наследования заключается в:

а) обеспечении развития класса путем естественного отбора;

б) облегчении создания классов;

в) избежании переписывания кода;

г) предоставлении полезной концептуальной основы.

4. Напишите первую строку описания класса Bosworth, который является public-производным класса Alphonso.

5. Будет ли правильным утверждение: создание производного класса требу­ет коренных изменений в базовом классе?

6. Члены базового класса для доступа к ним методов производного класса должны быть объявлены как public или _____________.

7. Пусть базовый класс содержит метод basefunc(), а производный класс не имеет метода с таким именем. Может ли объект производного класса иметь доступ к методу basefunc()?

8. Допустим, что класс, описанный в вопросе 4, и класс Alphonso содержат метод alfunc(). Напишите выражение, позволяющее объекту BosworthObj класса Bosworth получить доступ к методу alfuncQ.

9. Истинно ли следующее утверждение: если конструктор производного клас­са не определен, то объекты этого класса будут использовать конструкто­ры базового класса?

10. Допустим, что базовый и производный классы включают в себя методы с одинаковыми именами. Какой из методов будет вызван объектом произ­водного класса, если не использована операция разрешения имени?

11. Напишите объявление конструктора без аргументов для производного класса Bosworth из вопроса 4, который будет вызывать конструктор без ар­гументов класса Alphonso.

12. Оператор разрешения обычно:

а) ограничивает видимость переменных для определенных методов;

б) обозначает, от какого базового класса создан производный;

в) определяет отдельный класс;

г)  разрешает неопределенности.

13. Истинно ли следующее утверждение: иногда полезно создать класс, объ­ектов которого никогда не будет создано?

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

15. Предположим, что класс Derv является частным производным класса Base. Мы определяем объект класса Derv, расположенный в функции main(). Через него мы можем получить доступ к:

а) членам класса Derv, объявленным как public;

б) членам класса Derv, объявленным как protected;

в) членам класса Derv, объявленным как private;

г)  членам класса Base, объявленным как public;

д) членам класса Base, объявленным как protected;

е) членам класса Base, объявленным как private.

16. Истинно ли следующее утверждение: класс D может быть производным класса С, который в свою очередь является производным класса В, кото­рый производный класса А?

17. Иерархия классов:

а) показывает те же взаимоотношения, что и схема организации;

б) описывает взаимоотношение типа «имеет»;

в) описывает взаимоотношения типа «является частью»;

г)  показывает те же взаимоотношения, что и наследственное дерево.

18. Напишите первую строку описания класса Tire, который является произ­водным классов Wheel и Rubber.

19. Предположим, что класс Derv является производным класса Base. Оба класса содержат метод func() без аргументов. Напишите выражение, входя­щее в метод класса Derv, которое вызывает метод func() базового класса.


Список использованной литературы

 

1. Х.Дейтел Как программировать на C; Пер. с англ. под ред. В. Тимофеева. - М.: БИНОМ, 2000. - 1005 с.: ил.; 24

2. Язык программирования С / Брайан Керниган, Деннис Ритчи; [пер. с англ. и ред. В. Л. Бродового]. - 2-е изд., перераб. и доп. - Москва [и др.]: Вильямс, 2007. - 289 с.; 23 см.

3. Бьерн Страуструп Язык программирования C++; Пер. с англ. С. Анисимова и М. Кононова под ред. Ф. Андреева, А. Ушакова. - 3. изд. - М.: Binom Pablishers; СПб.: Нев. диалект, 2004. - 990 с.: ил.; 24 см.

ПРАКТИЧЕСКОЕ ЗАНЯТИЕ 12

Тема: «Разработка программ на Си++ с использованием свойств полиморфизма и виртуальных функций»

Цели:

- определить понятия полиморфизма и виртуальных функций;

- рассмотреть основные случаи применения виртуальных функций в С++;

- рассмотреть основные случаи применения чистых виртуальных функций в С++;

 

Характер занятия: репродуктивный.

Форма организации: индивидуальная.

Обеспечение занятия: тетрадь с лекциями,  СИ, компьютер.

 

Требования к знаниям студентов

Перед выполнением практической работы студент должен

знать:

- понятия класс и объект, абстрактный класс;

- основные особенности абстрактных классов;

- понятия конструктор и деструктор.

 

после выполнения практической работы студент должен

уметь:

- создавать программы, используя частичные виртуальные функции и виртуальные функции в языке С++;

- определять необходимость создания абстрактных классов.

 

 

Теоретический материал

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

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

shape * ptarr [100]: //массив из 100 указателей на фигуры

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

for(int j =0: j <N; j++) ptarr[j]->draw():

Абсолютно разные функции выпол­няются с помощью одного и того же вызова. Если указатель в массиве ptarr указывает на шарик, вызовется функция, рисующая шарик, если он указывает на треугольник, то рисуется треугольник. Вот это и есть полиморфизм, то есть различные формы. Функции выглядят одинаково — это выражение draw(), но реально вызываются разные функции, в зависимости от значения ptarr [ j ]. Поли­морфизм — одна из ключевых особенностей объектно-ориентированного про­граммирования (ООП) наряду с классами и наследованием.

Чтобы использовать полиморфизм, необходимо выполнять некоторые усло­вия. Во-первых, все классы (все эти треугольнички, шарики и т. д.) должны яв­ляться наследниками одного и того же базового класса. В MULTSHAP этот класс называется shape. Во-вторых, функция draw() должна быть объявлена виртуаль­ной (virtual) в базовом классе.

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

 

Доступ к обычным методам через указатели

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

// notvirt.cpp

// Доступ к обычным функциям через указатели

#include <iostream>

using namespace std;

///////////////////////////////////////////////

class Base // Базовый класс

{

public:

void show() // Обычная функция

{ cout << "Base\n"; } };

//////////////////////////////////////////////

class Dervl: public Base // Производный класс 1

{

public:

void show()

{ cout << "Dervl\n"; } };

//////////////////////////////////////////////

class Derv2: public Base // Производный класс 2

{

public: void show()

{ cout << "Derv2\n"; } };

//////////////////////////////////////////////

int main() {

Dervl dv1; //Объект производного класса 1

Derv2 dv2; //Объект производного класса 2

Base* ptr; //Указатель на базовый класс

ptr = &dv1;   //Адрес dvl занести в указатель

ptr->show(); //Выполнить showO

ptr = &dv2;   //Адрес dv2 занести в указатель

ptr->show(); // Выполнить showO

return 0;

}

Итак, классы Dervl и Derv2 являются наследниками класса Base. В каждом из этих трех классов имеется метод show(). В main() мы создаем объекты классов Dervl и Derv2, а также указатель на класс Base. Затем адрес объекта порожденно­го класса мы заносим в указатель базового класса:

ptr = &dvl: //Адрес объекта порожденного класса заносим в //указатель базового

Компилятор не выдаст ошибку потому, что указатели на объекты порож­денных классов совместимы по типу с указателями на объекты базового класса.

Теперь хорошо бы понять, какая же, собственно, функция выполняется в этой строчке:

ptr->show();

Это функция Base::show() или Dervl::show()? Опять же, в последних двух строчках программы NOTVIRT мы присвоили указателю адрес объекта, при­надлежащего классу Derv2, и снова выполнили

ptr -> show ():

Так какая же из функций show() реально вызывается? Результат выполнения программы дает простой ответ:

.Base Base

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

 

Доступ к виртуальным методам через указатели

Давайте сделаем одно маленькое изменение в нашей программе: поставим клю­чевое слово virtual перед объявлением функции show() в базовом классе. Вот лис­тинг того, что получилось — программы VIRT:

// virt.cpp

// Доступ к виртуальным функциям через указатели

#include <iostream>

using namespace std;

///////////////////////////////////////////////

class Base // Базовый класс

{

public:

virtual void show() // Виртуальная функция

{ cout << "Base\n"; }

};

//////////////////////////////////////////////

class Derv1: public Base // Производный класс 1

{

public: void show()

{ cout << "Dervl\n"; }

};

//////////////////////////////////////////////

class Derv2: public Base // Производный класс 2

{

public: void show()

{ cout << "Derv2\n"; }

};

//////////////////////////////////////////////

int main () {

Derv1 dv1; //Объект производного класса 1

Derv2 dv2; //Объект производного класса 2

Base* ptr; //Указатель на базовый класс

ptr = &dv1;   //Адрес dvl занести в указатель

ptr->show(); //Выполнить showO

ptr = &dv2;   //Адрес dv2 занести в указатель

ptr->show(); // Выполнить showO

return 0;

}

На выходе имеем:

Dervl Derv2

Теперь, как видите, выполняются методы производных классов, а не базового. Мы изменили содержимое ptr с адреса из класса Dervl на адрес из класса Derv2, и изменилось реальное выполнение showQ. Значит, один и тот же вызов

ptr->show()

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

 В программе NOTVIRT у компилятора нет проблем с выражением

ptr->show{):

Он всегда компилирует вызов функции show() из базового класса. Однако в программе VIRT компилятор не знает, к какому классу относится содержимое ptr. Ведь это может быть адрес объекта как класса Dervl, так и класса Derv2. Какую именно версию draw() вызывает компилятор — тоже загадка. Па самом деле компилятор не очень понимает, что ему делать, поэтому откладывает принятие решения до фактического запуска программы. А когда программа уже поставле­на на выполнение, когда известно, на что указывает ptr, тогда будет запущена соответствующая версия draw. Такой подход называется поздним связыванием или динамическим связыванием. (Выбор функций в обычном порядке, во время компиляции, называется ранним связыванием или статическим связыванием.) Позднее связывание требует больше ресурсов, но дает выигрыш в возможностях и гибкости.

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

 

Абстрактные классы и чистые виртуальные функции

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

Как нам объяснить людям, использующим созданную нами структуру клас­сов, что объекты родительского класса не предназначены для реализации? Мож-ао, конечно, заявить об этом в документации, но это никак не защитит наш базо-зый класс от использования не но назначению. Надо защитить его программно. Для этого достаточно ввести в класс хотя бы одну чистую виртуальную функ­цию. Чистая виртуальная функция — это функция, после объявления которой добавлено выражение =0. Продемонстрируем сказанное в примере VIRTPURE:

// virtpure. cpp

// Чистая вир!уальная функция

#include <iostream>

using namespace std;

/////////////////////////////////////////////////

class Base //базовый класс

{

public:

virtual void show () = 0;

//чистая виртуальная

//функция

};

//////////////////////////////////////////////////

class Derv1: public Base //порожденный класс 1

{

public:

void show() { cout << "Derv1\n"; }

};

//////////////////////////////////////////////////

class Derv2: public Base //порожденный класс 2

{

public: void show() {cout << "Derv2\n"; }

};

/////////////////////////////////////////////////

int main () {

// Base bad;  //невозможно создать объект

//из абстрактного класса

Base * arr [2]; //массив указателей на

//базовый класс

Derv 1 dv 1; //Объект производного класса 1

Derv 2 dv 2; //Объект производного класса 2

arr [0] = & dv 1; //Занести адрес dvl в массив

arr [1] = & dv 2;//Занести адрес dv 2 в массив

arr [0]-> show (); //Выполнить функцию showO

arr [1]-> show (); //над обоими объектами

return 0; }

Здесь виртуальная функция show() объявляется так:

 

virtual void show() = 0; //чистая виртуальная функция

Знак равенства не имеет ничего общего с операцией присваивания. Нулевое значение ничему не присваивается. Конструкция =0 — это просто способ сооб­щить компилятору, что функция будет чистой виртуальной. Если теперь в main() попытаться создать объект класса Base, то компилятор будет недоволен тем, что объект абстрактного класса пытаются реализовать. Он выдаст даже имя чистой виртуальной функции, которая делает класс абстрактным. Помните, что хотя это только объявление, но определение функции show() базового класса не явля­ется обязательным. Впрочем, если вам надо его написать, это можно сделать.

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

Между прочим, мы внесли еще одно, не связанное с предыдущими, измене­ние в VIRTPURE: теперь адреса методов хранятся в массиве указателей и доступ­ны как элементы этого массива. Обработка этого, впрочем, ничем не отличается от использования единственного указателя. VIRTPURE выдает результат, не отли­чающийся от VIRT:

Dervl Derv2

 

Виртуальные функции и класс person

Теперь, уже зная, что такое виртуальные функции, рассмотрим области их при­менения. Примером будет служить расширение программ PTR0BJ и PERS0RT. Новая программа использует все тот же класс person, но добавлены два новых класса: student и professor. Каждый из этих производных классов содержит метод isOutstanding(). С помощью этой функции администра­ция школы может создать список выдающихся педагогов и учащихся, которых следует наградить за их успехи. Листинг программы VIRTPERS:

// vitrpers.срр

// виртуальные функции и класс person

#include <iostream>

using namespace std;

///////////////////////////////////////////////

class person // класс person

{

protected:

char name[40];

public: void getName()

{ cout << " Введите имя: ";

 cin >> name; }

void putName()

{ cout << " Имя: " << name << endl; }

virtual void getData() = 0;   // чистые

virtual bool isOutstanding() =0; // виртуальные

// функции

};

////////////////////////////////////////////////

class student: public person // класс student

{

private:

float gpa; // средний балл

public:

void getData() //запросить данные об ученике у

{  //пользователя

person::getName();

cout << " Средний балл ученика: ";

cin >> gpa;}

bool isOutstanding() { return (gpa > 3.5)? true: false; }

};

////////////////////////////////////////////////

class professor: public person // класс professor

{

private:

int numPubs; // число публикаций

public:

void getData() //запросить данные о педагоге у

{  //пользователя

person::getName();

cout << " Число публикаций: ";

cin >> numPubs; }

bool isOutstanding() { return (numPubs > 100)? true: false; }

};

////////////////////////////////////////////////

int main() {

person* persPtr[100]; // массив указателей на person

int n = 0; //число людей, внесенных в список

char choice;

do {

cout << " Учащийся (s) или педагог (p): ";

cin >> choice;

if(choice=='s') // Занести нового ученика

persPtr[n] = new student; // в массив

else // Занести нового

persPtr[n] = new professor; // педагога в массив

persPtr[n++]->getData(); //Запрос данных о персоне

cout << " Ввести еще персону (у/п)? "; //создать еще

//персону

cin >> choice;

} while(choice=='y'); // цикл, пока ответ ' у '

for(int j=0; j<n; j++); {

persPtr[j]->putName();  //Вывести все имена,

if(persPtr[j]->isOutstanding()) // сообщать о

cout << " Это выдающийся человек!\n"; //выдающихся

}

return 0;

}  //Конец main()

 

Классы

Класс person — абстрактный, так как содержит чистые виртуальные функции getData()n isOutstanding(). Никакие объекты класса person не могут быть созданы. Он существует только в качестве базового класса для student и professor. Эти по­рожденные классы добавляют новые экземпляры данных для базового класса. Student содержит переменную gpa типа float, представляющую собой средний балл учащегося. В классе Professor мы создали переменную numPubs типа int, кото­рая представляет собой число публикаций педагога. Учащиеся со средним бал­лом свыше 3,5 и педагоги, опубликовавшие более 100 статей, считаются выла­ющимися.

Функция isOutstanding()

IsOutstandingO объявлена чистой виртуальной функцией в классе person. В клас­се student эта функция возвращает true, если gpa больше 3,5, в противном слу­чае — false. В классе professor она возвращает true, если numPubs больше 100.

Функция GetData() запрашивает у пользователя GPA, если она запускается для обслуживания класса student, или число публикаций для professor.

Программа main()

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

Учащийся (s) или педагог (р): s Введите имя: Сергеев Михаил Средний балл ученика: 1.2 Ввести еще персону (у/n)? у Учащийся (s) или педагог (р): s Введите имя: Пупкин Василий Средний балл ученика: 3.9 Ввести еще персону (у/n)? у Учащийся (s) или педагог (р): s Введите имя: Борисов Владимир Средний балл ученика: 4.9 Ввести еще персону (у/n)? у Учащийся (s) или педагог (р): р Введите имя: Михайлов Сергей Число публикаций: 714 Ввести еще персону (у/n)? у Учащийся (S) ипи педагог (р): р Введите имя: Владимиров Борис Число публикаций: 13 Ввести еще персону (у/n)? п

Имя: Сергеев Михаил Имя: Пупкин Василий Это выдающийся человек! Имя: Борисов Владимир Зто выдающийся человек! Имя: Михайлов Сергей Это выдающийся человек! Имя: Владимиров Борис

 

Виртуальные деструкторы

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

//vertdest.срр

//Тест невиртуальных и виртуальных деструкторов

#include <iostream>

using namespace std;

///////////////////////////////////////////////////////////

class Base {

public:

~Base() // невиртуальный деструктор

//virtual ~Base()

// виртуальный деструктор

{ cout << "Base удален \n"; }

};

///////////////////////////////////////////////////////////

class Derv: public Base {

public: ~Derv()

{ cout << "Derv удален \n"; }

};

///////////////////////////////////////////////////////////

int main() {

Base* pBase = new Derv;

delete pBase;

return 0; }

Программа выдает такой результат:

Base удален

Это говорит о том, что деструктор для Derv не вызывается вообще! К такому результату привело то, что деструктор базового класса в приведенном листинге невиртуальный. Исправить это можно, закомментировав первую строчку опре­деления деструктора и активизировав вторую. Теперь результатом работы про­граммы является:

Derv удален Base удален

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

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

 

Виртуальные базовые классы

Рассмотрим ситуацию, представленную на рис. 12.1. Базовым классом явля­ется parent, есть два порожденных класса — childl, child2 и есть еще четвертый класс — Grandchild, порожденный одновременно классами Childl и Child2.

В такой ситуации проблемы могут возникнуть, если метод класса Grandchild захочет получить доступ к данным или функциям класса Parent. Что в этом слу­чае будет происходить, показано в программе N0RMBASE.


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

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

Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции...

Эмиссия газов от очистных сооружений канализации: В последние годы внимание мирового сообщества сосредоточено на экологических проблемах...

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



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

0.429 с.