    Глава 16 

    Библиотека шаблонов классов Borland 

    В библиотеку шаблонов классов Borland входят контейнерные классы, которые 
можно включать в программы для управления коллекциями данных любых видов. 
Подобно обычным массивам и спискам, контейнерные классы — это хранилища 
данных, предназначенные для широкого использования. Кроме того, контейнерные 
классы объектно-ориентированы, и вы можете расширять их с помощью 
наследования и виртуальных функций, приспосабливая для самых различных типов 
объектов и задач. 
    Эта глава познакомит вас с библиотекой шаблонов классов Borland, в которой 
содержится ряд полезных контейнеров и сопутствующих классов. 
    В предыдущей версии Borland C++ 3.1 существовали две формы библиотеки: 
одна с обычными классами, другая — с шаблонами C++. В версии 4.0 
поддерживаются только более универсальные шаблоны классов, хотя их 
использование требует дополнительных усилий. Если вы не знаете, как писать 
программы с использованием шаблонов, перед тем, как продолжить изучение этой 
главы, перечитайте необходимые разделы главы 15. 
    Можно использовать библиотеку контейнерных классов в приложениях для DOS, 
EasyWin или Windows. Для упрощения примеры программ, приведенные в этой главе, 
запускаются в виде DOS- и Easy Win-приложений, однако вы можете использовать 
все, что описано в этой главе, и в ваших собственных программах для Windows. 

    ЗАМЕЧАНИЕ 

    В версии 4.0 поставляется первоначальная библиотека классов в подкаталоге 
OBSOLETE, расположенном в каталоге INCLUDE\CLASSIJB, а ее реализация находится 
в библиотеке LIB\ОВSОLETE.LIB, Этими классами следует пользоваться только дня 
компиляции уже существующих программ. Для издания новых программ используйте 
библиотеку шаблонов. 


    Введение в шаблоны контейнерных классов 

    В контейнерах можно запоминать объекты данных любых видов и выбирать любой 
метод хранения. Например, можно запоминать объекты в векторе (масси-


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

    АТД и ФСД 

    Контейнерный класс — это абстрактный тип данных (АТД). Он обеспечивает 
абстракцию методов хранения данных, например стек — АТД с такими функциями, 
как Push() и Рор(), для запоминания и восстановления данных методом стека. 
Внутренняя реализация этих функций не зависит от концепции стека. Вы можете 
создать стек с помощью вектора или использовать список. Стек — просто метод 
программирования, абстракция, которая не имеет ничего общего с конкретными 
деталями реализации внутреннего хранения данных в нем. 
    Однако векторы и списки неотделимы от их внутренней структуры. Поэтому они 
называются фундаментальными структурами данных (ФСД). Не давая точного 
определения, можно сказать, что ФСД — любая структура, которая может 
использоваться для реализации АТД. Например, можно реализовать класс стека 
(АТД) с помощью связного списка (ФСД). Или же вы можете реализовать стек с 
помощью другого фундаментального типа. 

    ЗАМЕЧАНИЕ 

    Гуру C++, без сомнения, только для того, чтобы привести нас в 
замешательство, называют массивы векторами. Однако в действительности это не 
одно и то же. Вектор — это фундаментальная структура данных, в которой объекты 
запоминаются один за другим. Массив же — это абстракция, обеспечивающая 
случайный доступ к объектам посредством оператора индексирования []. Как 
контейнер массив можно реализовать с помощью вектора, списка или другого 
фундаментального механизма запоминания данных.
 
    В каждом контейнерном классе комбинируются один АТД и одна ФСД. Имя 
результирующего класса может звучать как TArrayAsVector. По такому имени можно 
определить, что контейнер — это массив (АТД), реализованный вектором (ФСД). 
Начальная буква Т означает "тип". 
    В библиотеке шаблонов Borland — широкий выбор шаблонов АДТ и ФСД плюс 
несколько других классов. Для того чтобы воспользоваться контейнерным 
классом, следует задать тип данных, которые вы собираетесь запоминать, в 
общем виде: TClass<T>, где Т — ваш тип данных. Например, TStackAsList<long> 
задает стековый   контейнерный   класс,    способный   запоминать   длинные   
целые   объекты   в   связном   списке; TQueueAsVector<MyDataType> создает 
контейнерный класс очереди, в котором можно запоминать объекты типа MyDataType 
с использованием вектора в качестве внутреннего механизма запоминания данных. 
    Контейнеры могут содержать простые объекты (целые, вещественные и т.д.), 
структурные данные (массивы, строки и структуры) и объекты классов. Вы 
можете хранить в контейнере непосредственно сами объекты или же указатели на 
них. Возможности хранения данных практически безграничны. 

    Как создать контейнер         

    Покажем на простом примере, как создаются контейнерные классы. Сначала 
следует включить один или несколько заголовочных файлов, содержащихся в 
подкаталоге INCLUDE\CLASSLIB каталога C:\BC45. Например, для того чтобы 
воспользоваться стековым контейнером, следует включить заголовочный файл STACKS.H. 

    «include <classlib\stacks.h>	

    Затем создайте контейнерный объект одного из классов, содержащихся в 
заголовочном файле, например   TStack As Vector. Поищите конструктор класса 
в вашем справочнике по библиотекам ВС45.	

    TStackAsVector(unsigned max = DEFAULT_STACK_SIZE); 

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

    TStackAsVector<double> myStack(100); 

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


    Глава 16. Библиотека шаблонов классов Borland	425 

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

    ЗАМЕЧАНИЕ 

    Вы можете скомпилировать STACKED. CPP как DOS- или Easy Win-приложение, 
Однако в обоих случаях программу следует скомпоновать вместе с библиотекой 
скомпилированных классов BIDSx.LIB (x — модель памяти); 

    bcc stacked bidss.lib 

    BIDS расшифровывается как Borland International Data Structure (структура 
данных Borland International). Для каждой модели памяти существует свой файл, 
идентифицируемый последней буквой имени файла: S (small -малая), С (compact — 
компактная), М (medium — средняя), L (large — большая), Н (huge — огромная) и 
Hflat — линейная 32-битовая). 
    Для компиляции программы как EasyWin-приложения щелкните правой кнопкой 
мыши или нажмите <ALT+F10> и выберите TargetExpert, Установите Target Type 
равным EasyWin и задайте Large или Small Target Model (другие модели памяти 
тоже годятся, однако для Windows-приложений лучше всего подходит Large). 
Убедитесь, что помечена ячейка Class Library в Standard Libraries. Прочие 
примеры программ в этом разделе компилируются аналогично. 

    Листинг 16.1.   STACKED.CPP (демонстрация TStackAsVector) 

    1:	#include <iostream.h> 
    2:	#include <classlib\stacks.h> 
    3: 
    4:	#define TRUE 1 
    5:	#define FALSE 0                 
    6:	
    7:	// Создать глобальный контейнерный объект 
    8:	TStackAsVector<double> myStack(100);     
    I?!	. int nain()        
    11;	{                  
    12:	double d;      
    13:	int done = FALSE; 
    14: 
    15:	// Запросить значения и поместить их в стек 
    16:	while (Idone && !myStack.IsFull()) { 
    17:	  cout « "Enter double value (0.0 to quit): "; 
    18:	  cin » d;       // Ввести значение пользователя 
    19:	  if (d == 0.0) 
    20:	   done = TRUE;    // Закончить цикл while
    21:	else 
    22:	   myStack.Push(d); // Поместить d в стек 
    23:	} 
    24 
    25	// Отобразить информацию о стеке 
    26	int n = myStack.GetITemsInContainer();            
    27	cout « endl « "Number of objects: " « n « endl;   
    28	if (n > 0) 
    29	   cout « "Top of stack: " « myStack.Top() « endl « endl; 
    30 
    31	// Извлечь значения из стека и отобразить их 
    32	while (!myStack.IsEmpty()) 
    33	  cout « myStack.Pop() « endl; 
    34 
    35	return 0; 
    36	}

    Запустите программу STACKED на выполнение и введите несколько вещественных 
значений с плавающей запятой. Если вы введете 0, программа отобразит число 
элементов, запомненных в стеке, и значение, находящееся в верхушке стека. В 
программе извлекаются и отображаются значения из стека, что опорожняет его 
перед завершением демонстрации. Поскольку структура данных стека основана на 
принципе "последним вошел — первым вышел", значения будут выводиться в 
обратном порядке. На рис. 16.1 приводятся результаты работы программы:	


    426     Часть IV. Разработка приложений 

    Enter double value (0.0 to quit)  9.9 
    Enter double value (0.0 to quit)  3,14159 
    Enter double value (0.0 to quit)  -88.55 
    Enter double value (0.0 to quit)  4.32 
    Enter double value {0.0 to quit)  6.9 
    Enter double value (0.0(to quit)  0 
    Numbexfof objects: 5 
    Top -of- stack: 6. 9 
    6.9 
    4 .32 
    -88.55 
    3.14159 
    9.9
 
    Рис. 16.1. Результат работы программы STACKED 

    Для определения, достигло ли число элементов стека максимума, указанного 
при инициализации, следует вызывать функцию IsFull(). Чтобы проверить, пуст ли 
стек, следует обратиться к функции IsEmpty(). Вы можете вызывать функцию 
IsFull() перед сохранением объекта в стеке следующим образом: 

    if  (ImyStack.IsFull()) 
       myStack.Push; 

    Переменная d имеет тип double. Она не обязательно должна быть объектом 
класса, порожденным из поставляемого базового класса, как это было необходимо 
в старой, основанной на использовании объектов, библиотеке. Поскольку все 
контейнеры новой библиотеки — шаблоны, они подстраиваются под любой тип данных, 
который вы захотите хранить. 
    Для извлечения значения следует обратиться к функции Рор(), которая удаляет 
один объект из стека. Функция Рор() возвращает объект, сохраненный в 
контейнере. Это может быть копия объекта или указатель на динамический объект, 
в зависимости от типа контейнера. Обычно следует вызывать функцию IsEmpty() для 
предотвращения попытки извлечь значение из пустого стека: 

    if  ( !myStack. IsEmpty() )	
       d = myStack.PopO; 

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

    if ( !myStack.IsEmpty() ) 
       d = myStack.Top(); 

    Для определения числа объектов, хранящихся в стеке, следует обратиться к 
функции GetItemsInContainer(). Обращение к ней всегда безопасно: 

    int n = myStack.GetltemsInContainer();	

    Большинство контейнерных классов имеют функции с одинаковыми именами, 
такими, как GetltemsInContainer(). Изучив функции в одном контейнере, вы сразу 
же будете знать функции и в другом. Однако только стек имеет функции типа 
Push(), Рор() и Тор().	

    Соглашения именования шаблонов 

    Бессчетное количество имен классов в библиотеке поначалу может показаться 
ошеломляющим, и поскольку одни классы выводятся из других, то часто приходится 
читать информацию о нескольких классах для того, чтобы разобраться, что делает 
один из них. Например, в классе TStackAsVector определен только конструктор. В 
предке класса TMStackAsVector определяются другие функции, подобные PushO, 
Рор() и Тор(). Вам придется разобраться в обоих классах, чтобы понять, как 
работает TStackAsVector. 

    ЗАМЕЧАНИЕ 

    В классах с именами, начинающимися на ТМ, обеспечивается управление 
памятью для контейнера. Эти цклассы следует использовать только в том случае, 
если вы собираетесь реализовать свое собственное управление памятью, о чем 
речь пойдет позже. 
    Хорошее знание имен контейнерных классов поможет вам продраться сквозь 
дебри библиотечной документации. Еще более важно свободно владеть соглашениями 
именования классов. Имена контейнерных классов характеризуют их возможности и 
взаимосвязи. Сперва имена контейнерных классов могут показаться вам столь же 
непонятными, как слова песен панк-рока, однако разложите имя каждого класса на 
составные части и вы обнаружите, что все они составлены по единому плану. 


    Глава 16. Библиотека шаблонов классов Borland     427 

    В целом имена контейнерных классов имеют следующий вид:	
   
    Т [префикс] [АТД] [АsФСД] [суффикс] 

    Все имена классов начинаются с буквы Т (тип). Необязательный префикс 
указывает на способ запоминания данных — например, сортируются ли объекты (S) 
или же запоминаются косвенно в виде указателей (I). Затем следует такой АТД, 
как массив (Array) или стек (Stack). Если за ним следует слово As, значит 
контейнер реализован на основе заданной ФСД, которая может быть, к примеру, 
списком (List), двусвязным списком (DoubleList) или другим фундаментальным 
типом данных. Наконец, необязательный параметр "суффикс" ссылается на тип 
сопутствующего класса. Некоторые классы, к примеру, заканчиваются словом 
Iterator, означающим, что этот класс выполняет итерационные действия над 
данными в контейнере.	, 
    Однако не все классы в библиотеке твердо придерживаются этих соглашений 
именования. Некоторые классы даже не являются контейнерными — они могут быть, 
к примеру, фундаментальными структурами, использующимися для реализации 
контейнеров. Другие классы играют просто вспомогательную роль. Вы также не 
найдете абсолютно всех возможных комбинаций АТД и ФСД. Например, не существует 
класса TStackAsHashTable, поскольку бессмысленно реализовать стек (тип данных 
с последовательным доступом) с помощью хэш-таблицы (структура данных с 
произвольным доступом). 
    В табл. 16.1 перечислены библиотечные абстрактные типы данных и 
соответствующие им заголовочные файлы, а также фундаментальные структуры 
данных, используемые для реализации каждого контейнера. Например, Deque может 
реализоваться как DoubleList или Vector. Исходя из таблицы, можно предположить 
существование контейнерных классов с именами TDequeAsDoubleList и 
TDequeAsVector. Можно также заключить, что класса TDequeAsHashTable 
не существует. Ни один контейнер не использует структуру BinarySearchTree. 

    Таблица 16.1. Абстрактные типы данных 

                        АТД 		Заголовочный 	Дерево 	  Двусвязный Хэш-	      Список    Вектор 
                                    	    файл 		двоичного список	       таблица
					поиска
    Array (Массив)		ARRAYS.H					           • 
    Bag (Мультимножество)	BAGS.H					                             • 
    Deque (Дек — очередь 
    с двусторонним доступом)	DEQUES.H		         •			           • 
    Dictionary (Словарь)	DICT.H			                                 • 
    Queue (Очередь)		QUEUES.H		         •			           • 
    Set (Множество)		SETS.H					                             • 
    Stack (Стек)		STACKS. H				               •	           • 

    В табл. 16.2 приводится та же информация, что и в табл. 16.1, с той 
лишь разницей, что в ней перечислены фундаментальные структуры данных и их 
заголовочные файлы. Например, в табл. 16.2 указывается, что только контейнер 
Stack может использовать List, следовательно, можно предположить существование 
в библиотеке класса с именем TStackList. Таблица также свидетельствует о том, 
что Vector — самая часто используемая фундаментальная структура, 
задействованная в шести различных типах контейнеров. 

    Таблица 16.2. Фундаментальные структуры данных 

               ФСД                    Заголовочный 	Array   Bag  Deque	Dictionary	  Queue  Set  Stack 
                                                 файл

    Дерево двоичного       BINIMP.H		 
    поиска
    Двусвязный список      DLISTIMP.H                                 •                                 •
   Хэш-таблица	         HASHIMP.H                                                 • 	
   Список	                           LISTIMP.H                                                                                           • 
   Вектор	                           VECTIMP.H	   •         •                                               •        •          • 


    428     Часть IV. Разработка приложений 

    В табл.  16.3 имена АДТ и ФСД, приведенные в табл. 16.1 и 16.2, дополняются 
несколькими модификаторами способа хранения. Модификаторы задаются сразу 
после лидирующей буквы Т в имени класса. Например TlStackVector означает косвенный 
контейнерный класс стека, реализованный с помощью вектора. Косвенные контейнеры 
хранят указатели на объекты. Без модификатора I (например, в TStackVector) контейнер
запоминает непосредственно объекты данных. Добавочное S означает автоматическую 
сортировку. Например, кжасс TSArrayAsVector — отсортированный массив объектов, 
реализованный с помощью вектора, класс TШSArrayAsVector — отсортированный 
массив указателей на объекты, реализованный с помощью вектора. 

    Таблица 16.3. Модификаторы способов хранения	.   ;. 

    Модификатор	           СВОЙСТВО 

    С	С подсчетом числа объектов 
    I	С запоминанием указателей (без I  запоминаются сами объекты) 
    IS	С сортировкой и запоминанием указателей 
    М	С управлением памятью 
    MC	С управлением памятью и подсчетом числа объектов 
    Ml	С управлением памятью и запоминанием указателей 
    MIC	С управлением памятью, запоминанием указателей и подсчетом числа объектов 
    MIS	С управлением памятью, запоминанием указателей и сортировкой 
    MS	С управлением памятью и сортировкой 
    S	С сортировкой 

    В классах с модификатором М обеспечивается управление памятью для классов 
со схожими именами. (Я называю их контейнерами класса М, возможно, потому, 
что насмотрелся повторных показов фильма Star Trek). Например, в классе 
TMQueueAsVector обеспечивается управление памятью для класса TQueueAsVector. 
Единственная причина использования контейнера класса М — замена 
стандартного управления памятью вашим собственным. Большинство программистов 
крайне редко занимаются подобными вещами. 
    Контейнеры класса М используют класс выделения памяти по умолчанию 
TStandardAllocator, определенный в заголовочном файле ALLOCSTR.H. Этот класс 
пользуется базовыми операторами new, new[], delete и delete[], которые в 
Borland C++ вызывают стандартные функции языка С malloc() и free(). Если вы 
хотите реализовать свой класс выделения памяти, создайте класс с такими же 
функциями-членами, как в TStandardAllocator, и реализуйте в них выделение и 
освобождение памяти по своему усмотрению. 
    Например, вы можете зарезервировать блок памяти в начале программы и 
использовать ваш собственный класс выделения памяти для размещения 
контейнерных объектов внутри этого блока (см. листинг PERSIST. CPP и 
комментарии к нему в главе 15, где демонстрируются несколько подходящих 
приемов). Это может гарантировать наличие свободного пространства для 
некоторых видов объектов, используемых, например, в процедурах обработки 
критических ошибок, для которых стандартный метод выделения памяти может 
быть непригоден. 

    ЗАМЕЧАНИЕ 

    Контейнеры с сортировкой должны иметь возможность сравнивать свои объекты. 
Контейнеры с хранением непосредственно объектов должны быть способны их 
копировать. Любители напустить туман сказали бы: "Объекты должны иметь 
содержательную семантику копирования и сравнения". Это означает, что вы должны 
снабдить класс конструктором копии и перегруженными операторами == и <. Если 
вы в растерянности, перечитайте часть III. 

    После беглого ознакомления с библиотекой контейнеров рассмотрим несколько 
дополнительных классов, завершающихся одним из трех возможных суффиксов (я 
называю их модификаторами типа класса, однако стандартного термина для них не 
существует). Классы, имена которых заканчиваются словом Element, обеспечивают 
"списочные оболочки" объектов. Существует только два таких класса — 
TMDoubleListElement и TMListElement. Они дают возможность создавать списки 
объектов, не имеющих полей-указателей. Вы не мо жете использовать классы 
Element в контейнерах абстрактных типов данных. Они используются только 
фунддаментальными структурами данных.


    Глава 16. Библиотека шаблонов классов Borland     429 

    Другой модификатор типа класса, Imp, означает реализацию. В общем, классы, 
имена которых заканчиваются на Imp, — это фундаментальные структуры данных, 
используемые для реализации абстрактных контейнеров. Например, класс 
TDoubleListlmp реализует двусвязный список. Вы можете использовать классы Imp 
для создания своих собственных, контейнеров, однако для этого необходимо 
хорошо разбираться во внутренностях классов. В большинстве случаев лучше 
пользоваться абстрактным контейнером, подобным TDequeAsDoubleList, чем 
реализацией TDoubleListlmp, на которой основывается абстрактный контейнер. 
    Третий и последний тип модификатора, Iterator, означает, что класс 
снабжает средствами итерации фундаментальную структуру или контейнер с похожим 
именем. Итератор обеспечивает доступ ко всем объектам, содержащимся в 
контейнере. Например, класс TDequeAsVectorlterator — итератор для объектов, 
хранящихся в контейнере TDequeAsVector. Классы итераторов, заканчивающиеся на 
Imp, снабжают средствами итерации фундаментальные структуры. Например, класс 
TDoubleListlteratorlmp — реализация итератора для объектов, хранящихся в 
структуре TDoubleListlmp. 
    Наконец, существует еще один класс, TShouldDelete, "плавающий" под своим 
флагом. Как можно заключить из его имени, этот специальный класс сообщает, 
должен ли контейнер удалять объекты, которые он хранит, или следует переложить 
эту обязанность на вас. Изучите раздел "Принадлежность объектов" в этой главе 
для того, чтобы узнать больше об этом классе.	

    Развитие "сознания" класса 

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

    Косвенное и непосредственное запоминание объектов в контейнерах 

    Независимо от типа контейнера, важно решить, запоминать ли объекты 
косвенно (по указателю) или непосредственно (копированием). Контейнеры, у 
которых в имени содержится буква I, подобные TIQueueAsVector, запоминают 
объекты косвенно, в виде указателей на них. Контейнеры без I (TQueueAsVector, 
например) запоминают непосредственно сами объекты. В листинге 16.2 
демонстрируется использование контейнеров обоих видов. 

    Листинг 16.2.  DIRECT.CPP (контейнеры с непосредственным и косвенным запоминанием) 

     1:	#include <iostream.h>                                           
     2:	#include <cstring.h>                                              
     3:	#include <classlib\arrays. h> 
     4:	
     5:	#define TRUE 1 
     6:	#define FALSE 0 
     7: 
     8: 	// Контейнеры с непосредственным и косвенным запоминанием объектов   
     9:	TArrayAsVector<string> dStrings(10);                           
    10:	TIArrayAsVector<string> iStrings(10); 
    11:	
    12:	int main() 
    13:	{ 
    14:	int done = FALSE; // Управляющая переменная цикла while 
    15:	int i;         // Управляющая переменная цикла for 
    16:	string s;        // Объект вводимой строки 
    17:	char buf[81];    // Символьный буфер ввода 
    18: 
    19:	while (!done) { 
    20:	  cout « "Enter a string: ";   // Приглашение ввода строки 
    21:	  cin.getline(buf, sizeof(buf)); // Записать в буфер 
    22:	  s = buf;               // Преобразовать в объект string 
    23:	  if (s.length() == 0)       // Проверить длину на нуль 
    24:	    done = TRUE;           // Если да - завершить цикл while 
    25:	  else {                 // ...ина^е. 
    26:	   dStrings.Add(s);   "     // Добавить в контейнер 
    21:	   iStrings.Add (new string(s)); // Добавить в косвенный контейнер 
    28:	} 
    29:	} 
    30: 
    31:	// Отобразить объекты контейнера с непосредственным запоминанием 
    32:	cout << endl;. 
    33:	cout << "Direct string array" << endl;                
    

    430	Часть IV. Разработка приложений 

34:	for (i = 0; i < dStrings.GetItemsInContainer(); i++ )
35:	cout << dStrings[i] << endl;		
36: 
37:	// Отобразить объекты контейнера с косвенным запоминанием		
38:	cout << endl;		
39:	cout << "Indirect string array" << endl;		
40:	for ( i = 0; i < iStrinfls.GetltemsInContainer(); i++ ) 
41:	  cout << *iStrings[i] << endl; 
42:		 
43:	return 0; 
44:	} 

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

    TArrayAsVector<string> dStrings(10); 

    Оператор, очень похожий на предыдущий, однако использующий другой 
класс, создает контейнер, который запоминает указатели на динамические строки, 
созданные с помощью оператора new: 

    TIArrayAsVector<string> iStrings(10); 

    Оба контейнера, dStrings и iStrings, обладают одинаковыми наборами функций 
и возможностями. Они различаются только тем, что в dStrings создается копия 
запоминаемой строки, а в iStrings запоминается указатель на строковый объект. 
    Далее программа запрашивает строку, которую помещает в символьный 
буфер. После использования буфера для создания строкового объекта s 
программа вставляет его в контейнер dStrings с помощью обращения к функции 
Add() контейнера класса: 

    dStrings.Add(s); 

    Функция Add() запоминает новый объект в массиве контейнера. Не все 
контейнерные классы снабжаются функцией AddO, но все контейнеры имеют как 
минимум один способ запоминания объектов. Для вставки строки в контейнер 
iString в программе используется оператор	

    iString.Add(new string(s)); 

    Было бы некорректно передавать адрес объекта s функции косвенного 
контейнера Add(). Строковая переменная s — только временный объект, который 
размещается в стеке программы. Передача адреса этого объекта косвенному 
контейнеру послужит причиной возникновения всевозможных проблем при 
уничтожении контейнера. Мораль сей басни такова — при использовании косвенных 
контейнеров всегда следует знать, где размещаются ваши объекты. Неплохо, 
например, воспользоваться оператором создания динамических строковых объектов 
new для запоминания их в контейнере.	 
    Поскольку в этом примере контейнерами служат массивы, они снабжаются 
функцией GetltemsInContainer(), сообщающей число сохраненных строк. В программе 
эта функция используется в цикле for для отображения объектов в контейнере с 
запоминанием непосредственно объектов:

    for (i=0;   i < dStrings.GetItemsInContainer();   i++)	
       cout « dStrings[i] « endl; 

    Отображение объектов в косвенном контейнере почти такое же, только 
необходимо разыменовывать элементы массива, поскольку все они — указатели:

    for ( i= 0 ;  i < iStrings.GetItemsInContainer();  i++) 
      cout << *iStrings[i] << endl;         // Обратите внимание на разыменование

    Удалите "звездочку" из последнего оператора, затем скомпилируйте заново и 
запустите программу на выполнение. Теперь будут выводиться адреса объектов, 
поскольку компилятор воспринимает iStrings[i] a качестве указателя на строку. 

    Итераторы 

    Итераторы предоставляют альтернативный метод доступа к объектам 
контейнера. Итераторами снабжаются абстрактные контейнеры вроде массивов и 
очередей, а также такие фундаментальные структуры, как списки и векторы. 
    С помощью итератора вы можете "передвигаться" по объектам в структуре без 
их удаления. Например, обычно только верхний объект стека доступен для 
просмотра, для получения же других объектов необходимо удалять их, вызывая 
функцию Рор(). С помощью итератора можно "передвигаться" по стеку для 
выполнения операций над его содержимым. 
    

    Глава 16. Библиотека шаблонов классов Borland	431 

    Простой пример демонстрирует, как использовать итераторы для просмотра по 
объектов в очереди. Как и в программе DIRECT.CPP, в листинге 16.3 набор строк 
запоминается косвенным образом, но на этот раз в контейнере класса TIQueueAsDoubleList. 

    Листинг 16.3.   ITERATE.CPP (демонстрация итераторов) 

    1:   «include <iostream.h> 
    2:   «include <cstring.h> 
    3:   «include <classlib\queues.h> 
    4: 
    5: // Объявление глобального косвенного строкового контейнера очереди
    6 TIQueueAsDoubleList<string> iQueue;	
    7: 
    8: int main()	
    9: {	
    10:	// Запомнить несколько строк в очереди                                 
    11:	iQueue.Put(new string("Line up")); 
    12:	iQueue.Put(new string("for the"));                                    
    13:	iQueue.Put(new string("Magical"));         
    14:	iQueue.Put(new string("Mystery"));   
    15:	iQueue.Put(new string("Tour"  ));                                      
    16:	
    17:	// Объявить косвенный строковый итератор для iQueue                    
    18:	TIQueueAsDoubleListIterator<string> iterator(iOueue); 
    19: 
    20:	// Применить итератор для сканирования по очереди 
    21:	cout « "\nWalk the queue using an iterator:" « endl; 
    22:	cout « "————————————————————" « endl; 
    ! 23:	while (iterator != 0) { 
    24:	   cout « «iterator.Current() « endl; 
    25:	   iterater++; 
    26:	} 
    27:	 
    28:	// Применить стандартный метод извлечения объектов из очереди          
    29:	cout « "\nExtract queue objects:" « endl;               
    30:	cout « "———————————————————" « endl; 
    31:	while ( !iQueue.IsEmpty() ) { 
    32:	   string *p = iQueue.Get();                                     
    33:	   cout « *p « endl; 
    34:	   delete p; 
    35:	}                                                                      
    36:	
    37:	return 0; 
    38:	}

    В качестве контейнера в программе объявляется глобальный объект с именем 
iQueue. Поскольку контейнер — косвенный (его имя начинается с TI), в нем 
запоминаются указатели на строковые объекты: 

    TIQueueAsOoubleList<string> iQueue;	

   Как я уже упоминал, обычно лучше всего пользоваться оператором создания 
динамических объектов new для запоминания их в косвенном контейнере. Для 
добавления нескольких строк в очередь программа передает указатели, 
возвращаемые оператором new, функции-члену Put() всех контейнеров очергдей. 
Функция PutO для очереди имеет такое же значение, как и функция AddO для массивов: 

    iQueue.Put(new string("Hagical")); 
    iQueue.Put(new string("Mystery")); 

    Затем в программе создается итератор класса, разработанный для совместного 
использования с классом TIQueueAsDoubleList. Для формирования имени класса 
добавьте в конце слово Iterator: 

    TIQueueAsDoubleListIterator<string> iterator(iQueue); 

    Можно сломать язык, пока выговоришь хотя бы половину этого имени, поэтому 
в подобных случаях полезно создать более короткий алиас с помощью оператора 
typedef, например:	

    typedef TIQueueAsDoubleListIterator<string> Tlterator; 

    Директива typedef означает, что отныне TIterator — синоним предыдущего 
"кулдыкающего" словосочетания. Объявление контейнера теперь выглядит намного 
приятнее (и намного легче для чтения): 

    TIterator iterator(iQueue); 

    В любом случае, объявление создаст итератор (который я назвал iterator) 
типа TIQueueAsDoubleListIterator<string>. Передача объекта iQueue конструктору 
итератора связывает итератор с этим контейнером. Операции с использованием 
итератора будут выполняться над данными контейнера iQueue. 


    432	Часть IV. Разработка приложений 

     Например, выражение iterator.Current() возвращает текущий элемент 
контейнера Queue — первого объекта при первом обращении. Если контейнер пуст, 
итератор вернет нулевой указатель (или нулевую ссылку, если контейнер 
запоминает сами объекты). 
    При использовании итератора в выражениях имейте в виду, что он возвращает 
целое значение. Это возможно благодаря тому, что в классах-итераторах 
перегружается оператор преобразования к типу int(). Таким образом, когда объект 
итератора используется в выражениях, оперирующих целыми значениями, в 
программе вызывается оператор int() для преобразования объекта к этому 
значению. Если это значение равно нулю (ложь), значит итератор завершил 
сканирование данных в контейнере. 
    С помощью функций и значений итератора можно "прогуливаться по очереди", 
отображая объекты контейнера в том порядке, в котором они запоминались:	

    while (iterator != 0) {	// Использовать перегруженный оператор int()       
       cout « «iterater.Current() « nedl;	// Извлечь текущий объект               
       iterator++;                                              // Переместить итератор к следующему объекту 

    Во всех итераторах реализован оператор инкремента ++ (как постфиксный, так 
и префиксный). Как продемонстрировано в предыдущем фрагменте, вы можете 
пользоваться выражениями iterator++ (или ++iterator) для перемещения объекта 
итератора к следующему объекту в контейнере. Оператор ++ возвращает объект 
того же типа, что и контейнер, — этим фактом вы можете воспользоваться для 
эффективной реализации циклов. Установим итератор в начальное положение с 
помощью обращения к его функции Restart() и объявим указатель на строку р 
следующим образом:	

    iterator.Restart();      // Установить итератор на первый объект
    string *p;                                    // Объявить указатель на строку 

    Теперь можно написать следующий цикл while (или что-нибудь в этом роде) 
для перемещения по контейнеру: 

    while (р = iterator++)   // Извлечь текущий объект и продвинуть итератор 
    cout << *р << endl;      // Отобразить объект,  адресуемый р 

    В управляющем выражении цикла while указателю р присваивается адрес 
текущего строкового объекта, кроме того итератор перемещается к следующему 
объекту. По достижении последнего объекта управляющее выражение принимает 
нулевое значение и цикл завершается. В простом операторе вывода в поток 
отображается каждый строковый объект. 
    Если вы скомпилируете предыдущий код, возможно, компилятор выдаст вам 
предупреждение "possibly incorrect assignment" ("возможно неверное 
присваивание"), поскольку оператор = в данном контексте распознается как 
возможная ошибка в написании оператора сравнения ==. Тем не менее, наличие 
единственного знака = в данном случае корректно, поскольку мы хотим присвоить 
значение, возвращаемое итератором, указателю р. Если вас беспокоит присутствие 
этого предупреждения, замените предыдущий код следующим фрагментом, он 
полностью эквивалентен приведенному выше, хотя и труднее читается: 

    while ( ( р = iterator++ ) != 0 )     
         cout « «р « endl; 

    Все итераторы имеют, по крайней мере, один конструктор, функцию Restart() и 
перегруженные операторы int() и ++. Некоторые итераторы снабжаются также 
дополнительными специальными функциями. Например, в итераторах массивов 
перегружается функция Restart с двумя параметрами: 

    void   Restart(unsigned start,   unsigned stop); 

    Можно передать функции Restart() значения индексов для того, чтобы начать 
итерационный процесс в ограниченном диапазоне объектов, хранящихся в контейнере-массиве. 

    Принадлежность объектов 

    Запомнить объект в контейнере совсем не так легко, как положить конфетку 
на блюдечко. С конфеткой все просто. Вы ее положили, затем откусили кусочек — 
и все прекрасно. С контейнерными объектами больше хлопот (и меньше риска 
растолстеть). Вам необходимо знать, имеете ли вы дело с динамическими 
объектами, созданными с помощью оператора new, или же с объектами, которые 
существуют временно в стеке, а может быть с глобальными, размещающимися в 
сегменте данных. Вам также должно быть известно, "подлинный" объект или это 
только его "внебрачная" копия. Объектам класса могут потребоваться 
деструкторы, в особенности, если они ссылаются на другие динамические 
структуры. В подобных случаях вы должны обеспечить вызов деструктора и 
своевременное уничтожение объектов. 
    В результате этих хлопот возникает вопрос, должны ли объекты принадлежать 
контейнеру, в котором они запоминаются? В целом, если объекты принадлежат 
контейнеру, он должен их уничтожать автоматически, когда объекты удаляются из 
контейнера. Если же объекты не принадлежат контейнеру, ответственность за их 
уничтожение в случае необходимости лежит на вас.	


    Глава 16. Библиотека шаблонов классов Borland	433 

    Как задать принадлежность объектов 

    Можно задать принадлежность объектов, создав косвенный контейнер, подобный 
TIArrayAsVector, и вызвав функцию, наследованную из класса TShouldDelete. 
Например, рассмотрим следующий косвенный контейнер, запоминающий указатели на 
строковые объекты: 

    TIArrayAsVector<string> stuff(100); 

   Для того чтобы определить, владеет ли stuff своими объектами (по умолчанию   владеет), 
следует вызвать функцию OwnsElements(), наследованную из класса TShouldDelete:

    cout « stuff.OwnsElements() « endl;	

  Функция OwnsElements() возвращает истину (1), если контейнер владеет своими объектами, 
или ложь (0), если нет. 
    Для изменения статуса собственности в контейнере следует передать истину 
или ложь второй перегруженной функции OwnsElement(), которая ничего не 
возвращает. Вообще неплохо определить константы TRUE и FALSE.для облегчения 
чтения текста программы: 

    #define TRUE 1 
    #define FALSE 0	

    ...
    stuff.OwnsElements(FALSE);  // Запретить владение объектов	

    Такая отмена прав собственности контейнера stuff означает, что объекты не 
будут удаляться автоматически. Для разрешения автоматического удаления следует 
вызвать функцию OwnsElementsO с аргументом TRUE: 

    stuff.OwnsElements(TRUE);	


    ЗАМЕЧАНИЕ 

    Вы не сможете проделать нечто подобное с контейнерными классами, которые 
хранят сами объекты и не выводятся из TShouldDelete. Если вы создадите такой 
контейнер следующим образом: 

    TArrayAsVector<strmg> stuff2(100); , 

    вы не сможете изменить статус владения объектами stuff2: 

    stuff2.OwnsElements(TRUE);	// !!!

    При компиляции этого оператора будет выдано сообщение об ошибке 
" 'OwnsElements' is not a member of 'TArrayAsVector<string>' " ( 'OwnsElements' — 
не член 'TArrayAsVector<string>' ), поскольку контейнерные классы, запоминающие 
сами объекты, не выводятся из класса TShouldDelete. Вы можете задать статус 
владения только для косвенных контейнеров, которые хранят указатели на объекты. 


    Как использовать принадлежность объектов 

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

    Листинг 16.4.  OWNER.CPP (демонстрация принадлежности объектов) 

    1:	#include <iostream.h>               
    2:	#include <cstring.h>           
    3:	#include <classlib\arrays. h>                            
    4: 
    5:	// Определить алиасы имен типов 
    6:	typedef TIArrayAsVector<string> TContainer; 
    7: 
    8.	typedef TIArrayAsVectorlterator<string> TIterator;	
    9:	// Прототип функции отображения содержимого контейнера	
    10:	void ShowMe(const char «msg, TContainer &cr);
    11:		
    12:	int main()	
    13:	{ 
    14:	  TContainer *cp;   // Указатель на контейнер 
    15:	  string s("Tulips"); // Строковый объект	
    16: 
    17:	  // Создать контейнер и отобразить его права владения 
    18:	  ср = new TContainer(10, 0, 10);	
    19:	  cout « endl;                                  
    20:	  cout « "Array owns objects: ";	
    21:	  if (cp->OwnsElements()) 


    434	Часть IV. Разработка приложений 

    22:	  cout « "TRUE" « endl;   
    23:	else               
    24:	  cout « "FALSE" « endl;	
    25:		
    26:	// Запомнить несколько строковых объектов в контейнере	
    27:	cp->Add(new string("Tip")); 
    28:	cp->Add(new string("Toe")); 
    29:	cp->Add(new string ("Through"));	
    30:	cp->Add(new string("The"));	
    31:	cp->Add(&s); // ! ! ! Опасность! ! ! Смотри комментарий 
    32:	ShowMe("Original array: ", *cp);	
    33: 
    34:	// Удалить объект из контейнера и уничтожить по индексу 
    35:	cp->Detach(2, TShouldOelete::Delete);  
    36:	ShowMe("After Detach(2, Delete)", *cp);  
    37: 
    38:	// Удалить объект из контейнера, НЕ удаляя его при этом   
    39:	cp->Detach(&s, TShouldDelete::NoDelete);           
    40:	ShowMe("After Detach(&s, NoDelete)", *cp); 
    41: 
    42:	// Удалить последний объект и, возможно, уничтожить его	
    43:	int n = cp->GetItemsInContainer();	
    44:	cp->Detach(n - 1, TShouldDelete::DefDelete);	
    45:	ShowMe("After Detactj(n - 1, DefDelete)", *cp);	
    46: 
    47:	// Удалить все объекты из контейнера, и, возможно, уничтожить их 
    48:	Cp->Flush(); 
    49:	ShowMe( "After Flush()", *cp);	
    50:		
    51:	// Удалить контейнер. НЕ удалять объекты???	
    52:	delete cp; 
    53:		
    54:	return 0;     
    55:	}             
    56: 
    57:	// Отображает содержимое контейнера сг, переданного по ссылке 
    58:	void ShowMe(const char *msg, TContainer &cr) 
    59:	{ 
    60:	TIterator iterator(cr); // Создать итератор для контейнера 
    61:	string *sp;          // Указатель на объекты контейнера 
    62: 
    63:	cout « endl « msg « endl;         // Отобразить сообщение 
    64:	cout « "Items in container = ";       // Отобразить сообщение	
    65:	cout « cr.6etItemsInContainer() « endl; // Отобразить число элементов 
    66:	while ((sp = iterator++) != 0)       // Цикл по итератору 
    67:	  cout « " " « *sp;              // Отобразить элементы	
    68:	cout « endl;                   // Перевести строку 
    69:	}
 
    Для сокращения чрезмерно длинных объявлений в программе определяются два 
алиаса типов. Тип TContainer определяется как косвенный массив строк, 
реализованный с помощью вектора. Тип TIterator — итератор для этого 
контейнера. В программе объявляется указатель на тип TContainer, и с помощью 
оператора new создается объект контейнера:	

    TContainer «cp; 
    cp = new TContainer(10,  0,  10);	

    После отображения сообщения о том, владеет ли контейнер, адресуемый 
указателем ср, своими объектами (по умолчанию владеет), в контейнере 
запоминается несколько строковых объектов с помощью следующих операторов:	

    cp->Add(new string("Tip")); 

    Обратите внимание, что так же передается адрес временного строкового 
объекта s, размещающегося в системном стеке:	

    cp->Add(&s);    // !!!	

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


    Глава 16. Библиотека шаблонов классов Borland	435 

    Для удаления объекта из контейнера в программе выполняется следующий оператор: 

    cp->Detach( 2, TShouldDelete::Delete); 

    Аргумент 2 означает, что третий объект должен быть удален из контейнера. 
Другие объекты сдвигаются вверх для уплотнения освобожденного объектом места. 
Аргумент TShouldDelete::Delete задает, что этот объект должен быть уничтожен 
независимо от статуса владения. 
    Можно также задать, чтобы объект не уничтожался вне зависимости от статуса 
владения. Например, необходимо удалить локальный строковый объект из 
контейнера таким образом: 

    cp->Detach(&s,  TShouldDelete::NoDelete); 

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

    cp->Flush(); 

    При вызове функции Flush() удаляются все объекты, которыми владеет 
контейнер. Для предотвращения сброса вашего кода в "канализацию" в случае, 
если контейнер адресует локальные объекты, следует или отменить владение 
объектами, или заблаговременно удалить такие объекты из контейнера. 
    Можно сделать и так, чтобы объекты удалялись при условии, что контейнер 
владеет ими. Например, в программе можно удалить последний объект, 
индексированный целым п следующим образом:

    cp->Detach(n-1, TShouldDelete::DefDelete); 

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

    delete cp; 

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

    1. Вызвать функцию Flush() перед удалением контейнера. 
    2. Вывести новый контейнерный класс и написать к нему деструктор, который 
вызывает функцию Flush(). 

    Первое решение, с успехом примененное в примере программы, самое простое, 
но если вы забудете вызвать функцию Flush(), в вашей программе может 
образоваться неприятная утечка памяти. Второе решение гарантирует уничтожение 
динамического контейнера с предварительным вызовом функции Flush(). Для 
реализации последнего объявите новый шаблон класса в модуле OWNER.CPP: 

    template <class T> class TIMyArrayAsVector : 
         public TIArrayAsVector<T> { 
    public: 
       TIMyArrayAsVector(int upper,   int lower = 0,   int delta = 0)  : 
         TIArrayAsVector<T>(upper,  lower,  delta)  { } 
       ~TImyArrayAsVector  { Flush();  }
     };

    В шаблоне класса TIMyArrayAsVector вызывается конструктор его предка. 
Деструктор вызывает функцию Flush() с аргументом по умолчанию DefDelete. 
Контейнер, созданный из этого класса, автоматически уничтожает принадлежащие 
ему объекты, когда контейнер уничтожается или выходит из области видимости. 
    Если вы внесли предыдущие изменения в модуль OWNER.CPP, перепишите алиас 
typedef для TContainer так, как приведено ниже. Теперь в модифицированной 
программе будет использоваться новый производный класс. 

    typedef TIMyArrayAsVector<string> TContainer;        

   
    СОВЕТ

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

    436	Часть IV. Разработка приложений 

    Резкме 

    •   Библиотека шаблонов классов Borland предлагает широкий выбор 
контейнерных классов для хранения объектов данных различными способами. В 
версии Borland C++ 3.1 существовали две ,формы библиотеки: одна с обычными 
классами, другая с шаблонами C++. В версии 4.5 существует только одна 
библиотека шаблонов классов более широкого назначения. (Старые классы также 
входят в поставку, однако в новых программах ими пользоваться не следует.) 
    •   Вы можете использовать библиотеку шаблонов классов в программах для 
DOS, EasyWm и Windows. Поддерживаются 16- и 32-битовые библиотеки. 
    •  Абстрактные типы данных обеспечивают абстракцию методов хранения 
данных, например с помощью стека. 
    •  Фундаментальные структуры данных обеспечивают внутреннее представление 
хранения данных, например с помощью связного списка. 
    •   В каждом контейнерном классе комбинируются один АТД и одна ФСД. 
Например, класс TArrayAsVector реализует абстрактный массив с вектором в роли 
внутреннего механизма хранения данных. 
    •   Для того чтобы полностью использовать возможности библиотеки вам 
следует ознакомиться с соглашениями именования шаблонов, которые описываются в 
этой главе. Все имена классов начинаются с буквы Т, за которой по очереди 
следуют АДТ, ФДС и суффиксы, описывающие природу каждого класса. Не все 
комбинации АДТ и ФДС реализованы. 
    •   В библиотеке контейнерных классов имеются классы для создания 
абстрактных контейнеров, подобных массивам, мультимножествам, декам, словарям, 
очередям, множествам и стекам. 
    • В библиотеке шаблонов классов также имеются классы, реализующие 
внутренние структуры данных, используемые в абстрактных контейнерах: деревья 
двоичного поиска, двусвязные списки, хэш-таблицы, списки и векторы. 
    •   Косвенные контейнеры запоминают указатели на объекты, прочие 
контейнеры запоминают копии объектов. 
    •   С помощью итераторов можно "передвигаться" по объектам контейнера. 
Следует использовать итераторы для чтения данных контейнера или 
фундаментальной структуры данных. 
    •   Класс TShouldDelete задает статус принадлежности объектов косвенному 
контейнеру. Если контейнер владеет своими объектами, то он уничтожает их 
автоматически, когда они удаляются из него. Вы можете сделать так, чтобы 
объекты удалялись всегда, не удалялись никогда или удалялись только тогда, 
когда контейнер ими владеет. 
    •  Только косвенные контейнеры частично выводятся из класса TShouldDelete. 
Прочие контейнеры не выводятся из TShouldDelete. Вы не можете задать владение 
объектами для таких контейнеров. 


    Глава 16. Библиотека шаблонов классов Borland	437 
