Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Теллес М. - Borland C++ Builder. Библиотека программиста - 1998

.pdf
Скачиваний:
790
Добавлен:
13.08.2013
Размер:
4.35 Mб
Скачать

Borland C++ Builder (+CD). Библиотека программиста 111

Borland CBuilder включает в себя, как мы отмечали выше, полный компилятор стандарта ANSI C++. Одним из новых требований к любому компилятору C++, наложенных комитетом ANSI C++, является требование поставки компилятора с реализацией стандартной библиотеки. Это нужно для упрощения переноса так называемого переносимого кода (portable code), например, вспомогательных функций и функций работы с базами данных, на другой компилятор (другую платформу). Это изменение одно из самых важных в отношении движения C++ к настоящей переносимости между компиляторами, операционными системами и платформами.

Частью стандартной библиотеки C++ является библиотека стандартных шаблонов (Standard Template Library, STL), которая содержит общие классы, необходимые почти во всех разрабатываемых программах и системах. CBuilder включает полную реализацию библиотеки Rogue Wave Tools' STL, которая содержит классы для строк, контейнеров и других изящных вещей. В этой главе мы начинаем исследование STL, сначала вне графической среды CBuilder, а затем как дополнение к разработке приложений, основанных на формах.

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

STL — построенная на шаблонах библиотека вспомогательных классов. В ней нет ни графики, ни компонентов, ни чего-либо подобного. Библиотека служит для создания максимального удобства работы с любыми данными любым способом. Библиотека STL предоставляет переносимый класс string (строка), который позволяет забыть о проблемах переполнения строк в символьных массивах, выделения частей строки и других проблемах, связанных со строками. Класс string широко распространен в среде CBuilder, так что вам стоит подумать о его (или подобного ему класса AnsiString, который совместим с VCL) использовании вместо символьных массивов для представления строк в ваших приложениях.

Класс STL string и связанная с ним функциональность являются достаточными причинами, чтобы использовать его в ваших приложениях или как минимум изучить его, но класс string является только небольшой частичкой мощи STL. В дополнение к строкам, STL также предоставляет полную реализацию массивов (которые на жаргоне STL называются векторами, vectors), связанных списков (и одно-, и двунаправленных), очередей (queues), таблиц (maps, что-то типа словаря) и других стандартных структур данных.

Все классы STL представляют из себя шаблоны . Если вы некоторое время работали с C++, то вы вероятно знаете, что шаблоны это очень умные макросы, с которыми умет работать компилятор. Чтобы понять, что такое шаблоны, представьте себе следующую ситуацию. У вас есть класс массив (array), который хранит целые числа. Он знает, как выделить память под целое, сохранить его в памяти, получить его из памяти по индексу в массиве и найти заданное целое в массиве. Этот класс замечательно подходит для хранения ваших целых чисел и вы используете его некоторое время, но теперь вам нужно кроме целых хранить еще и вещественные числа с плавающей точкой. У вас есть два варианта. Во-первых, вы можете скопировать изначальный класс для массива целых и заменить в нем все ключевые слова int (целое) на double (вещественное с двойной точностью). Или вы можете разработать некую фантастическую систему, которая будет преобразовывать вещественные в целые, теряя при этом всю их точность (чем, я уверен, неслыханно обрадуете банковских служащих).

Вместо болезненного клонирования всего класса для нового типа данных, не проще ли было бы если бы можно было заставить компилятор подставлять ключевое слово double всюду, где в вашем классе встретится int? Ну, может быть, не всюду и вот в этом основное препятствие. Давайте посмотрим на описание класса (часть нашего мифического класса массив целых):

class IntegerArray

{

Borland C++ Builder (+CD). Библиотека программиста 112

int *pArrayOfInts; int nNumberOfInts; public:

IntegerArray (int nNumberOfInts); ~IntegerArray(void);

void Add (int nIntToAdd );

void Remove (int nIntToRemove ); int Find (int nIntToFind ); // Возврат:

// индекс найденного числа int GetAt (int nIndex );

void SetAt (int nIndex, int nIntegerToAdd);

};

Теперь, предположим, вам нужно хранить массив вещественных чисел с плавающей точкой (floating-point). Вы, вероятно, просто скопировали бы предыдущий заголовок и изменили его так, чтобы поддерживать вещественные числа (двойной точности, double). В этом случае у вас, вероятно, получился бы еще блок кода, типа такого:

class DoubleArray

{

double *pArrayOfDoubles; int nNumberOfDoubles; public:

DoubleArray (int nNumberOfDoubles); ~DoubleArray(void);

void Add (double dDoubleToAdd );

void Remove (double dDoubleToRemove );

int Find (double dDoubleToFind ); // Возврат: индекс

// найденного числа int GetAt (int nIndex );

void SetAt (int nIndex, double dDoubleToAdd); };

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

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

template <class _Type> class Array

{

Borland C++ Builder (+CD). Библиотека программиста 113

_Type *pArrayOfType; int nNuberOfType; public:

Array (int nNumberOfType); ~Array (void);

void Add (_Type tTypeObject ); void Remove (_Type tTypeObject );

int Find (_Type tTypeObject ); // Возвращает номер

// найденного объекта

_Type GetAt (int nIndex );

void SetAt (int nIndex, _Type tTypeObject); };

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

Array<int> intArray;

Для массива вещественных чисел это выглядело бы так:

Array<double> doubleArray;

И, наконец, массив некоторого произвольного типа (например, класса string) выглядел бы так:

Array<string> stringArray;

Что-то такого типа было бы замечательно, не правда ли? Вероятно, вам никогда бы не понадобилось писать другой класс Array. Ну, это в точности и есть функциональность, предоставляемая STL. Эта библиотека содержит шаблонные версии всех стандартных структур данных, нужных большинству приложений. Для использования массива целых, например, вам нужно использовать класс vector. Для создания таблички, сопоставляющей строке целое, используется класс map и так далее.

Зачем нужна библиотека стандартных шаблонов?

Весь этот сгенерированный код, конечно, является хорошим стимулом для использования STL, но есть более убедительные аргументы. Когда вы пишете вспомогательный код, например управление файлами, или базами данных, или просто классы для бизнес-приложения, вы частенько найдете, что вам нужно поддерживать разные платформы. Код должен работать в Windows 95 под C++Builder, также необходимо использовать объекты в управляющих элементах ActiveX для Internet и Unix-приложений. Если вы будете использовать собственные структуры данных, такие как в OWL или MFC, то вам очень не повезет, если вам придется переходить на другие компиляторы или операционные системы. Используя библиотеку STL, которая доступна в исходниках (бесплатно) от компании Hewlett Packard (откуда и пошла библиотека STL), вы можете быть уверены, что этот код будет работать на всех платформах, которые вам нужны. Процедура, которую вы написали для своего любимого Amiga 2000 (да, у меня действительно такой есть, и да, он все еще работает), будет работать в неизменном виде на Pentium II, который вы вчера купили. Если это недостаточно мощный стимул для использования STL, то примите во внимание следующее. Компания Borland была так поражена мощью и гибкостью STL, что она не предоставляет никаких других компонентов в VCL для обработки массивов, связанных списков и

Borland C++ Builder (+CD). Библиотека программиста 114

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

Классы STL: с самого начала

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

Сначала давайте посмотрим на класс string (строка), который предоставляет методы для обработки символов. Этот класс умный и гибкий массив символов. Вместо того, чтобы ограничивать вас фиксированным количеством символов, этот класс предоставляет возможность поддержки строк любой длины (вплоть до максимально возможного блока памяти, поддерживаемого операционной системой, с которой вы работаете). Класс string похож на тип string из Visual Basic и предоставляет похожую функциональность для манипулирования символьными данными, хранящимися в строке.

Таблица 5.1 показывает список основных (наиболее часто используемых) функций класса string вместе с описанием того, что делает функция.

Таблица 5.1 Используемые методы класса string

operator[]

Предоставляет доступ к конкретным символам в строке для чтения или записи

c_str()

Конвертирует строку в char* (указатель на символ) для использования в функциях, не умеющих работать со string

append

Добавляет символы к концу строки

operator=

Присваивание строке других строк, символьных массивов и даже чисел

insert

Позволяет вставлять символы или другие строки в переменную типа string

erase

Позволяет удалять один или более символов из данной строки в данной позиции

replace

Замещение одного или более символов в данной позиции

length(или size)

Borland C++ Builder (+CD). Библиотека программиста 115

Возвращает количество символов в строке

empty

Указывает, есть ли в строке символы

find

Позволяет найти первое вхождение символа или подстроки в данную строку

rfind

Как find, но поиск идет с конца строки назад

find_first_of

Позволяет найти первое вхождение символов из набора в строке

substr

Возвращает подстроку. Очень похоже на функцию Visual Basic substr

find_first_not_of

Находит первый символ в строке, не входящий в заданный набор

compare

Позволяет сравнивать строки (также поддерживаются операторы !=, <, >)

Класс string — один из самых нужных большинству приложений. Он убирает весь этот путаный код, который мы когда-либо писали для доступа к строкам символов и управления ими, а также

снимает проблему превышения длины строки и возможного стирания содержимого памяти за ее пределами.

Давайте посмотрим на очень простой пример<Код программ из этой главы находится на прилагаемом компакт-диске в каталоге Chapter5. — Примеч. перев.> использования класса string:

#include <string> #include <stdio.h> #include <stdlib.h> #include <string> using namespace std;

int main(void)

{

//Присвоить строку символов объекту типа string string s = "Hello world";

//Получить первое слово в строке

int nWordEnd = s.find(' ');

Borland C++ Builder (+CD). Библиотека программиста 116

string sub_string = s.substr(0,nWordEnd);

// Вывести результаты printf("Исходная строка: %s\n", s.c_str);

printf("Подстрока из первого слова: %s\n", sub_string.c_str());

return 0;

}

Этот пример несколько важных случаев использования объекта string. Во-первых, вы видите, что мы можете прямо присваивать строку символов объекту string. Это упрощает использование строк с данными в нашем приложении.

Следующее использование метода find в объекте типа string. Метод find (и его брат, метод rfind) найдет первое вхождение символа в строке. Возвращает метод позицию (начиная с 0) найденного символа, или —1, если подходящего символа в строке не нашлось.

Метод substr возвращает копию части строки, начинающейся с позиции, заданной первым параметром метода, и длиной, заданной во втором параметре метода. Если опустить второй аргумент, то возвратится строка символов начиная с данной позиции до конца исходной строки, что эквивалентно функции языка BASIC Right$().

И, наконец, для вывода строки на экран мы используем нашу старую знакомую функцию printf, выводя строку как символьный массив (спецификация %s в строке формата printf) через использование метода c_str() для преобразования объекта string в символьный массив. Я предпочитаю использовать printf в приложениях консольного типа (например DOS-приложениях). Если вы предпочитаете использовать более современные потоки, таки как cout, то вы можете переписать предыдущие строки так:

cout << "Исходная строка: " << s << endl;

cout << "Подстрока из первого слова: "

<< sub_string << endl;

Заметьте, что в этом случае вам не нужно использовать метод c_str, так как у класса string есть переопределенные методы для потоковых операторов << и >>.

Работа с векторами (массивами)

В библиотеке STL массивы переменной длины представлены классом vector, который предоставляет доступ к элементам любого типа (правда, только к одному типу в одном объекте vector) последовательно или случайным доступом. Такие объекты используются в вашем приложении всюду, где бы вы обычно писали объявления массива элементов этого типа. Например, если бы вам нужно было хранить 100 целых чисел, то в вашей программе могло бы быть такое выражение:

int nItems [100];

Однако, это выражение склонно к ошибкам. Что, если позднее вы поймете, что вам на самом деле нужно хранить 200 элементов? Вы можете использовать выделенный блок памяти, например так:

Borland C++ Builder (+CD). Библиотека программиста 117

int *pnItems = new int [100];

Тогда, если вам потребуется больший массив, вы можете сделать что-нибудь подобное:

delete pnItems;

pnItems = new int [nSomeNewSize] ;

Проблема здесь в том, что когда вы удаляете все эти элементы, то вы теряете значения, которые они содержали. Как альтернативу, вы можете вернуться к старым функциям malloc и free в стиле языка C:

int *pnItems = (int *)malloc (100 * sizeof(int) );

Тогда перераспределение элементов возможно с помощью функции realloc:

int *pnItems = realloc (pnItems, nSomeNewSize * sizeof(int) );

В этом подходе тоже есть проблемы. Во-первых, вам надо не забыть включить размер целого при выделении памяти. Во-вторых, нет способа определить, были ли освобождены элементы. И наконец, удобнее работать с new и delete, чем с malloc и free. Все эти причины достаточно серьезны, так что STL содержит полную шаблонную реализацию массива в классе vector. В предыдущем примере мы бы заменили описание массива следующим выражением:

vector<int> nItems;

Это выделяет переменный массив целых. Для добавления элементов в массив используйте метод insert:

nItems.insert (nItems.end(), 12 )

Это выражение добавит значение 12 в конец массива. Если вы уже установили значение, то вы можете обращаться к нему для чтения или записи, используя оператор []. Например, представьте, что у нас были следующие выражения, устанавливающие значения элементов массива:

nItems.insert (nItems.end(), 1 ); nItems.insert (nItems.end(), 2 ); nItems.insert (nItems.end(), 3 ); nItems.insert (nItems.end(), 4 );

Это устанавливает четыре элемента массива в значения от 1 до 4. Тогда мы можем изменить третий элемент массива, используя оператор [] и индекс элемента (начиная с 0):

nItems[2] = 5;

count << nItems[2];

Два предыдущих выражения присвоят третьему элементу массива значение 5 и затем выведут (в стандартный выходной поток) значение этого элемента. Этот пример напечатает значение 5.

В табл. 5.2 приведены наиболее важные методы класса vector вместе с объяснением, что эти методы делают.

Borland C++ Builder (+CD). Библиотека программиста 118

Таблица 5.2 Важные методы класса vector

Insert

Добавляет данные в массив

Size

Возвращает текущее количество элементов в массиве operator[]

Дает доступ к элементу массива по его индексу для чтения или записи operator=(vector)

Копирует один массив в другой at

То же, что и operator[] begin

Возвращает начало массива end

Возвращает конец массива capacity

Возвращает выделенный размер массива. Может отличаться от size, если есть резерв reserve

Выделяет память под фиксированное число элементов, которое может быть изменено во время

выполнения empty

Указывает, содержит (FALSE) или нет (TRUE) массив элементы erase

Удаляет элементы из массива count

Вспомогательная функция, которая считает количество элементов данного типа в массиве max_size

Возвращает максимально возможный размер для массива. Обычно это количество свободной памяти массива. Обычно это количество свободной памяти

swap

Позволяет двум массивам быстро и эффективно обменяться данными resize

Изменяет размер массива на новое заданное число элементов, с необязательным параметром для

инициализации начальных значений

Пример программы замены строк

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

Создайте консольное приложение (console application) в CBuilder. Для этого выберите команду меню File|New и выберите Console Application из первой страницы предложенных вариантов. CBuilder создаст новый файл проекта и основной файл программы, называемый project.cpp. Эта программа будет выполняться только из окна MS-DOS или в консольном режиме, в Windows 95 или NT, соответственно. Добавьте следующий код в исходный файл project.cpp:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <vector> using namaspace std;

Borland C++ Builder (+CD). Библиотека программиста 119

class StringReplacement

{

private:

string strSearchText; string strReplcaeText;

public:

StringReplacement(void)

{

}

StringReplacement (string search, string replace )

{

strSearchText = search; strReplaceText = replace;

}

void DoReplace (string &text)

{

int i = 0;

string strComplete = ""; while (i < text.size() )

{

if (!strncmp(text.c_str()+i, strSearchText/c_str(), strSearchText.size()) )

{

i += strSearchText.size(); strComplete += strReplaceText;

}

else

{

strComplete += text[i]; i++;

}

}

text = strComplete;

}

};

vector< StringReplacement, allocator<StringReplacement> > strList;

int main(int argc, char **argv)

{

if (argc < 2)

{

printf("Использование: repl <input-file>"\ "<definitions-file>\n");

return -1;

}

FILE *fp = fopen(argv[1], "r");

Borland C++ Builder (+CD). Библиотека программиста 120

if (fp == NULL )

{

printf("Не могу открыть файл %s\n", argv[1]); return -1;

}

strList.insert (strList.end(), StringRepacement("$$Автор$$", "Matt Telles") );

strList.insert (strList.end(), StringRepacement("$$Программа$$", "Matt's Test") );

char szBuffer [256] ; while (!feof(fp) )

{

if (fgets(szBuffer, 255, fp) == NULL ) break;

string s = szBuffer;

for (int i=0; i<strList.size(), ++i ) strList[i].DoReplace (s ); printf("%s", s.c_str());

}

fclose(fp); return 0;

}

В этой демонстрационной программе есть несколько важных моментов. Программа, кстати, будет искать и заменять все вхождения данного набора строк в файле другим набором строк. Здесь мы используем vector для хранения объектов, которые содержат строки. Вы можете видеть, как мы проходимся по массиву, используя методы класса vector size и operator[]. В дополнение, вы можете видеть, как vector может хранить разные данные, даже если они и не происходят от общего предка. Если вы знакомы с другими системами, где работа с разными данными требует, чтобы они наследовали от общего базового класса, то вы знаете, насколько это приятно.

Для запуска программы замены откройте окно MS-DOS и запустите программу project1.exe. Вам придется создать два файла, первый из них файл замен, содержащий элементы в виде

oldvalue=newvalue

где oldvalue — значение, которое вы хотите заменить, а newvalue — значение, которым вы хотите заменить старое. Например, для замены всех вхождений строки $$Author$$ на имя Matt Telles вы можете написать:

$$Author$$=Matt Telles

<$FНетрудно заметить, что программа, приведенная в листинге, не читает данные из второго файла, заданного в параметрах командной строки. То есть замена происходит только явно указанных в программе строк. Код на компакт-диске полностью идентичен листингу. Видимо, автор оставил доработку этой программы читателю в качестве упражнения. — Прим. пер.>Эта программа хороша как обработчик шаблона для документов и исходных файлов, которым нужен заголовок, и в других случаях. Код этой программы может быть вырезан и помещен во вспомогательную функцию, которую вы могли бы использовать в своих приложениях для

Соседние файлы в предмете Программирование на C++