Объектно-ориентированное программирование, страница 17

Для реализации динамического списка следует определить класс List (Список). Среди методов класса List, очевидно, должны быть: вставка в список, удаление из него, вывод всего списка, файловые операции ввода-вывода, сортировка по одному из общих полей, уничтожение списка. Чтобы список работал эффективно и мог беспрепятственно пользоваться данными семейства классов Man, классы Node и List объявлены дружественными классам иерархии.

//============= class Node and List

class Node

{

public:

Man *man;

Node *next;

Node() { man = 0;  next = 0; }

Node(Man *p, Node *n) { man = p;  next = n; }

~Node () { delete man; }

};

class List

{

private:

Node *first;

int count;

bool bModified;

string fileName;

public:

List() { first=0; count=0; bModified=false; fileName = "SPList.txt"; }

~List() {  ClearList(); }

void Process();

void Add();

void Show();

void Delete();

void ClearList();

void Write();

void Read();

bool QuitSave();

void Sort();

void DoSort();

};

Далее вы должны реализовать функциональность списка, то есть написать коды методов класса List. Указатель Node *first; должен содержать адрес первого элемента динамического списка. При начальном создании объекта класса List список пуст и признаком этого служит устанавливаемое конструктором нулевое значение указателя first, а также нулевое количество узлов count. Операция operator< помогает осуществить сортировку списка по одному из ключевых полей. Она возвращает true, если отношение порядка между двумя сравниваемыми объектами не нарушено и false — если нарушено. Статические переменные lastSortBy, sortBy и sortAsc задают поле и порядок сортировки. В нашем случае сортировка возможна по имени, возрасту и статусу. Сортируемые объекты могут принадлежать разным классам, но они доступны с помощью указателя на базовый класс Man*.

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

//===================== List Implementation ================//

void List::Add()

{

cout << "\n\tInsert: Select 0 to stop inserting\n";

while (1)

{

  Man *p;

  switch (ManMenu())

  {

    //==== Создайте новый объект нужного типа

  }

  //===== Вызовите виртуальный метод ввода данных

  //===== Вставьте в список адрес нового объекта

  count++;

  bModified=true;

}

}

Одинаковые по структуре циклы while используются в методах Process и Add. Они позволяют произвольное количество раз осуществлять выбор элементов меню. Функция Menu помогает пользователю выбрать режим работы со списком, а функция ManMenu дает возможность выбрать тип очередного включаемого в список объекта. В зависимости от выбранного типа указатель Man *p ссылается на вновь созданный объект того или иного производного класса. Здесь срабатывает совместимость указателей, рассмотренная ранее. Полиморфизм позднего связывания, приводимый в действие виртуальным методом In, позволяет на этапе выполнения программы выбрать либо Stud-версию, либо Prof-версию одноименных функций, в зависимости от класса объекта, на который ссылается указатель p в данный момент. Например, тот или иной метод In позволяет ввести с консоли данные об объекте, то есть заполнить поля данных объекта, который мы собираемся включить в список. Так как мы вставляем адреса всегда в начало списка, то эту работу компактным способом выполняет только одна строка программы:

first = new Node (p, first); // Вставка в начало списка

Здесь происходит создание нового узла списка, наполнение его двумя указателями:

¨  Информационная часть — адрес (p) только что созданного объекта класса Man;

¨  Зацепление списка — адрес first узла, бывшего первым.

Присвоение адреса вновь созданного узла указателю first означает, что новый узел теперь является первым. Рассмотрим метод вывода элементов списка на экран.

void List::Show()

{

if (!count)

  cout << "\nList is empty\n\n";

//===== Выведите весь список (пользуйтесь методом Out

}

Цикл for перебирает узлы гетерогенного списка с помощью одного указателя (p) на класс Node, выуживая из них адрес информационного поля и посылая сообщение Out, то есть вызывая виртуальную функцию, тело которой выбирается в зависимости от класса текущего объекта. Здесь срабатывает полиморфизм позднего связывания.

void List::Delete()

{

while (count)

{

  cout<< "\n\tDelete\t0..." << count <<"  (0 - to Quit): ";

  int i, n;

  if (!(n = ::In ("a Node", 0, count)))

    break;

  Node *c, *p;

  //===== Найдите и удалите узел списка с номером n

  count--;

  bModified=true;

}

Show();

}

Метод Delete класса List позволяет удалить произвольное количество элементов списка. В случае отсутствия элементов в списке, происходит выход из функции. Предварительно просим пользователя ввести порядковый номер удаляемого объекта. Если номер выбран в верном диапазоне, то осуществляется поиск удаляемого объекта в списке. При этом используется техника работы с двумя указателями: на текущий с (current) и предыдущий p (previous) узлы списка. Список корректируется путем обхода найденного элемента списка указателем next предыдущего элемента. Если предыдущего элемента нет, то это означает, что удаляемый элемент стоит первым в списке и надо скорректировать указатель начала списка first. Только после коррекции списка можно удалить найденный объект (либо класса Stud, либо класса Prof). Уничтожение (delete c;) происходит с помощью указателя Node *c. Здесь происходит вызов цепочки деструкторов. Деструктор класса Node вызывает деструктор класса Man, который освободит память, занятую строкой символов имени человека. Нет необходимости использовать виртуальный деструктор, так как производные классы не имеют данных в области heap. Деструктор класса List пользуется услугами вспомогательной функции ClearList, которая поочередно уничтожает все элементы списка, используя два указателя (next и c). Так как перед освобождением текущего узла следует запомнить адрес следующего.

void List::ClearList()