Производные классы являются эффективным средством для расширения функциональных возможностей существующих классов без перепрограммирования и повторной компиляции существующих программ.
При создании объектов производного класса сначала автоматически вызываются конструкторы базовых классов согласно списку базовых классов в объявлении производного класса, а затем конструктор производного класса. Конструкторы базовых классов вызываются независимо от того, перечислены они явно в конструкторе производного класса или нет. Объекты разрушаются в порядке, обратном их созданию, т.е. вначале вызывается деструктор производного класса, а затем базового.
Один класс может быть базовым для нескольких производных, производный класс может быть, в свою очередь, базовым для какого-либо класса и т.д. Если производный класс имеет несколько базовых, то такое наследование называется множественным. Иногда базовый класс называют родительским, а производный – потомком.
В объекте производного класса наряду с собственными полями создаются поля, определенные в базовом классе, а объект производного класса получает доступ к методам базового. В результате объект производного класса может обращаться, соблюдая определенные правила доступа, к компонентам базового класса как к собственным, в чем и заключается наследование.
Если доступ к собственным компонентам производных классов определяется обычным образом, то на доступ к наследуемым компонентам влияет, во-первых, атрибут доступа в базовом классе и, во–вторых, модификатор доступа (public / private), указанный перед именем базового класса в конструкции определения производного класса.
На диаграммах отношение наследования изображается сплошной линией, начинающейся у производного класса и заканчивающейся стрелкой-пустым треугольником у базового класса.
В производном классе могут быть определены поля и методы с той же сигнатурой, что и в базовом классе. Но это уже члены производного класса и никакое наследование здесь ни при чем.
В иерархии классов часто используются виртуальные функции. Виртуальной называется функция, независимо определенная в базовом и в каждом из группы производных классов и имеющая в них общую сигнатуру с атрибутом virtual в базовом классе. Независимое определение означает, что в производных классах для нее создаются новые реализации:
class Based {
/* ...*/
public:
virtual int fb(){return 3;}
/* ...*/
};
class Derived:public Based {
/* ...*/
int fb() {return 5;}
/* ...*/
};
Виртуальная функция класса может вызываться обычным образом - как член этого класса через его объект. Но тогда никаких преимуществ по сравнению с не виртуальной функцией ее применение не приносит. Причина, по которой виртуальные функции так широко используются, состоит в том, производный класс, объект которого нам нужно создать в какой-то момент выполнения программы, определяется именно в этот момент, а не на стадии компиляции программы. Для реализации этой возможности обращения к виртуальным функциям производится через указатель на базовый класс. Т.е. сообщения передаются как указатели, которые указывают на объект вместо прямой передачи объекту.
В настоящей лабораторной работе можно использовать атрибут virtual в базовом классе и вызывать такие функции обычным образом; правильный вызов подробно рассматривается в следующей лабораторной работе.
Виртуальная функция в базовом классе может быть нулевой. В нашем случае
virtual int fb( )=0;
В этом случае базовый класс называется абстрактным; объект такого класса создать нельзя, только указатель на объект. Мало того, если в каком-либо производном классе эта функция отсутствует, то от такого класса тоже нельзя создавать объекты (в C# такие классы называются бесплодными).
Пример выполнения работы.
Создать базовый класс Vehicle. На его основе реализовать классы Car (автомобиль) и Lorry (грузовик). Классы должны иметь возможность задавать и получать параметры средств передвижения (цена, максимальная скорость, год выпуска и т.д.). Наряду с общими полями и методами, каждый класс должен содержать и специфичные для него компоненты.
В базовом классе ограничимся двумя общими полями типа protected: ценой и годом выпуска. Кроме того, введем идентификатор типа ID: 'C' для автомобилей и 'L' для грузовиков. Так как поля класса должны быть защищены, то введем общие методы для установления значения полей.
//Базовый класс
class Vehicle{
protected:
int price;//цена
int year; //год выпуска
char ID; // признак
public:
//Устанавливаем цену, год выпуска и идентификатор
void SetVehicle(int _price, int _year)
{price=_price; year=_year;}
void SetID(char _ID)
{ID=_ID;}
};
//Производный класс
class Lorry:public Vehicle{
int capacity; // грузоподъемность
public:
//Устанавливаем грузоподъемность
void SetCapacity(int _capacity)
{capacity=_capacity;}
};
// Производный класс
class Car: public Vehicle{
int speed; // скорость
public:
//устанавливаем скорость
void SetSpeed(int _speed){speed=_speed;}
};
/*Собственно, все. Теперь в main можно создавать объекты Lorry и Car и вводить данные, например, так:
Lorry lo;
lo. SetVehicle(10000, 2012);
lo. Capacity(5000);
Очевидно, что в программе мы будем иметь дело не с 1-м, а с несколькими автомобилями, т.е. с массивом, который надо организовать и обрабатывать. Чтобы не заставлять клиента заниматься этим, введем класс Garage, включив в него разработанные нами типы. ( см. замечания к работе 1).
Вот как он может выглядеть (продолжаем программу): */
class Garage{
int n; // Количество машин в гараже
Lorry *lor; // указатели на классы
Car *car;
public:
//Конструкторы
Garage(int _n)// для создания массива грузовиков
{n=_n; lor=new Lorry[n]; car=0;}//В объекте, создаваемым этим //конструктором указатель lor значимый,a car=0 – см. деструктор класса
//Второй конструктор в классе обязательно должен отличаться от первого списком //параметров
Garage(int _n, char _ID) // для создания массива автомобилей
{n=_n; car=new Car[n]; lor=0;} ;}//В объекте, создаваемым этим //конструктором указатель car значимый,a lor=0 – см. деструктор класса
// Ввод данных для автомобилей
void InCar()
{ int _price, _year, _speed; //Вспомогательные переменные
{ cout<<"Input values price, year, capacity for "<<i+1<< "
Lorry"<<endl;
cin>>_price>>_year>>_capacity;
lor[i].SetVehicle(_price,_year);
lor[i].SetCapacity(_capacity);
lor[i].SetID('L');
}
}
// Деструктор
~Garage(){ if(car==0)delete[] lor;
if(lor==0)delete[] car;
}
};
//Теперь наш клиент приобретает совсем простой вид
int _tmain(int argc, _TCHAR* argv[])
{
Garage g1(2); //Объект - массив из 2-х грузовиков
g1.InLorry(); // Ввод данных
Garage g2(4,'C'); //Объект - массив из 4-х авто
g2.InCar();
return 0;
}
В классе Garage можно инкапсулировать все методы работы с объектами: сортировку, ввод-вывод в файл и т.п.
Я не утверждаю, что приведенный вариант класса Garage является идеальным. Возможно, этот класс можно было сделать наследником Lorry и Car. Может быть, решение было бы проще ( проверьте). Но дело в том, что у нас появилось бы множественное наследование, а его, по возможности, надо избегать. Впрочем, я не возражаю и против такого решения ваших задач.
Варианты заданий
1. Создать базовый класс CList (линейный однонаправленный cписок) с полями: указатели на начало списка, на его конец и на следующий элемент; информационная часть – целое число. В производных классах – CQueue (очередь) и CStack (стек) – должны быть определены методы вставки и удаления узла в соответствии с дисциплиной обслуживания соответствующего класса.
2. Описать базовый класс CString (Строка) и производные CStringBit (Битовая_строка) и CStringHex (Шестнадцетиричная_строка). (Описание класса см. вариант 9 в 1-й работе.) Строки первого подкласса могут содержать только двоичные символы, второго только шестнадцетиричные. Разработать методы ввода данных с проверкой допустимых символов. Содержимое строк рассматривается как двоичное или шестнадцетиричное число без знака. Разработать операции сравнения двух строк, сложения и вычитания. Создать класс, содержащий массив объектов производных классов в динамической памяти.
3. Создать абстрактный класс Point (точка). На его основе создать классы ColoredPoint и Line. На основе класса Line создать класс ColoredLine и класс PolyLine (многоугольник). Все классы должны иметь методы установки и получения значений всех координат, а также изменения цвета и получения текущего цвета. Создать класс Picture, содержащий массив объектов этих классов в динамической памяти. Предусмотреть возможность ввода и вывода характеристик объектов.
4. Создать абстрактный класс Figure. На его основе реализовать классы Rectangle (прямоугольник), Circle (круг) и Trapeze (трапеция) с возможностью вычисления площади, центра тяжести и периметра. Создать класс Picture, содержащий массив объектов этих классов в динамической памяти. Предусмотреть возможность ввода и вывода характеристик объектов.
5. Создать абстрактный класс Number c виртуальными методами, реализующими арифметические операции. На его основе реализовать классы Integer и Real. Создать класс Series (набор), содержащий массив объектов этих классов в динамической памяти. Предусмотреть возможность ввода и вывода характеристик объектов.
6. Создать абстрактный класс Body. На его основе реализовать классы Parallelepiped (прямоугольный параллелепипед), Cone (конус) и Ball (шар) с возможностью вычисления площади поверхности и объема. Создать класс Series (набор), содержащий массив объектов этих классов в динамической памяти. Предусмотреть возможность вывода характеристик объектов списка.
7. Создать абстрактный класс Currency для работы с денежными суммами. Определить в нем методы перевода в рубли и вывода на экран. На его основе реализовать классы Dollar, Euro и Pound (фунт стерлингов) с возможностью пересчета в центы и пенсы соответственно. Создать класс Purse (кошелек), содержащий массив объектов этих классов в динамической памяти. Предусмотреть возможность вывода общей суммы, переведенной в рубли, и суммы по каждой из валют.
8. Создать абстрактный класс Triangle (треугольник), задав в нем длину двух сторон, угол между ними, методы вычисления площади и периметра. На его основе создать классы, описывающие равносторонний, равнобедренный и прямоугольный треугольники со своими методами вычисления площади и периметра. Создать класс Picture, содержащий массив объектов этих классов в динамической памяти. Предусмотреть возможность вывода характеристик объектов списка и получения суммарной площади..
9. Создать абстрактный класс Solution (решение) с методами вычисления корней уравнения и вывода на экран. На его основе реализовать классы Linear (линейное уравнение) и Square (квадратное уравнение). Создать класс Series (набор), содержащий массив объектов этих классов в динамической памяти. Предусмотреть возможность ввода и вывода характеристик объектов списка..
10. Создать абстрактный класс Function (функция) с виртуальными методами вычисления значения функции y = f(x) в заданной точке х и вывода результата на экран. На его основе реализовать классы Ellipse, Hiperbola и Parabola. Создать класс Series (набор), содержащий массив/параметризованную коллекцию объектов этих классов в динамической памяти. Предусмотреть возможность вывода характеристик объектов списка.
11. Создать абстрактный класс Triad (тройка) с виртуальными методами увеличения на 1. На его основе реализовать классы Date (дата) и Time (время). Создать класс Memories, содержащий массив пар (дата-время) объектов этих классов в динамической памяти. Предусмотреть возможность вывода характеристик объектов списка и выборки самого раннего и самого позднего событий.
12. Описать абстрактный класс Element (элемент логической схемы), задав в нем числовой идентификатор, количество входов, идентификаторы присоединенных к нему элементов (до 10) и двоичные значения на входах и выходе. На его основе реализовать классы AND и OR — двоичные вентили, которые могут иметь различное количество входов и один выход и реализуют логическое умножение и сложение соответственно. Создать класс Sсheme (схема), содержащий массив объектов этих классов в динамической памяти. Предусмотреть вычисление значений, формируемых на выходах схемы по заданным значениям входов.
13. Описать абстрактный класс Element (элемент логической схемы) задав в нем символьный идентификатор, количество входов, идентификаторы присоединенных к нему элементов (до 10) и двоичные значения на входах и выходе. На его основе реализовать классы AND_NOT и OR_NOT — двоичные вентили, которые могут иметь различное количество входов и один выход и реализуют логическое умножение c отрицанием и сложение c отрицанием соответственно. Создать класс Sсheme (схема), содержащий массив объектов этих классов в динамической памяти. Предусмотреть вычисление значений, формируемых на выходах схемы по заданным значениям входов.
14. Описать абстрактный класс Trigger (триггер), задав в нем идентификатор и двоичные значения на входах и выходах. На его основе реализовать классы RS и JK, представляющие собой триггеры соответствующего типа. Создать класс Register (регистр), содержащий массив объектов этих классов в динамической памяти. Предусмотреть общий сброс и установку значений каждого триггера по заданным значениям входов.
15. Создать абстрактный класс Progression (прогрессия) с виртуальными методами вычисления заданного элемента и суммы прогрессии. На его основе реализовать классы Linear (арифметическая) и Exponential (геометрическая). Создать класс Series (набор), содержащий массив объектов этих классов в динамической памяти. Предусмотреть вывод общей суммы всех прогрессий.
16. Создать абстрактный класс Pair (пара значений) с виртуальными методами, реализующими арифметические операции. На его основе реализовать классы Fractional (дробное) и LongLong (длинное целое).В классе Fractional вещественное число представляется в виде двух целых, в которых хранятся целая и дробная часть числа соответственно. В классе LongLong длинное целое число хранится в двух целых полях в виде старшей и младшей части. Создать класс Series (набор), содержащий массив объектов этих классов в динамической памяти. Предусмотреть возможность вывода характеристик объектов списка и вывода общей суммы всех значений..
17. Создать абстрактный класс Integer (целое) с символьным идентификатором, виртуальными методами, реализующими арифметические операции, и методом вывода на экран. На его основе реализовать классы Decimal (десятичное) и Binary (двоичное). Число представить в виде массива цифр. Создать класс Series (набор), содержащий массив/параметризованную коллекцию объектов этих классов в динамической памяти. Предусмотреть возможность вывода значений и идентификаторов всех объектов списка и вывода общей суммы всех десятичных значений.
18. Создать класс Sorting (сортировка) с идентификатором последовательности, виртуальными методами сортировки, получения суммы и вывода на экран. На его основе реализовать классы Choice (метод выбора) и Quick (быстрая сортировка). Создать класс Series (набор), содержащий массив в динамической памяти. Предусмотреть возможность вывода идентификаторов и сумм элементов каждого объекта списка, а также вывода общей суммы всех значений.
19. Создать класс Pair (пара значений) с виртуальными методами, реализующими арифметические операции, и методом вывода на экран. На его основе реализовать классы Money (деньги) и Complex (комплексное число). В классе Money денежная сумма представляется в виде двух целых, в которых хранятся рубли и копейки соответственно. При выводе части числа снабжаются словами «руб.» и «коп.». В классе Complex предусмотреть при выводе символ мнимой части (i). Создать класс Series (набор), содержащий массив объектов этих классов в динамической памяти. Предусмотреть возможность вывода объектов списка.
20. Создать класс Worker с полями, задающими фамилию работника, фамилии руководителя и подчиненных и методами вывода списка обязанностей и списка подчиненных на экран. На его основе реализовать классы Manager (руководитель проекта), Developer (разработчик) и Coder (младший программист) с соответствующими методами. Создать класс Group (группа), содержащий массивы объектов в динамической памяти. Предусмотреть возможность вывода всех объектов списка и выборки по фамилии с выводом всего дерева подчиненных.
Контрольные вопросы
1. В чем заключается наследование одного класса другому? В чем разница в организации
наследования полей и методов?
2. Удачной ли является иерархия классов, при которой некоторый класс Х является производным от большого количества классов с большим числом полей в каждом ( AßB ß C ß…ß X)? Какая существует альтернатива наследованию?
3. Каким образом производится управление доступом к унаследованным компонентам производных классов? Есть ли в представленном фрагменте программы ошибки?