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

Абстрактный класс

Абстрактный класс содержит по крайней мере одну чисто виртуальную функцию. Чисто виртуальная функция объявляется путем указания =0 в конце ее объявления.

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

{

public:

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

};

class Rectangle: public Shape

{

public:

  void draw();

};

class Circle : public Shape

{

public:

  void draw();

};

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

Методика выполнения

  1. Запустите MS Visual Studio и откройте проект консольного приложения из работы №5.

  2. Измените объявление класса так, чтобы он стал абстрактным базовым классом для задачи.

  3. Объявите нужное количество дочерних классов.

  4. Определите методы дочерних классов.

  5. Сделайте необходимые изменения в объявлениях и определениях функций обработки данных. Используйте полиморфизм позднего связывания. Отладьте программу. В процессе отладки используйте заглушки и комментарии для постепенного включения исправлений в программу.

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

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

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

  1. Как в классе указывается родительский класс?

  2. Что такое тип наследования и как он влияет на доступность элементов класса?

  3. Что такое полиморфизм раннего связывания?

  4. Что такое полиморфизм позднего связывания?

  5. Какие возможны варианты переопределения функций в классах?

  6. Что такое абстрактный класс?

  7. Какая функция называется чисто виртуальной?

  8. В чем достоинства и недостатки использования в программах виртуальных функций?

Лабораторная работа №7

Сложные структуры из объектов классов

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

Задание

  1. Разработать на языке С++ 6-ю версию консольного приложение для хранения и отображения сведений об объектах в виде структуры в соответствии с вариантом задания, указанном в таблице.

  2. Обеспечить выполнение следующих операций:

      1. Хранение сведений об объектах в файле, организация ввода/вывода элементов структуры.

      2. Поиск объекта в структуре.

      3. Редактирование сведений об объекте.

      4. Вставка объектов в структуру.

      5. Удаление объектов из структуры.

      6. Сортировка объектов в структуре и вывод отчета на экран.

  3. Все функции реализовать как методы класса.

  4. Операции файлового ввода/вывода реализовать с помощью библиотеки потоковых классов.

Бригада №

Вид структуры

1

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

2

Циклический однонаправленный список

3

Коллекция

4

Циклический двунаправленный список

5

Бинарное дерево

6

Линейный двунаправленный список

7

Очередь FIFO

8

Стек

9

Циклический двунаправленный список

10

Бинарное дерево

11

Очередь FIFO

12

Коллекция

Краткая теория

Вложенность классов

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

Существует специально зарезервированное ключевое слово this, определяе­мое как указатель на объект, которому было послано обрабатываемое сообще­ние. В каком-то конкретном методе указатель this содержит адрес объекта, ко­торый инициировал этот метод. Это справедливо только для методов класса, не имеющих описатель static. При любом вызове метода класса указатель this пе­редается как скрытый (hidden) параметр, то есть передается неявно. Он являет­ся локальной переменной внутри метода и неявно используется при обращении к данным и методам класса. Иногда его используют явно.

Рассмотрим эти воз­можности на примере инкапсуляции в классе Node такой распространенной струк­туры данных, как узел линейного односвязного списка. Класс Node имеет в каче­стве элемента данных указатель next на объект своего собственного класса.

class Node {

char *data; // Информационная часть узла

Node *next; // Указатель на следующий узел

publiс:

Node (char *s) // Конструктор

{

data = strcpy(new char[strlen(s)+l], s);

next = NULL;

}

void GetData()

{

puts (this->data);

}

void SetNextTo (Node& n)

{

n.next = this;

}

Node* GetNext()

{

return next;

}

~Node ()

{

puts("\nDeleting data");

delete data;

}

};

void main ()

{ // Использование класса Node

Node n1(“First"), n2("Second");

n1.GetData();

n2.GetData();

n2.SetNextTo(n1);

n1.GetNext()->GetData();

}

Конструктор класса Node дважды вызывается в функции main при объявлении объектов n1 и n2. Каждый раз он запрашивает память из области heap и разме­щает там строку символов (содержимое узла списка), которая передастся конст­руктору в качестве параметра. Метод класса GetData выводит в поток stdout (стандартный выходной поток) поле data того объекта, которому передается со­общение. Здесь мы специально вставили выбор с помощью указателя (this->), чтобы проиллюстрировать то, что обычно делает компилятор. Его можно оста­вить, а можно и убрать. Любое обращение к data в любом методе класса Node компилятор расширяет до this->data. Оператор программы n1.GetData(); выве­дет строку "First", а оператор n2.GetData(); — строку "Second". Внутри конст­руктора мы обращаемся к переменной data, не указывая явно, какому объекту принадлежит это поле данных. Компилятор C++ сам расширяет это обращение до this->data. Метод SetNextTo получает в качестве параметра ссылку на объект класса Node. Поскольку этот метод принадлежит тому же классу, что и параметр, то внутренний компонент next объекта n прямо доступен в данном методе. Ему присваивается адрес объекта, который инициировал метод (n.next=this;). Адрес содержится в переменной this. Так как в нашем случае объекту n2 послано со­общение SetNextTo(n1), то указатель this содержит адрес объекта n2, и он при­сваивается полю next объекта nl. Получается, что объект n1 имеет в качестве следующего узла списка объект n2, или можно сказать, что n2 зацеплен за n1.

Метод GetNext позволяет получить адрес следующего элемента списка, то есть поле next объекта, которому послано сообщение. В последней строке функции main он используется для того, чтобы добыть объект, стоящий в списке за объек­том n1. Выражение n1.GetNext() имеет результатом адрес объекта n2, которому посылается сообщение GetData. Следовательно, результатом всего выражения будет вывод поля data объекта n2, то есть строка "Second".

Сложные структуры данных

Стеком называется упорядоченный набор элементов, в котором размещение новых элементов и удаление существующих производится только с одного его конца, называемого вершиной стека. Его еще называют структурой типа LIFO (Last In First Out, первым вошел — последним вышел). Максимальное число элементов, которые можно размес­тить в стеке, не должно ограничиваться программным окружением: по мере вталкивания в стек новых элементов и выталкивания старых, память под него должна динамически запрашиваться и освобождаться. Состояние стека рассматривается только по отношению к его вершине, а не ко всему содержимому. Операции, выполняемые над стеком, имеют специальные названия. При до­бавлении элемента в стек мы говорим, что элемент вталкивается в стек (push). Для стека s и элемента sItem определена операция push(s, sItem), по которой в стек s добавляется элемент sItem. Ана­логичным образом определяется опера­ция выталкивания из стека - pop(s), по которой из стека s "верхний" элемент удаляется и возвращается в качестве зна­чения функции. Следовательно, опера­ция присваивания sItem = pop(s); удалит элемент из стека и присвоит его значение переменной sItem. На приме­нение операции выталкивания из стека существует единственное ограничение: она не может применяться к пустому стеку, т. е. к такому, который не содер­жит ни одного элемента. Помимо этих двух основных операций часто бывает необходимо прочитать элемент в вершине стека, не извлекая его оттуда. Для этих целей использу­ют операцию peek(s).

Очередью называется упорядоченный набор элементов, которые могут уда­ляться с одного ее конца (называемого началом очереди) и помещаться в другой конец этого набора (называемого концом очереди). Ее также называют структурой данных, организованной по принципу FIFO (First In First Out, первый вошел — первым вышел). Как и для стека, макси­мальное число элементов в очереди не должно лимитироваться используе­мым программным обеспечением: память должна запрашиваться и освобож­даться динамически по мере того, как в очередь добавляются новые, и из нее удаляются находящиеся там элементы.

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

  • insert — добавить новый элемент в список, сохраняя установленный по­рядок следования.

  • destroy — разрушить список.

  • display — вывести все элементы списка.

  • remove — удалить элемент списка.

Существует множество способов его реализации. В линейном однонаправленном списке поле next содержит адрес следующего элемента списка. Последний элемент в поле next хранит значение NULL. В линейном двунаправленном списке кроме поля next имеется также поле prev, которое содержит адрес предыдущего элемента списка. Поле prev первого элемента списка содержит NULL. В циклическом однонаправленном списке поле next последнего элемента содержит указатель назад на первый элемент. Такой список не имеет первого и последнего элементов. Однако в некоторых случаях удобно использовать внешний указатель на последний элемент, что автоматически делает пер­вым следующий за ним элемент. Альтернативный вариант предполагает использование указателя на первый элемент. В циклическом двунаправленном списке поле prev первого элемента содержит указатель на его последний элемент, а поле next последнего элемента – указатель на первый элемент.

Коллекция - это упорядоченный набор объектов, построенный на базе динамического массива и записи, что позволяет, с одной стороны, перенумеровать все элементы семейства, а с другой — иметь прямой доступ к объектам — элементам семейства — по значению определенного поля, на­зываемого ключом. Ключ — это строковое выражение, которое может быть использовано вместо индекса для доступа к элементу семейства. Класс Collection имеет одно свойство Count и три метода — Add, Item и Remove. Свойство Count возвращает количество элементов семей­ства.

Метод Add (элемент [, ключ][, до] [, после]) добавляет объект в семейство. Его обязательным аргументом является элемент, он указывает добавляемый в семейство элемент. Параметр ключ задает ключ, по которому возможно будет произвести поиск этого элемента. Параметры до и после указывают на то, перед каким или после какого элемента добавляется новый. По умолчанию элемент добавляется в конец семейства.

Метод Remove(ключ) удаляет элемент из семейства. Параметр ключ - это ключ или индекс, указывающие на удаляемый элемент. Заметьте, что при удалении элемента из семейства не остается "дыр": индексы перенумеровы­ваются, значение свойства Count уменьшается на единицу.

Метод Item (ключ) возвращает значение элемента семейства с ключом ключ. Как в случае с методом Remove, параметр ключ может быть как ключом, так и индексом.

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

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

  • Прямой порядок (нисходящий)

  • попасть в корень;

  • пройти в прямом порядке левое поддерево;

  • пройти в прямом порядке правое.

  • Симметричный порядок (последовательный)

  • пройти в симметричном порядке левое поддерево;

  • попасть в корень;

  • пройти в симметричном порядке правое поддерево.

  • Обратный порядок (восходящий)

  • пройти в обратном порядке левое поддерево;

  • пройти в обратном порядке правое поддерево;

  • попасть в корень.

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

Библиотека потоковых классов C++

В целом библиотека потоковых классов C++ состоит из многих классов, объе­диненных в два иерархических дерева.

Класс streambuf является базовым клас­сом для классов filebuf, stdiobuf и strstreambuf, обеспечивающих буферизованный интерфейс между данными пользователя и такими областями хранения данных, как память или физическое устройство. Более 30 методов класса streambuf по­зволяют выбирать символы из буфера, помещать их туда, позиционировать указатель, запрашивать количество оставшихся символов в буфере и т. п. Мето­ды производных классов позволяют открывать и закрывать файлы, манипулировать дескриптором файла, управлять вводом-выводом на консоль, создавать буфер для форматирования строки символов в памяти.

Второй базовый класс ios по­рождает дерево классов istream, ostream, iostream. Каждый из этих классов, в свою очередь, имеет несколько производных классов. В результате дерево состоит более чем из 10 классов, методы которых дают возможность производить ввод-вы­вод с использованием операций форматирования высокого уровня. Рассмотрим пример использования некоторых из классов библиотеки.

#include iostream.h

void main()

{

int с;

const char *fn = "test.txt";

ofstream fOut; // Объявление выходного потока

streambuf *bufOut, *bufIn = cin.rdbuf();

// Открыть файл, встать в конец, разрешить дозапись

fOut.open (fn, ios::ate | ios::app);

if (!fOut) cerr << "Can't open " << fn; exit(-l);

bufOut = fOut. rdbuf (); // Связывание of stream и streambuf

clog << "Enter some text (^Z - to stop)" << endl;

// Ввод с клавиатуры, вывод на экран и в файл

while ((c=bufIn->sbumpc()) != EOF)

{

cout << char (с); //Эхо на экран

if (bufOut->sputc(c) = = EOF) cerr << "Output error";

}

fOut.close();

// Позиционирование во входном потоке

int Size = 0;

// Объявление входного потока и попытка его открытия

ifstream fIn (fn, ios::in | ios::nocreate);

fIn. seekg (0L,ios:: end); // Переместиться в конец файла

if ((Size = fIn.tellg()) < 0)

{

сеrr << fn << " not found";

exit(-l);

}

Cout<<fn<<" Size = "<<Size<<endl;

}

Сначала в примере создается файл test.txt, в который вводится фрагмент текста. Класс ofstream происходит от класса ostream. Следующая строка про­граммы

streambuf *bufOut, *bufIn = cin.rdbuf();

определяет два указателя на класс streambuf, которые используются для управления буферами потоков выво­да и ввода. Метод cin.rdbuf() возвращает текущее значение указателя в потоке ввода, связанном с клавиатурой. Оператор

fOut.open (fn, ios::ate | ios::app);

обращается к методу open класса ofstream, который открывает файл с именем, переданным первым параметром в режиме, заданном вторым параметром. Вто­рой параметр является выражением, операнды (ate и арр) которого определены в классе ios. Они являются элементами перечисления enum openmode и допуска­ют выполнение логических операций над собой с целью задания набора битовых признаков режима открытия файла. В нашем случае объединяются признаки арр (разрешение добавлять в конец файла) и ate (режим перемещения в конец фай­ла при его открытии). Операция | переопределена так, что нулевой код возврата свидетельствует об ошибке при открытии файла. Сообщение об ошибке выво­дится в поток сеrr, который обычно направлен на экран монитора. Оператор bufOut = fOut.rdbuf(); связывает буфер вывода с выходным потоком, так как метод rdbuf класса ofstream возвращает текущее значение указателя потока (фай­ла), которое присваивается указателю bufOut типа streambuf*.

В цикле while осуществляется последовательный ввод символов из входного потока, вывод их сначала на экран cout<<char(c);, затем в буфер, связанный с фай­лом. Метод sbumpc() класса streambuf возвращает текущий символ буфера ввода и сдвигает указатель буфера на следующий символ. Аналогичным образом метод sputc() помещает символ в буфер вывода. Символ Ctrl+Z служит признаком конца файла (EOF — End Of File). Чтобы закончить ввод с клавиатуры, необходимо ввес­ти такую последовательность: Enter, ^Z, Enter.

Следующая часть программы иллюс­трирует, как можно позиционировать указатель в файле. Указатель переводится в конец созданного файла, и выясняется его позиция относительно начала файла. Таким образом определяется размер файла в байтах. Класс ifstream происходит от класса istream. Наличие двух аргументов при объявлении объекта заставляет выбрать один из конструкторов класса ifstream, который сразу открывает файл с именем, переданным первым параметром, и в режиме, задаваемом вторым пара­метром. Режим задан в виде выражения ios::in | ios::nocreate. Результатом по­битовой операции ИЛИ является установка двух признаков - открыть файл для ввода и не создавать файл, если он не существует. Позиционирование в файле осуществляется с помощью функции seekg(), которая определена в классе istream и наследуется классом ifstream. Результатом вызова будет перемещение указателя потока в конец файла. Метод tellg() класса istream возвращает значение текущей позиции потока, которое численно на единицу меньше размера файла в байтах.