Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лабораторные работы по ООП.doc
Скачиваний:
2
Добавлен:
01.05.2025
Размер:
613 Кб
Скачать
☆

Цель работы

Ознакомление с отношением наследования.

Основные сведения

Базовый и производные классы

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

Формат определения производного класса следующий:

тип_класса имя_производного_класса : список[ модификатор_доступа имя_базового_класса] {определение_производного_класса};

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

Один класс может быть базовым для нескольких производных, производный класс может быть, в свою очередь, базовым для какого-либо класса и т.д. Если производный класс имеет несколько базовых, то такое наследование называется множественным. Иногда базовый класс называют родительским, а производный – потомком.

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

Если доступ к собственным компонентам производных классов определяется обычным образом, то на доступ к наследуемым компонентам влияет, во-первых, атрибут доступа в базовом классе и, во–вторых, модификатор доступа (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; //Вспомогательные переменные

for(int i=0; i<n; i++)

{ cout<<"Input values price, year, speed for "<<i+1<< " Car" <<endl;

cin>>_price>>_year>>_speed;

car[i].SetVehicle(_price,_year);

car[i].SetSpeed(_speed);

car[i].SetID('C');

}

}

//Ввод данных для грузовиков

void InLorry()

{ int _price, _year, _capacity;

for(int i=0; i<n; i++)

{ 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. Может быть, решение было бы проще ( проверьте). Но дело в том, что у нас появилось бы множественное наследование, а его, по возможности, надо избегать. Впрочем, я не возражаю и против такого решения ваших задач.