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



Оглавление

Лабораторная работа 1. Классы и объекты в С++. 1

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

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

Варианты задания. 6

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

Лабораторная работа 2. Наследование в С++. Виртуальные функции. 10

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

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

Варианты задания. 16

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

Лабораторная работа 3. Виртуальные функции. Выбор типа объектов. 19

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

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

Варианты задания. 22

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

Лабораторная работа 4. Контейнеры STL. Обработка исключений. 25

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

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

Варианты задания. 30

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

Лабораторная работа 5. Классы, объекты, наследование в С#. 34

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

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

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

Требования к отчету. 37

Литература. 39

Приложения. 40

1.Создание консольного приложения в Visial Studio .Net 40

2. Cоздание графического интерфейса к программам на С++ в Visual Studio . Net 43

 

 

 Всего предусмотрено выполнение 5 лабораторных работ. Первые 3 работы сдаются в 1-м модуле. При несвоевременной сдаче лабораторных работ оценка снижается.

Требования к отчету см. ниже.

Первые 4 работы могут  быть выполнены на С++  как консольные приложения Visual Studio-Win32 / Builder C++/Qt . Последняя  - на С# в  Visual Studio . Создание программ с графическим интерфейсом не возбраняется (см. Приложения).

Лабораторная работа 1. Классы и объекты в С++

Цель работы

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

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

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

Базовыми понятиями объектно-ориентированной программы явля­ются «объект класса» и «класс». Эти понятия аналогичны понятиям переменной и её типа. Членами класса явля­ются переменные базовых типов языка, объекты  классов, объявленных в программе (последние называют пользовательскими типами), и функции, работающие с этими переменными. Обычно переменные, определенные в классе, называют полями или данными, а функции – методами класса. Говорят, что значения полей объекта класса определяют его состояние, а методы класса  - поведение. В С++ имеется возможность управлять доступом к полям и методам класса, в частности, установление такого порядка, при котором доступ к полям класса имеют только его методы. Объединение данных и методов работы с ними в одну языковую конструкцию с возможностью управления доступом к данным называется инкапсуляцией.

Тип класса может быть задан одним из 3-х атрибутов: class, struct, union. Имя класса становится идентификатоpом нового типа данных. Определение класса выглядит следующим образом:

class Alpha { // Начало определения

// Поля класса

int x;

double y;

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

Alpha (){x=0; y=0;}

//Метод

Alpha setAlpha( int _x, int _y){x=_x; y=_y;}

//Деструктор

~Alpha(){};

}; //Конец определения класса

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

 Объект класса может быть создан в памяти, управляемой компилятором,

          Alpha a1;

 а также в динамической памяти с помощью функции new.

             Alpha *a2=new Alpha;

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

Деструктор класса автоматически разрушает объект при выходе последнего из области видимости; деструктор имеет имя класса, которому предшествует символ ~. Деструктор не имеет параметров и не возвращает значе­ния; в классе может быть только один деструктор. Если при создании объекта в конструкторе используется опера­ция new, то в деструкторе должен быть использован операция delete.

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

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

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

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

Доступ к члену класса определяется наличием в определении класса атри­бутов доступа public, private, protected. Атрибут public определяет все следующие за ним члены класса как открытые, к которым разрешен прямой доступ из любой точки про­граммы; privateзапрещает прямой доступ к соответствующим компонентам извне (закрытые компоненты). В классе типа class все компоненты по умолчанию private, в классах типа struct – public. Каждый атрибут, записанный в классе, отменяет действие предыду­щего. (Атрибут protected(защищенный) – используется в иерархии классов).

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

Созданный объект имеет доступ ко всем не закрытым полям и методам класса. Обращение к этим членам может двояким:

имя_объекта.имя_члена или указатель_на_объект->имя_члена.

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

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

friend тип имя (параметры);

Дружественные функции рекомендуется использовать в следующих случаях:

– при необходимости упростить форму обращения к компонентам класса: не будучи компо­нентом класса, дружественная функция при вызове не требует имени объекта;

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

Агрегация

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

Например,

class Point{

public:

double x, y;

/*…..*/

};

class Triangle {

public:

Point x1, x2, x3;

/*…..*/

};

 

Point
Triangle

Далее

void main()

{ Triangle t1;

t1.x1.x=5;

t1.x2.y=7;

……}

Закроем поля в Triangle и введем в него открытый метод:

class Triangle {

Point x1, x2, x3;

public:

void SetTriangle(double _x1, double _y1,double _x2,double _y2,double _x3,double _y3)

{x1.x=_x1;x1.y=_y1;x2.x=_x2;x2.y=_y2;x3.x=_x3;x3.y=_y3;}

 

};

 

void main(int argc, _TCHAR* argv[])

{   

Triangle t1;

t1.SetTriangle(1,2,3,4,5,6);

}

Графическое изображение класса включает имя класса, его поля и методы. На диаграмме закрытому компоненту предшествует символ -, открытому – символ +.

                        Complex     - real : double (ДействительнаяЧасть) - imag: double (МнимаяЧасть)   +Complex (void):Конструктор +Complex(double,double):Конструктор с параметрами +Complex(Complex&): Конструктор копирования +~Complex (void):Деструктор +setComplex: void (ВводЧисла ) +showComlex: void (ВыводЧисла ) +operator +:Complex (Сложение чисел) +operator *: Complex (УмножениеЧисел ) +getComplex(Complex) (Вывод числа)

 

 

 


 

 

Ниже приведена программа реализация класса Complex в консольном проекте на Visual Studio.  Проект включает 3 файла: заголовочный - Complex.h , реализации - Complex.cpp и функции main - MyComplex.cpp. В main приведены примеры создания объектов с помощью различных конструкторов, перегрузка операций и дружественная функция.

Листинг 1.1

//complex.h

#pragma once

class Complex

{ //Поля

double real;

double imag;

public:

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

Complex(); //Пустой

Complex(double, double);//С параметрами

Complex(Complex&); //Копирования

// Методы

void setComplex(); // Ввод  числа

void showComplex () const; //Вывод на консоль      

Complex operator + (Complex); //Перегрузка операции сложения 

Complex operator * (Complex ); //Перегрузка операции умножения

//Дружественная функция вывода на консоль

friend void getComplex(Complex);

//Деструктор

~Complex(void);

};

 

//сomplex.cpp

#include "stdafx.h"

#include "Complex.h"

#include <iostream>

using namespace std;

Complex::Complex(void){ }

Complex::Complex(Complex& c1){ this->real=c1.real; this->imag=c1.imag;}

Complex::Complex(double _r, double _im){ this->real=_r; this->imag=_im;}

Complex::~Complex(void) { }

//Ввод числа

void Complex::setComplex() {

double re,im;

cout<<"Действительная часть ? "; cin>>re;

cout<<"Мнимая часть ? "; cin>>im;

real=re;

imag=im;

}

//Вывод числа

void Complex::showComplex()const{

cout<<this->real<<" +i"<<this->imag<<endl;

}

//Перегруженные операции +, *

Complex Complex:: operator + (Complex t)

{ Complex tmp;

tmp.real= this->real+t.real;

tmp.imag= this->imag+t.imag;

return tmp;

}

Complex Complex::operator *(Complex t){

Complex tmp;

tmp.real= this->real*t.real - this->imag*t.imag;

tmp.imag= this->imag*t.real + this->real*t.imag;

return tmp;

}

void getComplex(Complex c1)

{ cout<<c1.real<<" +i"<<c1.imag<<endl;}

 

 

//MyComplex.cpp

#include “stdafx.h”

#include "Complex.h"

#include <locale.h> // подключает setlocale

#include <iostream>

using namespace std;

int _tmain(int argc, _TCHAR* argv[])

{

setlocale(LC_ALL,"Russian");//Русификация текста

Complex cm1, cm2, cm3;

  cout<<"1-е число "<<endl;

 cm1.setComplex();

 cout<<"2-е число "<<endl;

 cm2.setComplex();

 cout<<"Сумма "<<endl;

 cm3=cm1+cm2;

 cm3.showComplex();

 cout<<"Произведение "<<endl;

 cm3=cm1*cm2;

 cm3.showComplex();

 Complex cm4(34.7, 21.8);

 cout<<"Массив из 2-х чисел"<<endl;

Complex cm5[2];

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

      cm5[i].setComplex();

 cout<<"Объект-указатель"<<endl;

 Complex *cm6=new Complex(3,8);

cm6->showComplex();

cout<<"Объект-копия 1-го "<<endl;

 Complex cm7(cm1);

getComplex(cm7);

 delete cm6;

 return 0;

}

Замечания

 1. this, используемый в листинге, называется скрытым указателем на объект. Он создается автоматически вместе с объектом и не требует объявления. Употребление его в данном случае необязательно.

2. В библиотеке С++ есть класс complex, предназначенный для работы с комплексными числами. Это класс–шаблон, об особенностях работы с которыми говорится в работе №4.

 

Варианты задания

При выполнении работы необходимо:

- разработать соответствующие классы, их поля и  методы;

- поля класса сделать закрытыми, для чтения и изменения их значений определить открытые методы;

- в функции main создать объекты   для демонстрации работы методов класса c выводом соответствующих сообщений на консоль .

В этой и следующих лабораторных работах в заданиях предложены англоязычные термины, которые обязательны к использованию; если вам понадобятся дополнительные термины, то пользуйтесь словарем, а не набором  латинских символов. Названия методов надо начинать с глаголов (Set, Get и т.п.), а атрибутов – с существительных. Не ограничивайте длину идентификатора(в разумных пределах). Отговорки типа « я изучаю немецкий» не принимаются.

 

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

1. Определить класс Дата(Date), в котором производится ввод календарной даты, определение соответствующего дате дня недели, прибавления к дате целой константы и т.п. операции без использования библиотечных классов, таких как ctime.

2. Определить класс Fraction, в котором производятся операции над обыкновенными дробями (сравнение, сложение, умножение, деление, сокращение).

3. Определить класс римских чисел RomanFigure. Реализовать алгоритм перевода из десятичной системы и обратно и операции сложения и вычитания.

4. Определить класс Точка на шаровой поверхности(PointSphere), объекты которого задаются в полярной системе координат (широта, долгота, радиус-вектор). Реализовать метод определения длины линии на поверхности шара между двумя точками и метод преобразования полярной системы координат в прямоугольные и обратно.

5. Определить класс Квадрат (Square) со сторонами, параллельными осям координат. Методы: перемещение, изменение размеров, построение прямоугольника, являющегося пересечением двух других. Желательно фигуры рисовать. Если не хотите связываться с графикой, то выведите на консоль контуры в виде последовательности символов '*', для чего используйте заголовочный  файл iomanip и его методы setw и setfill.

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

7. Определить класс Множество (Set) c методами: добавление элементов в множество, пересечение и объединение множеств. Множество реализовать на основе массива в динамической памяти или списка.

8. Определить класс вещественных матриц (Matrix) с методами, реализующими сложение и вычитание матриц, транспонирования, вычисления детерминанта и обратной матрицы. Воспользоваться массивoм, создающимся с помощью оператора new.

9. Написать класс для работы с   С-строкой (C_String)  ( завершающим символом строки является '\0'). Поля класcа: указатель на char – хранит адрес динамически выделенной памяти для размещения символов строки, значение типа int для хранения длины строки в байтах. Конструкторы: без параметров, копирования, создающий строку заданной длины. Методы: получение длины строки, очистка строки,  соединения 2 – х строк (перегрузить операцию +). Не   использовать соответствующие библиотечные функции.

10. Реализовать  класс Стек (Stack) для какого-либо типа данных с операциями инициализации, добавления и извлечения элементов согласно соответствующей дисциплине обслуживания. Воспользоваться массивoм, создающимся с помощью  new.

11. Реализовать класс  Очередь (Queue) для какого-либо типа данных с операциями инициализации, добавления и извлечения элементов согласно соответствующей дисциплине обслуживания. Воспользоваться массивoм, создающимся с помощью оператора new. Подумайте, как создать очередь с приоритетами.

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

13. Определить класс Морской_порт (NavyPort) , полями которого являются причал (Pier), судно(Ship), рейд (Raid); причал может быть занят/свободен,  судно – на причале/на рейде/ пустое/с грузом,  рейд – пустой/есть суда (до 5).  Реализовать методы, осуществляющую следующую последовательность действий:

a) разгрузка судна –> причал свободен, судно переходит к причалу, причал занят, количество судов на рейде уменьшается на 1, судно разгружено;

b) погрузка судна –> то же, что и в a), но в итоге судно с грузом.

 

14. Определить класс  «Поезд (Train)» с полями: номер поезда, поездная бригада(машинист, начальник поезда, проводники), движется/на станции, пассажиры: полный/частично заполненный /пустой , запас воды и продуктов/нет запасов. Реализовать методы, осуществляющую следующую последовательность действий:

a) поезд подается на станцию отправления ->бригада занимает свои места, пассажиры заполняют поезд, запасы воды и продуктов возобновляются;

b) поезд прибывает на промежуточную станцию -> пассажиры частично сменяются, запасы воды и продуктов проверяются и в случае необходимости увеличиваются;

c) поезд прибывает на конечную станцию -> все пассажиры и поездная бригада покидают поезд.

 

15. Определить класс Аэропорт (Airport), полями которого являются  взлетно-посадочная полоса (runway, занята/свободна), самолет(airplane, на полосе/ в воздухе) и пассажирский терминал(terminal, занят пассажирами/пуст).  Реализовать методы, осуществляющую следующую последовательность действий:

a) посадка самолета(landing)  –> самолет на полосе , полоса становится занятой,  терминал заполняется;

b) взлет самолета(flight) -> самолет в воздухе, полоса освобождается, терминал свободен.

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

16. Определить классы Автостоянка (Parking)  и Автомобиль (Car). Для каждого автомобиля во втором классе описывают госномер, марку, цвет и признак присутствия на стоянке. В первый класс вводится  массив автомобилей, которые могут присутствовать на стоянке. Определить в первом классе методы заезда автомобиля на стоянку, его выезда,  подтверждения присутствия автомобиля на стоянке по госномеру, вывода списка присутствующих автомобилей.

17. Определить классы Карта (Card) и Колода_карт (CardBatch). Поля первого – масть (suit)и достоинство(rank). Второй содержит  массив объектов первого и методы перемешивания колоды и раздачи карт на заданное число игроков равными порциями. Создать метод, моделирующий упрощенный  розыгрыш взятки: на стол выкладываются  по одной карте  одной масти от каждого из 4-х игроков; выигрывает карта, старшая по достоинству (картинки старше простых карт; козырной масти нет).

18. Определить классы  Автобусный_маршрут (BusLine) и Автобус (Bus). Во втором задаются  госномер, название остановки и время от начала маршрута. Объект этого класса вводится в первый класс, где, кроме того, определены:

a) массив структур из 3-х полей; первое поле – название остановки маршрута, второе – время от начала маршрута, которое автобус должен затратить для ее достижения, третье – признак нахождения автобуса на этой остановке;

b) метод   последовательного перехода автобуса от одной остановки к другой с фиксацией  времени автобуса от начала маршрута;

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

19. Определить  классы Книга (Book) и Библиотека (Library). Поля книги: ФИО автора(ов), название(Title), год издания(Year), издательcтво (Publishers). Библиотека содержит массив книг, размер которого является параметром. Массив  инициализируется 10-ю реальными книгами. Предусмотреть возможность поиска  книги по какому-либо признаку (например, по автору или по названию),  сортировки книг.

20. Определить  классы Студент (Student) и Студенческая_группа (Group). Поля первого: ФИО, дата рождения, адрес, учится/отчислен. Группа содержит массив студентов, размер которого является параметром. Предусмотреть возможность поиска студента по какому-либо признаку, сортировки по одному из полей, изменения значения поля учится/отчислен,  выдачи списка учащихся и отчисленных студентов.

Замечание относительно сортировки.

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

int arr[8];

…………

sort(arr, arr+8);

 Так указывается диапазон объектов, которые надо отсортировать, в данном случае весь массив. Но помните, что, сортируя объекты пользовательского класса, вы обязаны перегрузить в классе операцию «меньше» для того поля, по которому вы собираетесь вести сортировку (см. листинг 4.1)

 

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

  1. Для чего служит конструктор ? Может ли в классе быть несколько конструкторов? Чем должны отличаться различные конструкторы одного и того же класса?

  2. Для чего служит деструктор класса? Имеет ли деструктор параметры? В каком случае в тело деструктора включается оператор delete?

  3. Какие сообщения и в какой последовательности будут выведены на монитор?

class Alpha {

public:

int x, y;

   Alpha(){cout<<"Constructor #1"<<endl;}

Alpha(int _m){cout<<"Constructor #2"<<endl; x=y=_m; }

~Alpha(){cout<<"Destructor "<<endl;}

};

     void main()

{

Alpha a1;

a1.x=1;

 Alpha *a2;

a2=new Alpha;

Alpha a3[2];

Alpha a4(4);

 Alpha a5=a1;

Alpha a6(a1);

cout<<a5.x<<endl<<a6.x<<endl;

}

 

4. Определен следующий класс.

class Alhpa { public: int abc; };

Запишите обращения к компоненте abc с использованием точки и стрелки.

5. Каким образом компилятор отличает вызов стандартной операции от вызова перегруженной?  Вспомните язык С и его операции <<, >> и запишите результат второго выражения

int a=4;

cout<<(a<<3);

6. Что такое агрегация классов и как она изображается на диаграммах? Чем отличается строгая агрегация от  нестрогой?


Лабораторная работа 2. Наследование  в С++. Виртуальные функции

Цель работы

Ознакомление с отношением наследования, виртуальными функциями, абстрактными классами.

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

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

Наследование – механизм языка, позволяющий написать новый класс на основе уже существующего (базового, родительского) класса. Формат определения производного класса следующий:

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

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

Использование наследования позволяет строить иерархии классов.

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

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

Если доступ к собственным компонентам производных классов определяется обычным образом, то на доступ к наследуемым компонентам влияет, во-первых, атрибут доступа в базовом классе и, во–вторых, модификатор доступа (public / private), указанный перед именем базового класса в конструкции определения производного класса. Общепринятое правило - поля, базового класса определяются как защищенные (protected).

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

При создании иерархии классов, включающей базовый и производные классы, следует придерживаться принципа "это есть" (is a), выделяя общие черты классов и инкапсулируя их в базовом. Положим, например, что нам надо создать классы Car (автомобиль) и Lorry (грузовик). Что общего у этих классов и чем они разнятся? На самом деле у них много общих черт и много различий, но для упрощения примем следующие формулировки:

- грузовик  есть средство передвижения, имеющее цену и год выпуска, а также характеризующееся грузоподъемностью;

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

Создадим  базовый класс Vehicle.  В нем определим 2 целых поля для цены и года и методы для установки и возвращения значений этих полей. В производных классах  Car (автомобиль) и Lorry (грузовик) определим по одному полю и соответствующие методы.

Листинг 2.1

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

class Vehicle{

protected:

int price;//цена

int year; //год выпуска

public:

//Устанавливаем цену и год выпуска

void setVehicle(int _price, int _year)

{price=_price; year=_year;}

  int getPrice()const{return price;}

int getYear()const{return year;}

};

 

    

};

//Производный класс

class Lorry:public Vehicle{

int capacity; // грузоподъемность

public:

//Устанавливаем и возвращаем  грузоподъемность

void setCapacity(int _capacity)

{capacity=_capacity;}

int getCapacity()const {return capacity;}

 };

// Производный класс

class Car: public Vehicle{

int speed; // скорость

public:

//устанавливаем и возвращаем скорость

void setSpeed(int _speed){speed=_speed;}

  int getSpeed()const {return capacity;}

};

Собственно, все. Теперь в main можно создавать одиночные объекты Lorry и Car,  массивы и задавать их параметры, например, так:

Lorry lo;

lo. setVehicle(10000, 2012);

lo. setCapacity(5000);

Car car1[4];

car1[0]. setVehicle(15000, 2011); car1[0]. setSpeed(200);

car1[1]. setVehicle(....); car1[1]. setSpeed(...);

Lorry* lor=new Lorry[3];

. . .

 

Предположим теперь, что нам надо добавить к программе автобусы. Все просто: автобус есть средство передвижения для перевозки пассажиров – добавляем класс Bus c полем, например, "количество пассажиров" . При этом уже имеющиеся классы не трогаем; их можно даже не перекомпилировать. Таким образом, расширение  программы происходит гораздо проще, чем без использования наследования. Это обстоятельство является одной из главных причин применения наследования.

Продолжаем.

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

 Вот как он может выглядеть (продолжаем программу): 

class Garage{

int n, m; // Количество грузовиков и автомобилей  в гараже

// Указатели на соответствующие классы

Lorry *lor;

Car *car;

public:

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

Garage(int _n, int _m)// конструктор для создания обоих видов транспорта

{ n=_n; lor=new Lorry[n];

m=_m; car=new Car[m];

}

 

// Ввод данных для автомобилей

void setCars()

{ int _price, _year, _speed;

for(int i=0; i<m; 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);

      }

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

void setLorries()

{ int _price, _year, _capacity;

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

     { cout<<"Price, year, capacity for "<<i+1<< " Lorry"<<endl;

      cin>>_price>>_year>>_capacity;

lor[i].setVehicle(_price,_year);

      lor[i].setCapacity(_capacity);

     }

 }

//Вывод параметров грузовиков   

   void getLorries()

{ cout<<"Lorries"<<endl;

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

cout<< lor[i].getPrice()<<" "<<lor[i].getYear()<<" "

  <<lor[i].getCapacity()<<endl;

}

//Вывод для авто аналогичен

// Деструктор   

~Garage(){ delete[] car;

      delete[] lor;

     }

};

//Теперь наш клиент приобретает совсем простой вид

int _tmain(int argc, _TCHAR* argv[])

{

Garage g1(2,3); //Объект - массив из 2-х грузовиков и 3-х авто

g1. setLorries(); // Ввод данных

g1.setCars();

    

return 0;

}

 

Vehicle
Lorry
Car
Garage

В классе Garage можно инкапсулировать все требуемые  методы работы с массивами объектов:  сортировку, ввод-вывод в файл и т.п.

Упрощение клиентской части программы за счет введения некоторого дополнительного класса является содержанием паттерна  Фасад (Façade)[2].

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

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

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

class Based {

/* ...*/

public:

  virtual int fb(){return 3;}

/* ...*/

};

class Derived1:public Based {

  /* ...*/

int fb() {return 5;}

  /* ...*/

};

class Derived2:public Based {

int fb() {return 7;}

/* . . . */

};

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

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

Правильный вызов виртуальной функции заключается в последовательном применении следующих 2-х инструкций:

указатель_ на_базовый_ класс=указатель_на__объект_производного_ класса;

указатель_на базовый_класс àвызов_виртуальной_функции;

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

Для нашего примера вызов функции fb из производного класса:

Based* ptr=new Derived1;

ptr->fb(); // возвращает 5

. . .

ptr=new Derived2;

ptr->fb();// возвращает 7

 

Виртуальная функция в базовом классе может быть равна 0 (чистая функция). В нашем случае

virtual int fb( )=0;

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

При наличии виртуальных функций деструктор в базовом классе должен быть виртуальным.

Ниже приводится программа, аналогичная листингу 2.1, но с некоторыми упрощениями.

Листинг 2.2

class Vehicle{

protected:

int price;//цена

int year; //год выпуска

public:  

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

       virtual void setVehicle()=0;

     virtual void getVehicle()=0;

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

     virtual ~ Vehicle(){}

 

};

class Lorry: public Vehicle{

int capacity;

public:

//Для упрощения  делаем  параметры любого грузовика одними и теми же

void setVehicle(){cout<<"setLorry"<<endl;price=40000; year=5;capacity=6;}

 void getVehicle(){cout<<"Lorry"<<endl<<price<<" "<<year<<" "<<capacity<<endl;}

~Lorry(){}

 };

class Car:public Vehicle{

int speed;

public:

//То же для авто

void setVehicle(){cout<<"setCar"<<endl; price=10000; year=2;speed=3;}

 void getVehicle(){cout<<"Car"<<endl<<price<<" "<<year<<" "<<speed<<endl;}

~Car(){}

};

class Garage

{

public:

/*Для упрощения количество грузовиков и авто в гараже

одинаковы и заданы константой */

static const int n=2;

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

Lorry* lor[n];

Car* car[n];

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

Vehicle *vec;

public:

Garage()

{

//Инициализация указателей на производные классы

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

{ car[i]=new Car;

lor[i]= new Lorry;}

}

    

// Ввод  и вывод данных для машин с вызовом виртуальных функций

 void setVehicle(){

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

      { vec=car[i]; //Автомобили

     vec->setVehicle();

     vec=lor[i]; //Грузовики

     vec->setVehicle();

     }

 

      }

   void getVehicle(){

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

      { vec=car[i];

      vec->getVehicle();

vec=lor[i];

     vec->getVecicle();

      }

 }

~Garage()

    {

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

     {delete car[i];

      delete lor[i];

       }

}

};

int _tmain(int argc, _TCHAR* argv[])

{      Garage g;

 g.setVehicle();

 g.getVehicle();

 

}

Что мы получили , используя виртуальные функции?  Возможность доступа к производным классам с использованием одних и тех же функций, т.е. получили общий интерфейс иерархии. (Сравните с листингом 2.1.) Естественно, что при добавлении третьего производного класса интерфейс должен остаться тем же.

Класс, содержащей виртуальный метод, называют полиморфным классом. В нашем случае это означает, что функции setVehicle, getVehicle в разных точках программы действуют по-разному.

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

Замечу, что функции Garage::getVehicle, Garage::setVehicle находятся вне иерархии и не имеют никакого отношения к виртуальным. Их можно было назвать по-другому.

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

Варианты задания

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

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

1. Создать  базовый  класс CList (линейный однонаправленный cписок) с полями: указатели  на следующий элемент; информационная часть – целое число. В производных классах – CQueue (очередь) и CStack (стек) – должны быть определены методы  вставки  и удаления узла в  соответствии с  дисциплиной обслуживания соответствующего класса.

2. Описать класс  CString (Строка)  и производные CStringBit (Битовая_строка) и CStringHex (Шестнадцетиричная_строка). (Описание класса см. вариант 9 в 1-й работе.)   Строки первого  подкласса могут содержать только двоичные символы, второго только шестнадцетиричные. Разработать методы ввода данных с проверкой допустимых символов. Содержимое строк рассматривается как двоичное или шестнадцетиричное число без знака. Разработать операции сравнения двух строк, сложения и вычитания. Фасадный класс - Text.

3. Создать  класс ColoredPoint. На его основе создать производные классы Line и PolyLine (многоугольник). Все классы должны иметь методы установки и получения значений всех координат, а также изменения цвета и получения текущего цвета. Фасадный класс - Picture.

4. Создать  класс Figure. На его основе реализовать классы Rectangle (прямоугольник), Circle (круг) и Trapeze (трапеция) с возможностью вычисления площади, центра тяжести и периметра. Фасадный класс - Picture .

5. Создать  класс Number c виртуальными методами, реализующими арифметические операции. На его основе реализовать классы Integer и Real.(Операции в классах не должны быть одинаковыми). Фасадный класс - Calculus .

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 (сортировка) с массивом, задаваемым с помощью new, и виртуальными методами ввода элементов, сортировки и вывода на экран. На его основе реализовать классы Choice (метод выбора) и Quick (быстрая сортировка). Фасадный класс - Series.

19. Создать класс Pair (пара значений) с виртуальными методами, реализующими арифметические операции, и методом вывода на экран. На его основе реализовать классы Money (деньги) и Complex (комплексное число). В классе Money денежная сумма представляется в виде двух целых, в которых хранятся рубли и копейки соответственно. При выводе части числа снабжаются словами «руб.» и «коп.». В классе Complex предусмотреть при выводе символ мнимой части (i).  Фасадный класс - Series.

20. Создать  класс Worker с полями, описывающими должность, фамилию работника и его обязанности, а также фамилию его руководителя. На его основе реализовать классы Manager (руководитель проекта), Developer (разработчик) и Coder  программист) с соответствующими методами. Фасадный класс Group, в котором предусмотрена  возможность выборки по фамилии с выводом всего дерева подчиненных.  (Роль руководителя проекта заключается в полной ответственности за успешное планирование, выполнение и завершение проекта. Разработчик - поддержка существующего продукта, разработка новых функциональных возможностей и новых компонентов, выбор программных средств.)

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

 

1. В чем заключается наследование одного класса другому? В чем разница в организации наследования полей и методов?

2. Определены 2 класса:

сlass Based{public: int x;};

сlass Derived :public Based{};

Какое значение выводится на консоль?

Based b1;

b1.x=3;

Derived d1;

d1.x=4;

cout<<b1.x;

 

3. Удачной ли является иерархия классов, при которой некоторый класс Х является производным от большого количества классов с большим числом полей в каждом ( AßB ß C ß…ß X)? Какая существует альтернатива наследованию?

4. Каким образом производится управление доступом к унаследованным компонентам производных классов? Есть ли в представленном фрагменте программы ошибки и какое сообщение выдаст компилятор? Как исправить ошибку?

class Base {

int x;

public:

int y;

void setX(int n){x=n;}

void showX() const{ cout<<x<<endl;}

};

class Derived: public Base {

public:

void setY(int n){y=n;}

void show_sum() const{ cout<<x+y<<endl; }

};

 

 

int _tmain(int argc, _TCHAR* argv[])

{

Derived d1;

d1.show_sum();

return 0;

}

5. Есть ли ошибки в нижеследующих объявлениях

             // класс Shape абстрактный

Shape sh;  

Shape *psh;

  Shape *psh1=new Shape();

6. Что из себя представляет виртуальная функция и как она должна вызываться?

7. Как создать общий интерфейс  иерархии классов?

 


 


Дата добавления: 2018-04-15; просмотров: 1291; Мы поможем в написании вашей работы!

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






Мы поможем в написании ваших работ!