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

{

Node *next, *c;

for (c=first; c; c=next)

{

  next=c->next;

  delete c;

}

first = 0;

count=0;

}

Переопределение оператора <, который используется при сортировке списка, позволяет сравнить два рядом стоящих узла по выбранному пользователем полю и возвратить результат в виде булевского значения. Сортировка не производится, если список пуст или пользователь отказался от сортировки в меню выбора типа (SortMenu).

void List::DoSort ()

{

bool sorted;

Node *p1, *p2, *end;

//===== Реализуйте алгоритм сортировки

//===== Меняйте местами адреса объектов, в соответствии с таким алгоритмом

//   if ( Man::sortAsc && *(p2->man) < *(p1->man) ||

//     !Man::sortAsc && *(p1->man) < *(p2->man))

}

void List::Sort()

{

if (!count) {   Show();   return; }

while (true)

{

  Man::lastSortBy = Man::sortBy;

  switch (SortMenu())

  {

  default:

  case 'q': return;

  case 'n': /* Установите режим сортировки (по имени) */ break;

  case 'a': /* Установите режим сортировки (по возрасту) */ break;

  case 's': /* Установите режим сортировки (по статусу) */ break;

  case 't': Man::sortAsc = !Man::sortAsc; break;

  }

  DoSort();

  Show();

}

}

Ключевое поле Man::sortBy отражает выбор пользователя. Здесь для наглядности используется перечислимый тип eSortBy, определенный выше. Для сортировки используется метод пузырька, но элементы списка при этом не перемещаются в памяти. Изменяются только адреса зацеплений в элементах, тем самым реорганизуя список. Внешний цикл повторяет сжимающиеся проходы по списку, пока существуют дефекты в упорядоченности списка, то есть в предыдущем проходе была перестановка, о которой сигнализирует нулевое значение флага sorted. Большую работу должен выполнять вложенный цикл for, с помощью которого осуществляется проход по списку. До входа в каждый проход надо предположить, что список отсортирован (sorted = true) и установить в начальные позиции указатели на последовательные элементы списка.

Еще раз отметим, что сами элементы не перемещаются. Изменяются лишь ссылки (man) у рядом стоящих элементов списка так, чтобы изменилась очередность двух из них.

В конце цикла указатели сдвигаются вперед. Эта работа также выполняется в заголовке цикла for. Реорганизация зацеплений списка (в случае необходимости) выполняется внутри цикла. Для обмена позиций, кроме существующих указателей приходится задействовать еще один временный указатель Man *t.

Рассмотрим методы сохранения и восстановления содержимого списка, которые также пользуются услугами полиморфизма позднего связывания. При выводе элементов списка в файл сначала удобно записать общее количество элементов. Тогда проще будет прочесть список из файла. Далее следует пройти вдоль списка и каждому объекту из иерархии классов Man посылать сообщение о необходимости вывода в файл. Благодаря наличию метода Write в классе Man и виртуальных методов ToString в каждом из классов иерархии, нам не надо заботиться о выборе конкретного тела функции (из трех возможных версий). Выбор произведется автоматически на этапе выполнения программы. Нам надо лишь добыть из текущего узла указатель на базовый (абстрактный) класс прибора и вызвать с его помощью виртуальную функцию вывода в файл. Для упрощения процесса восстановления списка каждый тип прибора записывает идентификатор типа Class().

void List::Write()

{

if (!count)

{

  Show();

  return;

}

//===== Откройте файл с именем fileName

if (!os)

{

  cout <<"\nCan't open " << fileName << '\n';

  return;

}

//===== Выведите количество элементов списка

//===== Затем выведите весь список (используйте метод Write класса Man

os.close();

cout << "\nList has been stored\n";

bModified=false;

}

Чтение списка из файла подразумевает предварительное уничтожение списка в памяти, если он существует. При открытии файла следует задать флаг ios::in, который указывает на то, что файл открывается для чтения и его не нужно создавать. Процесс восстановления списка аналогичен процессу его создания. Каждый вновь прочитанный объект должен сначала сообщить свой тип, для того чтобы можно было создать в памяти новый объект нужного класса. Виртуальный конструктор невозможен, поэтому указатель на новый объект должен получить адрес одного из своих новых детей. Затем необходимо наполнить поля объекта данными из файла. Здесь нам опять помогают виртуальные функции ввода (Read), которые были заблаговременно созданы в классах иерархии.

void List::Read()

{

//===== Откройте файл с именем fileName. Не забудьте про режим чтения ios::in

if (!is) { cout << "\nCan't open " << fileName << '\n';  return; }

//===== Если в памяти есть список, то уничтожте его

cout << "\nRestoring from " << fileName << '\n';

//===== Прочтите первую строку и выудите информацию о количестве записей

count = strtol (buf, 0, 0);

//===== В цикле прочтите все записи

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

{

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

}

is.close();

Show();

bModified=false;

}

Вспомогательные внешние функции, смысл которых очевиден, приведены ниже.

void List::Process()

{

cout << "\n\t\t Linear List";

while (1)

{

  switch (Menu())

  {

  case 'q': if (QuitSave()) Write();  return;

  case 's': Show();  break; 

  case 'a': Add(); break;

  case 'd': Delete();  break;

  case 'r': Read();  break;

  case 'w': Write(); break;

  case 't': Sort();  break;

  default: cout << "\nPlease, enter a letter to indicate an action\n";

  }

}

}

bool List::QuitSave()

{

if (!bModified)

  return false;

cout << "\n\t\tList has been changed. Save (y/n) ?\n";

char a = cin.rdbuf()->sbumpc();

return toupper(a)=='Y';

}

Однако главная программа void main() должна быть в конце всего файла.

//===================== Main Function ===========================//

void main()

{

(new List)->Process();

cout<<"\n\n";

}

Попробуйте ответить на вопрос, когда появится сообщение об уничтожении списка?