Альтернативні Інтерфейси h2>
Після того, як описано засоби мови, які відносяться
до похідних класів, обговорення знову може повернутися до вартим завданням. У
класах, які описуються в цьому розділі, основна ідея полягає в
те, що вони одного разу написані, а потім їх використовують програмісти, які не
можуть змінити їх визначення. Фізично класи складаються з одного або більше
заголовків файлів, що визначають інтерфейс, і одного або більше файлів,
визначають реалізацію. Заголовки будуть поміщені кудись туди, звідки
користувач може взяти їх копії за допомогою директиви # include. Файли,
визначають реалізацію, звичайно компілюють і поміщають в бібліотеку. p>
Інтерфейс h2>
Розглянемо таке написання класу slist для одноразово
пов'язаного списку, за допомогою якого можна створювати як однорідні, так і
неоднорідні списки об'єктів тих типів, які ще повинні бути визначені.
Спочатку ми визначимо тип ent: p>
typedef void * ent; p>
Точна сутність типу ent несуттєва, але потрібно, щоб у
ньому міг зберігатися покажчик. Тоді ми визначимо тип slink: p>
class slink ( p>
friend class slist; p>
friend class slist_iterator; p>
slink * next; p>
ent e; p>
slink (ent a,
slink * p) (e = a; next = p;) p>
); p>
В одній ланці може зберігатися один ent, і за допомогою нього
реалізується клас slist: p>
class slist ( p>
friend class slist_iterator; p>
slink *
last;// last-> next - голова списку p>
public: p>
int insert (ent
a);// додати в голову списку p>
int append (ent
a);// додати у хвіст списку p>
ent get ();// повернутися і прибрати голову списку p>
void
clear ();// прибрати всі ланки p>
slist () (last = 0;) p>
slist (ent a) (last = new slink (a, 0);
last-> next = last;) p>
~ slist () (clear ();) p>
); p>
Хоча список очевидним чином реалізується як пов'язаний
список, реалізацію можна змінити так, щоб використовувався вектор з
ent "ов, не вплинувши при цьому на користувачів. Тобто, застосування
slink "ов ніяк не видно в описах відкритих функцій slist" ов, а
видно тільки в закритій частині і визначеннях функцій. p>
Реалізація h2>
реалізують slist функції в основному прості. Єдина
справжня складність - що робити у випадку помилки, якщо, наприклад, користувач
спробує get () що-небудь з пустого списку. Тут наводяться визначення
членів slist. Зверніть увагу, як зберігання покажчика на останній елемент
кругового списку дає можливість просто реалізувати обидві дії append () та
insert (): p>
int slist:: insert (ent a) p>
( p>
if (last) p>
last-> next = new slink (a, last-> next); p>
else ( p>
last = new slink (a, 0); p>
last-> next = last; p>
) p>
return 0; p>
) p>
int slist:: append (ent a) p>
( p>
if (last) p>
last = last-> next = new slink (a, last-> next); p>
else ( p>
last = new slink (a, 0); p>
last-> next = last; p>
) p>
return 0; p>
) p>
ent slist:: get () p>
( p>
if (last == 0) slist_handler ( "get fromempty
list "); p>
// взяти з пустого списку p>
slink * f = last-> next; p>
ent r f-> e; p>
if (f == last) p>
last = 0; p>
else p>
last-> next = f-> next; p>
delete f; p>
return f; p>
) p>
Зверніть увагу, як викликається slist_handler. Цей
вказівник на ім'я функції використовується точно так само, як якщо б він був ім'ям
функції. Це є короткою формою більш явною запису виклику: p>
(* slist_handler) ( "get fromempty list "); p>
І slist:: clear (), нарешті, видаляє зі списку всі
елементи: p>
void slist:: clear () p>
( p>
slink * l = last; p>
if (l == 0) return; p>
do ( p>
slink * ll = l; p>
l = l-> next; p>
delete ll; p>
) while (l! = last); p>
) p>
Клас slist не забезпечує способу зазирнути в список,
але тільки кошти для вставляння і видалення елементів. Однак обидва класу, і
slist, і slink, описують клас slist_iterator як одного, тому ми можемо описати
підходящий ітератор. Ось один, написаний в дусі цього пункту: p>
class slist_iterator ( p>
slink * ce; p>
slist * cs; p>
public: p>
slist_iterator (slist & s) (cs = &s; ce =
cs-> last;) p>
ent operator () () ( p>
// для індикації кінця ітерації повертає 0 p>
// для всіх типів не ідеальний, гарний для покажчиків p>
ent ret = ce? (ce = ce-> next) -> e: 0; p>
if (ce == cs-> last) ce = 0; p>
return ret; p>
) p>
); p>
Як Цим Користуватися h2>
Фактично клас slist в написаному вигляді даремний. У
Зрештою, навіщо можна використовувати список покажчиків void *? Штука в тому,
щоб вивести клас з slist та отримати список тих об'єктів, які
представляють інтерес в конкретній програмі. Уявімо компілятор мови на зразок
C + +. У ньому широко будуть використовуватися списки імен, а ім'я - це щось на зразок p>
struct name ( p>
char * string; p>
// ... p>
); p>
До списку будуть міститися покажчики на імена, а не самі
об'єкти імена. Це дозволяє використовувати невелику інформаційне поле e
slist "а, і дає можливість імені перебувати одночасно більше ніж в
одному списку. Ось визначення класу nlist, який дуже просто виводиться з
класу slist: p>
# include "slist.h" p>
# include "name.h" p>
struct nlist: slist ( p>
void insert (name * a) (slist:: insert (a);) p>
void append (name * a) (slist:: append (a);) p>
name * get () () p>
nlist (name * a): (a) () p>
); p>
Опції нового класу або успадковуються від slist
безпосередньо, або нічого не роблять крім перетворення типу. Клас nlist --
це ніщо інше, як альтернативний інтерфейс класу slist. Так як на самому
справі тип ent є void *, немає необхідності явно перетворювати покажчики
name *, які використовуються в якості фактичних параметрів. p>
Списки імен можна використовувати в класі, який
представляє визначення класу: p>
struct classdef ( p>
nlist friends; p>
nlist constructors; p>
nlist destructors; p>
nlist members; p>
nlist operators; p>
nlist virtuals; p>
// ... p>
void add_name (name *); p>
classdef (); p>
~ classdef (); p>
); p>
і імена можуть додаватися до цих списків приблизно
так: p>
void classdef:: add_name (name * n) p>
( p>
if (n-> is_friend ()) ( p>
if (find (& friends, n)) p>
error ( "friend redeclared "); p>
else if (find (& members, n)) p>
error ( "friend redeclared as member "); p>
else p>
friends.append (n); p>
) p>
if (n-> is_operator ()) operators.append (n); p>
// ... p>
) p>
де is_iterator () і is_friend () є функціями
членами класу name. Фукнцію
find () можна написати так: p>
int find (nlist * ll, name * n) p>
( p>
slist_iterator ff (* (slist *) ll); p>
ent p; p>
while (p = ff ()) if (p == n) return 1; p>
return 0; p>
) p>
Тут застосовується явне перетворення типу, щоб
застосувати slist_iterator до nlist. Більш хороше рішення, - зробити ітератор для
nlist "ов. Друкувати nlist може, наприклад, така функція: p>
void print_list (nlist * ll, char * list_name) p>
( p>
slist_iterator count (* (slist *) ll); p>
name * p; p>
int n = 0; p>
while (count ()) n ++; p>
cout <
slist_iterator print (* (slist *) ll); p>
while (p = (name *) print ()) cout < string
<< "n"; p>
) p>
Обробка помилок h2>
Є чотири підходи до проблеми, що ж робити, коли під
час виконання общецелевое засіб на зразок slist стикається з помилкою (в C + +
немає ніяких спеціальних засобів мови для обробки помилок): p>
Повертати неприпустиме значення і дозволити користувачеві
його перевіряти; p>
Повертати додаткове значення стану і дозволити
користувачеві перевіряти його; p>
Викликати функцію помилок, задану як частина класу slist;
або p>
Викликати функцію помилок, яку імовірно
надає користувач. p>
Для невеликої програми, написаної її єдиним
користувачем, фактично немає ніяких особливих причин віддати перевагу одне з цих
рішень іншим. Для кошти загального призначення ситуація зовсім інша. p>
Перший підхід, повертати неприпустиме значення,
нездійсненний. Немає абсолютно ніякого способу дізнатися, що якийсь конкретний
значення буде неприпустимим у всіх застосуваннях slist. p>
Другий підхід, повертати значення стану, можна
використовувати в деяких класах (один з варіантів цього плану застосовується в
стандартних потоках введення/виводу istream і ostream). Тут, однак, є
серйозна проблема, раптом користувач не подбає перевірити значення
стану, коли кошти не надто часто підводить. Крім того, засіб може
використовуватися в сотні або навіть тисячі місць програми. Перевірка значення в
кожному місці сильно ускладнить читання програми. p>
Третьому підходу, надавати функцію помилок, бракує
гнучкості. Той, хто реалізує общецелевое засіб, не може дізнатися, як
користувачі захочуть, щоб оброблялися помилки. Наприклад, користувач може
віддавати перевагу повідомлення на датському або угорською. p>
Четвертий підхід, дозволити користувачеві задавати функцію
помилок, має деяку привабливість за умови, що розробник
надає клас у вигляді бібліотеки (# 4.5), в якій містяться стандартні
функції обробки помилок. Рішення 3 і 4 можна зробити більш гнучкими (і по суті
еквівалентними), задавши покажчик на функцію, а не саму функцію. Це дозволить
розробнику такого засобу, як slist, надати функцію помилок,
діючу за умовчанням, і при цьому програмістам, які будуть використовувати
списки, буде легко поставити свої власні функції помилок, якщо потрібно, і там,
де потрібно. p>
Наприклад: p>
typedef void (* PFC) (char *);// покажчик на тип функція p>
extern PFC slist_handler; p>
extern PFC set_slist_handler (PFC); p>
Функція set_slist_hanlder () дозволяє користувачеві
замінити стандартну функцію. Невизначений реалізація надає діючу
за замовчуванням функцію обробки помилок, яка спочатку пише повідомлення про
помилку в cerr, після чого завершує програму за допомогою exit (): p>
# include "slist.h" p>
# include p>
void default_error (char * s) p>
( p>
cerr <
exit (1); p>
) p>
Вона описує також покажчик на функцію помилок і, для
зручності запису, функцію для її установки: p>
PFC slist_handler = default_error; p>
PFC set_slist_handler (PFC handler); p>
( p>
PFC rr = slist_handler; p>
slist_handler = handler; p>
return rr; p>
) p>
Зверніть увагу, як set_slist_hanlder () повертає попередній
slist_hanlder (). Це
робить зручним установку і переустановку обробників помилок на кшталт стека.
Це може бути в основному корисним у великих програмах, в яких slist може
використовуватися в кількох різних ситуаціях, у кожній з яких можуть, таким
чином, ставити свої власні підпрограми обробки помилок. p>
Наприклад:
p>
( p>
PFC old = set_slist_handler (my_handler); p>
// код, в якому у разі помилок в slist p>
// буде використовуватися мій обробник my_handler p>
set_slist_handler (old);// відновлення p>
) p>
Щоб зробити управління більш витонченим, slist_hanlder міг
б бути зроблений членом класу slist, що дозволило б різним спискам мати
одночасно різні обробники. p>
Узагальнені Класи h2>
Очевидно, можна було б визначити списки інших типів
(classdef *, int, char * і т.д.) точно так само, як було визначено клас nlist:
простим висновком з класу slist. Процес визначення таких нових типів
стомлюючий (і тому загрожує помилками), але за допомогою макросів його можна "механізувати".
На жаль, якщо користуватися стандартним C препроцесором, це теж може
виявитися обтяжливим. Однак отриманими в результаті макросами користуватися
досить просто. p>
Ось приклад того, як узагальнений (generic) клас slist,
названий gslist, може бути заданий як макрос. Спочатку для написання такої
роду макросів включаються деякі інструменти з: p>
. html # include "slist.h" p>
# ifndef GENERICH p>
# include p>
# endif p>
Зверніть увагу на використання # ifndef для того,
щоб гарантувати, що в одній компіляції не буде включено двічі. GENERICH
визначений ст. p>
Після цього за допомогою name2 (), макросу з для
конкатенації імен, визначаються імена нових узагальнених класів: p>
# define gslist (type) name2 (type, gslist) p>
# define gslist_iterator (type) name2 (type, gslist_iterator) p>
І, нарешті, можна написати класи gslist (тип) і
gslist_iterator (тип): p>
# define gslistdeclare (type) p>
struct gslist (type): slist ( p>
int insert (type a) p>
(return slist:: insert (ent (a));) p>
int append (type a) p>
(return slist:: append (ent (a));) p>
type get () (return type (slist:: get ());) p>
gslist (type) () () p>
gslist (type) (type a): (ent (a)) () p>
~ gslist (type) () (clear ();) p>
); p>
p>
struct gslist_iterator (type): slist_iterator ( p>
gslist_iterator (type) (gslist (type) & a) p>
: ((slist &) s) () p>
type operator () () p>
(return type (slist_iterator:: operator () ());) p>
) p>
на кінці рядків вказує, що такий рядок
є частиною визначається макросу. p>
За допомогою цього макросу список покажчиків на ім'я,
аналогічний використаного раніше класу nlist, можна визначити так: p>
# include "name.h" p>
typedef name * Pname; p>
declare (gslist, Pname);// описати клас gslist (Pname) p>
gslist (Pname) nl;// описати один gslist (Pname) p>
Макрос declare (описати) визначений ст. Він конкатенірует
свої параметри і викликає макрос з такою назвою, у даному випадку gslistdeclare,
описаний вище. Параметр ім'я типу для declare повинен бути простим ім'ям.
Використаний метод макровизначеннями не може обробляти імена типів на зразок
name *, тому застосовується typedef. p>
Використання виведення класу гарантує, що всі приватні
випадки узагальненого класу поділяють код. Цей метод можна застосовувати тільки для
створення класів об'єктів того ж розміру або менше, ніж базовий клас,
який використовується в макросі. p>
Обмежені Інтерфейси h2>
Клас slist - досить загального характеру. Іноді подібна
спільність не потрібно або навіть небажана. Обмежені види списків, такі
як стеки і черги, навіть більш звичайні, ніж сам узагальнений список. Такі
структури даних можна задати, не описавши базовий клас як відкритий. Наприклад,
чергу цілих можна визначити так: p>
# include "slist.h" p>
class iqueue: slist ( p>
// передбачається
sizeof (int) <= sizeof (void *) p>
public: p>
void put (int a) (slist:: append ((void *) a);) p>
int det () (return int (slist:: get ());) p>
iqueue () () p>
); p>
При такому виведення здійснюються два логічно розділених
дії: поняття списку обмежується поняттям черги (зводиться до нього), і
задається тип int, щоб звести поняття черги до типу даних чергу цілих,
iqueue. Ці дві дії можна виконувати і роздільно. Тут перша частина - це
список, обмежений так, що вона може використовуватися тільки як стек: p>
# include "slist.h" p>
class stack: slist ( p>
public: p>
slist:: insert; p>
slist:: get; p>
stack () () p>
stack (ent a): (a) () p>
); p>
який потім використовується для створення типу "стек
покажчиків на символи ": p>
# include "stack.h" p>
class cp: stack ( p>
public: p>
void push (char * a) (slist:: insert (a);) p>
char * pop () (return (char *) slist:: get ();) p>
nlist () () p>
); p>
Список літератури h2>
Для підготовки даної роботи були використані матеріали
з сайту http://www.realcoding.net
p>