Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Stoneshard |#11| Battle at the castle
Stoneshard |#10| A busy reaper
The Elder Scrolls IV: Oblivion Remastered - Trash review
Stoneshard |#9| A Million Liches

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Страустрап Б. Весь текст 579.17 Kb

Язык С++

Предыдущая страница Следующая страница
1 ... 26 27 28 29 30 31 32  33 34 35 36 37 38 39 ... 50
которая  работает   с  бошим   числом  производных  классов,  может
оказаться сложной  задачей, и  даже когда  все они  найдены, бывает
нелегко понять, что же в них делается.

     7.2.8 Виртуальные Функции

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

  struct employee {
      employee* next;
      char*     name;
      short     department;
      // ...
      virtual void print();
  };

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

  void employee::print()
  {
      cout << e->name << "\t" << e->department << "\n";
      // ...
  }

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

                             - стр 209 -

  struct manager : employee {
      employee* group;
      short     level;
      // ...
      void print();
  };

  void manager::print()
  {
      employee::print();
      cout << "\tуровень" << level << "\n";
      // ...
  }

Функция print_employee() теперь не нужна, поскольку ее место заняли
функции члены  print(), и теперь со списком служащих можно работать
так:

  void f(employee* ll)
  {
      for (; ll; ll=ll->next) ll->print();
  }

Каждый служащий  будет  печататься  в  соответствии  с  его  типом.
Например:

  main()
  {
      employee e;
          e.name = "Дж.Браун";
          e.department = 1234;
          e.next = 0;
      manager m;
          m.name = "Дж.Смит";
          e.department = 1234;
          m.level = 2;
          m.next = &e;
      f(&m);
  }

выдаст

  Дж.Смит 1234
          уровень 2
  Дж.Браун 1234

  Заметьте, что это будет работать даже в том случае, если f() была
написана и  откомпилирована еще  до  того,  как  производный  класс
manager был  задуман!  Очевидно,  при  реализации  этого  в  каждом
объекте класса  employee сохраняется  некоторая информация  о типе.
Занимаемого для  этого пространства  (в текущей реализации) как раз
хватает для  хранения указателя. Это пространство занимается только
в объектах  классов с виртуальными функциями, а не во всех объектах
классов и  даже не во всех объектах производных классов. Вы платите
эту пошлину  только за  те классы,  для которых описали виртуальные
функции.

                             - стр 210 -

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

     7.3 Альтернативные Интерфейсы

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

     7.3.1 Интерфейс

  Рассмотрим такое  написание класса slist для однократно сязанного
списка, с  помощью которого  можно создавать  как однородные, так и
неоднородные списки  объектов тех  типов, которые  еще должны  быть
определены. Сначала мы определим тип ent:

  typedef void* ent;

Точная сущность  типа ent  несущественна, но нужно, чтобы в нем мог
храниться указатель. Тогда мы определим тип slink:

  class slink {
  friend class slist;
  friend class slist_iterator;
      slink* next;
      ent e;
      slink(ent a, slink* p) { e=a; next=p;}
  };

В  одном   звене  может  храниться  один  ent,  и  с  помощью  него
реализуется класс slist:

                             - стр 211 -

  class slist {
  friend class slist_iterator;
      slink* last;        // last->next - голова списка
  public:
      int insert(ent a);  // добавить в голову списка
      int append(ent a);  // добавить в хвост списка
      ent get();          // вернуться и убрать голову списка
      void clear();       // убрать все звенья

      slist()      { last=0; }
      slist(ent a) { last=new slink(a,0); last->next=last; }
      ~slist()     { clear(); }
  };

Хотя список  очевидным образом  реализуется какк  связанный список,
реализацию  можно  изменить  так,  чтобы  использовался  вектор  из
ent'ов, не  повлияв при  этом на пользователей. То есть, применение
slink'ов никак  не видно  в описаниях  открытых функций slist'ов, а
видно только в закрытой части и определениях функций.

     7.3.2 Реализация

  Реализующие  slist   функции  в   основном  просты.  Единственная
настоящая сложность  - что  делать в случае ошибки, если, например,
пользователь попытается  get() что-нибудь  из  пустого  списка.  Мы
обсудим это  в #7.3.4.  Здесь приводятся  определения членов slist.
Обратите внимание,  как хранение  указателя  на  последний  элемент
кругового списка  дает возможность  просто реализовать оба действия
append() и insert():

                             - стр 212 -

  int slist::insert(ent a)
  {
      if (last)
          last->next = new slink(a,last->next);
      else {
          last = new slink(a,0);
          last->next = last;
      }
      return 0;
  }

  int slist::append(ent a)
  {
      if (last)
          last = last->next = new slink(a,last->next);
      else {
          last = new slink(a,0);
          last->next = last;
      }
      return 0;
  }

  ent slist::get()
  {
      if (last == 0) slist_handler("get fromempty list");
                                 // взять из пустого списка
      slink* f = last->next;
      ent r  f->e;
      if (f == last)
          last = 0;
      else
          last->next = f->next;
      delete  f;
      return f;
  }

Обратите внимание, как вызывается slist_handler (его описание можно
найти в  #7.3.4). Этот  указатель на имя функции используется точно
так же,  как если  бы он  был именем  функции. Это является краткой
формой более явной записи вызова:

  (*slist_handler)("get fromempty list");

И slist::clear(), наконец, удаляет из списка все элементы:

  void slist::clear()
  {
      slink* l = last;
      if (l == 0) return;
      do {
          slink* ll = l;
          l = l->next;
          delete ll;
      } while (l!=last);
  }

                             - стр 213 -

  Класс slist не обеспечивает способа заглянуть в список, но только
средства для  вставления и удаления элементов. Однако оба класса, и
slist, и  slink, описывают  класс slist_iterator как друга, поэтому
мы можем  описать подходящий  итератор. Вот один, написанный в духе
#6.8:

  class slist_iterator {
      slink* ce;
      slist* cs;
  public:
      slist_iterator(slist& s) { cs = &s; ce = cs->last; }

      ent operator()() {
          // для индикации конца итерации возвращает 0
          // для всех типов не идеален, хорош для указателей
          ent ret = ce ? (ce=ce->next)->e : 0;
          if (ce == cs->last) ce= 0;
          return ret;
      }
  };

     7.3.3 Как Этим Пользоваться

  Фактически класс  slist в  написанном виде бесполезен. В конечном
счете, зачем  можно использовать  список указателей  void*? Штука в
том, чтобы  вывести класс  из slist и получить список тех объектов,
которые представляют  интерес в  конкретной  программе.  Представим
компилятор языка  вроде C++.  В  нем  широко  будут  использоваться
списки имен; имя - это нечто вроде

  struct name {
      char* string;
      // ...
  };

В список  будут помещаться  указатели на  имена, а  не сами объекты
имена. Это  позволяет использовать  небольшое информационное поле e
slist'а, и дает возможность имени находиться одновременно более чем
в одном  списке. Вот определение класса nlist, который очень просто
выводится из класса slist:

  #include "slist.h"
  #include "name.h"

  struct nlist : slist {
      void insert(name* a) { slist::insert(a); }
      void append(name* a) { slist::append(a); }
      name* get()          {}
      nlist(name* a) : (a) {}
  };

Функции нового класса или наследуются от slist непосредственно, или
ничего не  делают кроме  преобразования типа.   Класс  nlist -  это
ничто иное,  как альтернативный  интерфейс класса slist. Так как на

                             - стр 214 -

самом  деле   тип  ent   есть   void*,   нет   необходимости   явно
преобразовывать указатели  name*, которые  используются в  качестве
фактических параметров (#2.3.4).
  Списки имен  можно использовать  в классе,  который  представляет
определение класса:

  struct classdef {
      nlist friends;
      nlist constructors;
      nlist destructors;
      nlist members;
      nlist operators;
      nlist virtuals;
      // ...
      void add_name(name*);
      classdef();
      ~classdef();
  };

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

  void classdef::add_name(name* n)
  {
      if (n->is_friend()) {
          if (find(&friends,n))
              error("friend redeclared");
          else if (find(&members,n))
              error("friend redeclared as member");
          else
              friends.append(n);
      }
      if (n->is_operator()) operators.append(n);
      // ...
  }

где is_iterator()  и is_friend()  являются функциями членами класса
name. Фукнцию find() можно написать так:

  int find(nlist* ll, name* n)
  {
      slist_iterator ff(*(slist*)ll);
      ent p;
      while ( p=ff() ) if (p==n) return 1;
      return 0;
  }

Здесь  применяется   явное  преобразование  типа,  чтобы  применить
slist_iterator к  nlist. Более  хорошее решение,-  сделать итератор
для nlist'ов,  приведено в  #7.3.5. Печатать nlist может, например,
такая функция:

                             - стр 215 -

  void print_list(nlist* ll, char* list_name)
  {
      slist_iterator count(*(slist*)ll);
      name* p;
      int n = 0;
      while ( count() ) n++;
      cout << list_name << "\n" << n << "members\n";
      slist_iterator print(*(slist*)ll);
      while ( p=(name*)print() ) cout << p->string << "\n";
Предыдущая страница Следующая страница
1 ... 26 27 28 29 30 31 32  33 34 35 36 37 38 39 ... 50
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 
Комментарии (4)

Реклама