Программирование — страница 51 из 57

“По возможности, вся сложность должна быть скрыта

от постороннего взгляда”.

Дэвид Дж. Уилер (David J. Wheeler)


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

Б.1. Обзор

Это приложение является справочником и не предназначено для последовательного чтения от начала до конца, как обычная глава. В нем более или менее систематично описываются основные элементы стандартной библиотеки языка С++. Впрочем, этот справочник не полон; он представляет собой краткий обзор с немногочисленными примерами, иллюстрирующими ключевые возможности. За более подробным объяснением читателям часто придется обращаться к соответствующим главам данной книги. Кроме того, следует подчеркнуть, что мы не стремились к точности стандарта и не придерживались его терминологии. Более подробную информацию читатели найдут в книге Stroustrup, The C++ Programming Language[13]. Полным определением языка является стандарт ISO C++, но этот документ не предназначен для новичков и не подходит для первоначального изучения языка. Не забудьте также об использовании документации, доступной в Интернете.

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

Здесь вы можете найти готовые средства, вместо того, чтобы изобретать их самостоятельно. Все, что есть в стандартной библиотеке (и особенно все, что перечислено в приложении), оказалось весьма полезным для многих людей. Стандартные возможности библиотеки практически всегда разработаны, реализованы и документированы намного лучше, чем это можете сделать вы, находясь в цейтноте. Кроме того, их переносимость из одной системы в другую обеспечена намного лучше. Итак, по возможности всегда следует отдавать предпочтение стандартным библиотечным средства, а не “самогону” (“home brew”). В таком случае ваш код будет намного понятнее.

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

Б.1.1. Заголовочные файлы

Интерфейсы средств из стандартной библиотеки определены в заголовках. Некоторые из заголовков, упомянутых в следующей таблице, не входят в стандарт языка C++, принятый ISO в 1998 году. Тем не менее они станут частью следующего стандарта и в настоящее время являются широкодоступными. Такие заголовки обозначены “C++0x”. Для их использования может потребоваться отдельная инсталляция и/или пространство имен, отличающееся от

std
(например,
tr1
или
boost
). В этом разделе вы узнаете, какие средства могут стать доступными в вашей программе, а также можете угадать, где они определены и описаны.



Для каждого заголовка стандартной библиотеки языка С существует аналогичный заголовочный файл без первой буквы c в имени и с расширением

.h
, например заголовочный файл
для заголовка
. Версии заголовков с окончанием
.h
определяют глобальные имена, а не имена в пространстве имен
std
.

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

Б.1.2. Пространство имен std

Средства стандартной библиотеки определены в пространстве имен

std
, поэтому, чтобы использовать их, необходимо указать их явную квалификацию, выполнить объявление
using
или директиву
using
.


std::string s;       // явная квалификация

using std::vector;   // объявление using

vectorv(7);

using namespace std; // директива using

map m;


В этой книге для доступа к пространству имен

std
мы использовали директиву
using
. Будьте осторожны с директивами
using
(см. раздел A.15).

Б.1.3. Стиль описания

Полное описание даже простой операции из стандартной библиотеки, например конструктора или алгоритма, может занять несколько страниц. По этой причине мы используем чрезвычайно лаконичный стиль представления. Рассмотрим пример.



Мы старались выбирать мнемонические идентификаторы, поэтому символы

b,e
будут обозначать итераторы, задающие начало и конец диапазона;
p
— указатель или итератор;
x
— некое значение, полностью зависящее от контекста. В этой системе обозначений отличить функцию, не возвращающую никакого результата, от функции, возвращающей переменную булевого типа, без дополнительных комментариев невозможно, поэтому, если не приложить дополнительных усилий, их можно перепутать. Для операций, возвращающих переменную типа
bool
, в объяснении обычно стоит знак вопроса.

Если алгоритмы следуют общепринятым соглашениям, возвращая конец входной последовательности для обозначения событий “отказ”, “не найден” и т.п. (раздел Б.3.1), то мы это явно не указываем.

Б.2. Обработка ошибок

Стандартная библиотека состоит из компонентов, которые разрабатывались в течение сорока лет. По этой причине ее стиль и принципы обработки ошибок являются несогласованными.

• Библиотека в стиле языка С состоит из функций, многие из которых для индикации ошибок устанавливают флаг

errno
(см. раздел 24.8).

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

• Библиотека потоков ввода-вывода для сообщений об ошибках использует состояние каждого потока и может (если пользователь этого потребует) генерировать исключения (см. разделы 10.6 и Б.7.2).

• Некоторые компоненты стандартной библиотеки, такие как

vector
,
string
и
bitset
, при обнаружении ошибок генерируют исключения.


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

Б.2.1. Исключения

Некоторые средства стандартной библиотеки сообщают об ошибках, генерируя исключения.



Эти исключения могут возникнуть в любом коде, явно или неявно использующем указанные средства библиотеки. Если вы уверены, что все использованные средства были использованы правильно и поэтому не могли сгенерировать исключение, то целесообразно всегда в каком-то месте (например, в функции

main()
) перехватывать объекты одного из корневых классов иерархии исключений из стандартной библиотеки (например,
exception
).

Мы настоятельно рекомендуем не генерировать исключения встроенных типов, например числа типа

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


class exception {

public:

  exception();

  exception(const exception&);

  exception& operator=(const exception&);

  virtual ~exception();

  virtual const char* what() const;

};


Функцию

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

Приведенная ниже иерархия стандартных исключений может помочь вам классифицировать исключения.



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


struct My_error:runtime_error {

  My_error(int x):interesting_value(x) { }

  int interesting_value;

  const char* what() const { return "My_error"; }

};

Б.3. Итераторы

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



Б.3.1. Модель итераторов

Итератор — это аналог указателя, в котором реализованы операции косвенного доступа (например, оператор

*
для разыменования) и перехода к новому элементу (например, оператор
++
для перехода к следующему элементу). Последовательность элементов определяется парой итераторов, задающих полуоткрытый диапазон
[begin:end]
.



Иначе говоря, итератор

begin
указывает на первый элемент последовательности, а итератор
end
— на элемент, следующий за последним элементом последовательности. Никогда не считывайте и не записывайте значение
*end
. Для пустой последовательности всегда выполняется условие
begin==end
. Другими словами, для любого итератора p последовательность
[p:p]
является пустой.

Для того чтобы считать последовательность, алгоритм обычно получает пару итераторов (

b, e
) и перемещается по элементам с помощью оператора
++
, пока не достигнет конца.


while (b!=e) { // используйте !=, а не <

               // какие-то операции

  ++b;         // переходим к последнему элементу

}


Алгоритмы, выполняющие поиск элемента в последовательности, в случае неудачи обычно возвращают итератор, установленный на конец последовательности. Рассмотрим пример.


p = find(v.begin(),v.end(),x); // ищем x в последовательности v

if (p!=v.end()) {

      // x найден в ячейке p

}

else {

      // x не найден в диапазоне [v.begin():v.end())

}


См. раздел 20.3.

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


template void f(Iter p, int n)

{

  while (n>0) *p++ = ––n;

  vector v(10);

  f(v.begin(),v.size()); // OK

  f(v.begin(),1000);     // большая проблема


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

f()
, но этот код нельзя считать переносимым; многие реализации эту проверку не проводят.

Перечислим операции над итераторами.



Обратите внимание на то, что не каждый вид итераторов (раздел Б.3.2) поддерживает все операции над итераторами.

Б.3.2. Категории итераторов

В стандартной библиотеке предусмотрены пять видов итераторов.



С логической точки зрения итераторы образуют иерархию (см. раздел 20.10).



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

iterator_traits
в профессиональном справочнике.

Каждый контейнер имеет собственные итераторы конкретной категории:

vector
— итераторы произвольного доступа;

list
— двунаправленные итераторы;

deque
— итераторы произвольного доступа;

bitset
— итераторов нет;

set
— двунаправленные итераторы;

multiset
— двунаправленные итераторы;

map
— двунаправленные итераторы;

multimap
— двунаправленные итераторы;

unordered_set
— однонаправленные итераторы;

unordered_multiset
— однонаправленные итераторы;

unordered_map
— однонаправленные итераторы;

unordered_multimap
— однонаправленные итераторы.

Б.4. Контейнеры

Контейнер содержит последовательность элементов. Элементы этой последовательности имеют тип

value_type
. Наиболее полезными контейнерами являются следующие.



Эти контейнеры определены в классах

,
и др. (см. раздел Б.1.1). Последовательные контейнеры занимают непрерывную область памяти или представляют собой связанные списки, содержащие элементы соответствующего типа
value_type
(выше мы обозначали его буквой
T
). Ассоциативные контейнеры представляют собой связанные структуры (деревья) с узлами соответствующего типа value_type (выше мы обозначали его как
pair(K,V)
). Последовательность элементов в контейнерах
set
,
map
или
multimap
упорядочена по ключу (K). Последовательность в контейнерах, название которых начинается со слова
unordered
, не имеет гарантированного порядка. Контейнер
multimap
отличается от контейнера
map
тем, что в первом случае значение ключа может повторяться много раз. Адаптеры контейнеров — это контейнеры со специальными операциями, созданные из других контейнеров.

Если сомневаетесь, используйте класс

vector
. Если у вас нет весомой причины использовать другой контейнер, используйте класс
vector
.

Для выделения и освобождения памяти (см. раздел 19.3.6) контейнеры используют распределители памяти. Мы не описываем их здесь; при необходимости читатели найдут информацию о них в профессиональных справочниках. По умолчанию распределитель памяти использует операторы

new
и
delete
, для того чтобы занять или освободить память, необходимую для элементов контейнера.

Там, где это целесообразно, операция доступа реализована в двух вариантах: один — для константных объектов, другой — для неконстантных (см. раздел 18.4).

В этом разделе перечислены общие и “почти общие” члены стандартных контейнеров (более подробную информацию см. в главе 20). Члены, характерные для какого-то конкретного контейнера, такие как функция

splice()
из класса
list
, не указаны; их описание можно найти в профессиональных справочниках.

Некоторые типы данных обеспечивают большинство операций, требующихся от стандартного контейнера, но все-таки не все. Иногда такие типы называют “почти контейнерами”. Перечислим наиболее интересные из них.



Б.4.1. Обзор

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



Б.4.2. Типы членов

Контейнер определяет множество типов его членов.



Б.4.3. Конструкторы, деструкторы и присваивания

Контейнеры имеют много разнообразных конструкторов и операторов присваивания. Перечислим конструкторы, деструкторы и операторы присваивания для контейнера C (например, типа

vector
или
map
).



Для некоторых контейнеров и типов элементов конструктор или операция копирования может генерировать исключения.

Б.4.4. Итераторы

Контейнер можно интерпретировать как последовательность, порядок следования элементов в которой определен либо итератором контейнера, либо является обратным к нему. Для ассоциативного контейнера порядок определяется критерием сравнения (по умолчанию оператором

<
).



Б.4.5. Доступ к элементам

К некоторым элементам можно обратиться непосредственно.



Некоторые реализации — особенно их тестовые версии — всегда выполняют проверку диапазонов, но рассчитывать на корректность или наличие такой проверки на разных компьютерах нельзя. Если этот вопрос важен, просмотрите документацию.

Б.4.6. Операции над стеком и двусторонней очередью

Стандартные контейнеры

vector
и
deque
обеспечивают эффективные операции над концами (
back
) последовательности элементов. Кроме того, контейнеры
list
и
deque
обеспечивают аналогичные операции над началом (
front
) своей последовательности.



Обратите внимание на то, что функции

push_front()
и
push_back()
копируют элемент в контейнер. Это значит, что размер контейнера увеличивается (на единицу). Если копирующий конструктор элемента может генерировать исключения, то вставка может завершиться отказом.

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

front()
и
back()
(см. раздел Б.4.5). Мы не ставили себе задачу перечислить все ограничения; попробуйте догадаться об остальных (как правило, компиляторы сообщают пользователям об их неверных догадках) или обратитесь к более подробной документации.

Б.4.7. Операции над списком

Ниже приведены операции над списком.



Результат

q
функции
insert()
ссылается на последний вставленный элемент. Результат
q
функции
erase()
ссылается на элемент, следующий за последним удаленным элементом. 

Б.4.8. Размер и емкость

Размер — это количество элементов в контейнере; емкость — это количество элементов, которое контейнер может содержать до того, как потребуется дополнительно увеличить память



Изменяя размер или емкость, можно переместить элементы в новое место. Из этого следует, что итераторы (а также указатели и ссылки) на элементы могут стать некорректными (т.е. относиться к старым адресам).

Б.4.9. Другие операции

Контейнеры можно копировать (см. раздел Б.4.3), сравнивать и обменивать.



Если сравнение контейнеров производится с помощью соответствующего оператора (например,

<
), то их элементы сравниваются с помощью эквивалентного оператора для сравнения элементов (например,
<
). 

Б.4.10. Операции над ассоциативными контейнерами

Ассоциативные контейнеры обеспечивают поиск на основе ключей.



Упорядоченные ассоциативные контейнеры (

map
,
set
и др.) имеют необязательный шаблонный аргумент, указывающий тип предиката сравнения, например,
set
использует предикат
C
для сравнения значений типа
K
.

Первый итератор пары, возвращенной функцией

equal_range
, равен
lower_bound
, а второй —
upper_bound
. Вы можете вывести на печать значения всех элементов, имеющих ключ "
Marian
" в контейнере
multimap
, написав следующий код:


string k = "Marian";

typedef multimap::iterator MI;

pair pp = m.equal_range(k);

if (pp.first!=pp.second)

  cout << "elements with value ' " << k << " ':\n";

else

  cout << "no element with value ' " << k << " '\n";

for (MI p = pp.first; p!=pp.second; ++p) cout << p–>second << '\n';


В качестве альтернативы можно выполнить следующую эквивалентную инструкцию:


pair pp = make_pair(m.lower_bound(k),m.upper_bound(k));


Однако эта инструкция выполняется вдвое дольше. Алгоритмы

equal_range
,
lower_bound
и
upper_bound
можно выполнять также для упорядоченных последовательностей (раздел Б.5.4). Определение класса
pair
приведено в разделе Б.6.3. 

Б.5. Алгоритмы

В заголовке

определено около 60 алгоритмов. Все они относятся к последовательностям, определенным парами итераторов (для ввода) или одним итератором (для вывода).

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

[b:e]
, а вторая — только одним итератором
b2
, который считается началом последовательности, содержащей элементы, количество которых достаточно для выполнения алгоритма, например, столько же, сколько элементов в первой последовательности:
[b2:b2+(e–b)]
.

Некоторые алгоритмы, такие как

sort
, используют итераторы произвольного доступа, а многие другие, такие как
find
, только считывают элементы с помощью однонаправленного итератора.

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

Б.5.1. Немодицифирующие алгоритмы для последовательностей

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



Предотвратить модификацию элементов операцией, передаваемой алгоритму

for_each
, невозможно; это считается приемлемым. Передача операции, изменяющей проверяемые ею элементы, другим алгоритмам (например, count или
==
) недопустима.

Рассмотрим пример правильного использования алгоритма.


bool odd(int x) { return x&1; }

int n_even(const vector& v) // подсчитывает количество четных

                                 // чисел в v

{

  return v.size()–count_if(v.begin(),v.end(),odd);

}

Б.5.2. Алгоритмы, модифицирующие последовательности

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



Алгоритм

shuffle
перетасовывает последовательность точно так же, как перетасовывается колода карт; иначе говоря, после перетасовки элементы следуют в случайном порядке, причем смысл слова “случайно” определяется распределением, порожденным датчиком случайных чисел.

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

remove
, не может уменьшить длину входной последовательности, удалив (стерев) ее элементы; вместо этого он передвигает эти элементы к началу последовательности.


typedef vector::iterator VII;

void print_digits(const string& s, VII b, VII e)

{

  cout << s;

  while (b!=e) { cout << *b; ++b; }

  cout << '\n';

}


void ff()

{

  int a[] = { 1,1,1,2,2,3,4,4,4,3,3,3,5,5,5,5,1,1,1 };

  vector v(a,a+sizeof(a)/sizeof(int));

  print_digits("all: ",v.begin(), v.end());

  vector::iterator pp = unique(v.begin(),v.end());

  print_digits("head: ",v.begin(),pp);

  print_digits("tail: ",pp,v.end());

  pp=remove(v.begin(),pp,4);

  print_digits("head: ",v.begin(),pp);

  print_digits("tail: ",pp,v.end());

}


Результат приведен ниже.


all: 1112234443335555111

head: 1234351

tail: 443335555111

head: 123351

tail: 1443335555111

Б.5.3. Вспомогательные алгоритмы

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



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

uninitialized_fill
и
uninitialized_copy
, должны иметь встроенный тип или быть неинициализированными.

Б.5.4. Сортировка и поиск

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

<
, а эквивалентность пар значений
a
и
b
определяется условием
!(a
, а не оператором
==
.



Рассмотрим следующий пример:


vector v;

list lst;

v.push_back(3); v.push_back(1);

v.push_back(4); v.push_back(2);

lst.push_back(0.5); lst.push_back(1.5);

lst.push_back(2); lst.push_back(2.5); // список lst упорядочен

sort(v.begin(),v.end());              // сортировка вектора v

vector v2;

merge(v.begin(),v.end(),lst.begin(),lst.end(),back_inserter(v2));

for (int i = 0; i


Алгоритмы вставки описаны в разделе Б.6.1. В итоге получается следующий результат:


0.5, 1, 1.5, 2, 2, 2.5, 3, 4,


Алгоритмы

equal_range
,
lower_bound
и
upper_bound
используются точно так же, как и их эквиваленты для ассоциативных контейнеров (раздел Б.4.10).

Б.5.5. Алгоритмы для множеств

Эти алгоритмы интерпретируют последовательность как множество элементов и выполняют основные операции над множествами. Входные и выходные последовательности предполагаются упорядоченными.



Б.5.6. Кучи

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



Куча позволяет быстро добавлять элементы и обеспечивает быстрый доступ к элементу с наибольшим значением. В основном кучи используются при реализации очередей с приоритетами.

Б.5.7. Перестановки

Перестановки используются для генерирования комбинаций элементов последовательности. Например, перестановками последовательности

abc
являются последовательности
abc
,
acb
,
bac
,
bca
,
cab
и
cba
.



Если последовательность

[b:e]
уже содержит последнюю перестановку (в данном примере это перестановка
cba
), то алгоритм
next_permutation
возвращает значение
x
, равное
false
; в таком случае алгоритм создает первую перестановку (в данном примере это перестановка
abc
). Если последовательность
[b:e]
уже содержит первую перестановку (в данном примере это перестановка
abc
), то алгоритм
prev_permutation
возвращает значение
x
, равное
false
; в таком случае алгоритм создает последнюю перестановку (в данном примере это перестановка
cba
).

Б.5.8. Функции min и max

Сравнение значений полезно во многих случаях.



Б.6. Утилиты библиотеки STL

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

Б.6.1. Вставки

Запись результатов в контейнер с помощью итератора подразумевает, что элементы, на которые указывает итератор, можно перезаписать. Это открывает возможность для переполнения и последующего повреждения памяти. Рассмотрим следующий пример:


void f(vector& vi)

{

  fill_n(vi.begin(),200,7); // присваиваем 7 элементам

                              // vi[0]..[199]

}


Если вектор

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



Для правильной работы алгоритма

inserter(c,p)
необходимо, чтобы итератор p был корректным итератором для контейнера
c
. Естественно, каждый раз при записи очередного элемента с помощью итератора вставки контейнер увеличивается на один элемент. При записи алгоритм вставки добавляет новый элемент в последовательность с помощью функции
push_back(x)
,
c.push_front()
или
insert()
, а не перезаписывает существующий элемент. Рассмотрим следующий пример:


void g(vector& vi)

{

  fill_n(back_inserter(vi),200,7); // добавляет 200 семерок

                                   // в конец vi

}

Б.6.2. Объекты-функции

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

bool
) и арифметических операций. Несколько самых общих объектов-функций описано в заголовке
стандартной библиотеки.



Рассмотрим следующий пример:


vector v;

// ...

sort(v.begin(),v.end(),greater()); // сортировка v в убывающем

                                        // порядке

Обратите внимание на то, что предикаты

logical_and
и
logical_or
всегда вычисляют оба свои аргумента (в то время как операторы
&&
и
||
— нет).



Б.6.3. Класс pair

В заголовке

стандартная библиотека содержит несколько вспомогательных компонентов, включая класс
pair
.


template 

  struct pair {

    typedef T1 first_type;

    typedef T2 second_type;

    T1 first;

    T2 second;

    pair();      // конструктор по умолчанию

    pair(const T1& x,const T2& y);

                 // копирующие операции:

    template pair(const pair& p
);

};


template 

  pair make_pair(T1 x, T2 y) { return pair(x,y); }


Функция

make_pair()
упрощает использование пар. Например, рассмотрим схему функции, возвращающей значение и индикатор ошибки.


pair my_fct(double d)

{

  errno = 0; // очищаем индикатор ошибок в стиле языка C

             // выполняем много вычислений, связанных с переменной d,

             // и вычисляем x

  error_indicator ee = errno;

  errno = 0; // очищаем индикатор ошибок в стиле языка C

  return make_pair(x,ee);

}


Этот пример является полезной идиомой. Его можно использовать следующим образом:


pair res = my_fct(123.456);

if (res.second==0) {

  // используем res.first

}

else {

  // Ой: ошибка

}

Б.7. Потоки ввода-вывода

Библиотека потоков ввода-вывода содержит средства форматированного и неформатированного буферизованного ввода-вывода текста и числовых значений.

Определения потоков ввода-вывода находятся в заголовках

,
и т.п. (см. раздел Б.1.1).

Объект класса

ostream
преобразовывает объекты, имеющие тип, в поток символов (байтов).



Объект класса

istream
преобразовывает поток символов (байтов) в объекты, имеющие тип.



Объект класса

iostream
— это поток, который может действовать и как объект класса
istream
, и как объект класса
ostream
. Буфера, изображенные на диаграмме, являются потоковыми буферами (
streambuf
). Если читателям потребуется перейти от потоков класса
iostream
к новым видам устройств, файлов или памяти, они смогут найти их описание в профессиональных учебниках.

Существуют три стандартных потока.



Б.7.1. Иерархия потоков ввода-вывода

Поток

istream
можно связать с устройством ввода (например, клавиатурой), файлом или объектом класса
string
. Аналогично поток
ostream
можно связать с устройством вывода (например, текстовым окном), файлом или объектом класса
string
. Потоки ввода-вывода образуют иерархию классов.



Поток можно открыть либо с помощью конструктора, либо вызова функции

open()
.



Для файловых потоков имя файлов представляет собой строку в стиле языка С.

Открыть файл можно в одном из режимов, приведенных ниже.



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

good()
. Рассмотрим пример.


void my_code(ostream& os); // функция my_code может использовать

                           // любой поток вывода

ostringstream os;          // буква "o" означает "для вывода"

ofstream of("my_file");

if (!of) error("невозможно открыть 'my_file' для записи");

my_code(os); // используется объект класса string

my_code(of); // используется файл


См. раздел 11.3.

Б.7.2. Обработка ошибок

Поток

iostream
может пребывать в одном из четырех состояний.



Используя функцию

s.exceptions()
, программист может потребовать, чтобы поток
iostream
сгенерировал исключение, если из состояния
good()
он перешел в другое состояние (см. раздел 10.6).

Любая операция, в результате которой поток не находится в состоянии

good()
, не имеет никакого эффекта; такая ситуация называется “no op”.

Объект класса

iostream
можно использовать как условие. В данном случае условие является истинным (успех), если поток
iostream
находится в состоянии
good()
. Это обстоятельство стало основой для распространенной идиомы, предназначенной для считывания потока значений.


X x; // "буфер ввода" для хранения одного значения типа X

while (cin>>x) {

  // какие-то действия с объектом x

}

// мы окажемся в этой точке, если оператор >> не сможет прочитать

// очередной объект класса X из потока cin

Б.7.3. Операции ввода

Почти все операции ввода описаны в заголовке

, за исключением операций ввода в объект класса
string
; эти операции описаны в заголовке
:



Если не указано иное, операция ввода возвращает ссылку на объект класса

istream
, поэтому можно создавать цепочки таких операций, например
cin>>x>>y
;.



Функции

get() 
и
getline()
помещают после символов, записанных в ячейки
p[0]
и т.д., число
0
(если символы были введены); функция
getline()
удаляет признак конца ввода (
t
из потока ввода, если он обнаружен, а функция
get()
этого не делает. Функция
read(p,n)
не записывает число
0
в массив после считанных символов. Очевидно, что операторы форматированного ввода проще в использовании и менее уязвимы для ошибок, чем операции неформатированного ввода.

Б.7.4. Операции вывода

Почти все операции вывода описаны в заголовке

, за исключением операции записи в объекты класса
string
; такие операции описаны в заголовке
.



Если не указано иное, операции вставки в поток

ostream
возвращают ссылку на его объекты, поэтому можно создавать цепочки операций вывода, например
cout << x<
;.

Б.7.5. Форматирование

Формат потока ввода-вывода управляется комбинацией типа объекта, состояния потока, информацией о локализации (см. раздел

) и явными операциями. Большая часть информации об этом изложена в главах 10-11. Здесь мы просто перечислим стандартные манипуляторы (операции, модифицирующие поток), поскольку они обеспечивают наиболее простой способ изменения формата.

Вопросы локализации выходят за рамки рассмотрения настоящей книги.

Б.7.6. Стандартные манипуляторы

В стандартной библиотеке предусмотрены манипуляторы, соответствующие разнообразным изменениям формата. Стандартные манипуляторы определены в заголовках

,
,
,
и
(для манипуляторов, получающих аргументы).



Каждая из этих операций возвращает ссылку на свой первый операнд потока

s
.

Рассмотрим пример.


cout << 1234 << ',' << hex << 1234 << ',' << oct << 1234 << endl;


Этот код выводит на экран следующую строку:


1234,4d2,2322


В свою очередь, код


cout << '(' << setw(4) << setfill('#') << 12 << ") (" << 12 << ")\n";


выводит на экран такую строку:


(##12) (12)


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


b.setf(ios_base::fmtflags(0),ios_base::floatfield)


См. главу 11.

Б.8. Манипуляции строками

В стандартной библиотеке предусмотрены операции классификации символов в заголовке

, строки с соответствующими операциями в заголовке
, регулярные выражения в заголовке
(C++0x) и поддержка С-строк в заголовке
.

Б.8.1. Классификация символов

Символы из основного набора могут быть классифицированы так, как показано ниже.



Кроме того, в стандартной библиотеке описаны две полезные функции для изменения регистра символа.



Расширенные наборы символов, такие как Unicode, также поддерживаются стандартной библиотекой, но эта тема выходит за рамки рассмотрения настоящей книги.

Б.8.2. Строки

Класс

string
из стандартной библиотеки представляет собой специализацию общего шаблонного класса
basic_string
для символьного типа
char
; иначе говоря, объект
string
— это последовательность переменных типа
char
.



Б.8.3. Сравнение регулярных выражений

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

.

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

regex_search()
.

Сопоставление (matching) регулярного выражения со строкой (известного размера) — обеспечивается функцией

regex_match()
.

Замена соответствий (replacement of matches) — обеспечивается функцией

regex_replace()
; в данной книге не описывается; см. профессиональные учебники или справочники.


Результатом работы функций

regex_search()
и
regex_match()
является коллекция соответствий, как правило, представленных в виде объекта класса
smatch
.


regex row("^[\\w ]+(\\d+)(\\d+)(\\d+)$"); // строка данных

while (getline(in,line)) { // проверка строки данных

  smatch matches;

  if (!regex_match(line, matches, row))

  error("bad line", lineno);


  // проверка строки:

  int field1 = from_string(matches[1]);

  int field2 = from_string(matches[2]);

  int field3 = from_string(matches[3]);

  // ...

}


Синтаксис регулярных выражений основан на символах, имеющих особый смысл (см. главу 23).



Некоторые классы символов поддерживаются аббревиатурами.



Б.9. Численные методы

В стандартной библиотеке языка C++ содержатся основные строительные конструкции для математических (научных, инженерных и т.д.) вычислений.

Б.9.1. Предельные значения

Каждая реализация языка C++ определяет свойства встроенных типов, чтобы программисты могли использовать эти средства для проверки предельных значений, установки предохранителей и т.д.

В заголовке

определен класс
numeric_limits 
для каждого встроенного или библиотечного типа
T
. Кроме того, программист может определить класс
numeric_limits
для пользовательского числового типа
X
. Рассмотрим пример.


class numeric_limits {

public:

  static const bool is_specialized = true;

  static const int radix = 2;    // основание системы счисления

                                 // (в данном случае двоичная)

  static const int digits = 24;  // количество цифр в мантиссе

                                 // в текущей системе счисления

  static const int digits10 = 6; // количество десятичных цифр

                                 // в мантиссе


  static const bool is_signed = true;

  static const bool is_integer = false;

  static const bool is_exact = false;


  static float min() { return 1.17549435E–38F; } // пример

  static float max() { return 3.40282347E+38F; } // пример


  static float epsilon() { return 1.19209290E–07F; } // пример

  static float round_error() { return 0.5F; }        // пример


  static float infinity() { return /* какое-то значение */; }

  static float quiet_NaN() { return /* какое-то значение */; }

  static float signaling_NaN() { return /* какое-то значение */; }

  static float denorm_min() { return min(); }


  static const int min_exponent = –125;  // пример

  static const int min_exponent10 = –37; // пример

  static const int max_exponent = +128;  // пример

  static const int max_exponent10 = +38; // пример


  static const bool has_infinity = true;

  static const bool has_quiet_NaN = true;

  static const bool has_signaling_NaN = true;

  static const float_denorm_style has_denorm = denorm_absent;

  static const bool has_denorm_loss = false;


  static const bool is_iec559 = true; // соответствует системе
 IEC-559

  static const bool is_bounded = true;

  static const bool is_modulo = false;

  static const bool traps = true;

  static const bool tinyness_before = true;


  static const float_round_style round_style =

                                 round_to_nearest;

};


В заголовках

и
определены макросы, определяющие основные свойства целых чисел и чисел с плавающей точкой.



Б.9.2. Стандартные математические функции

В стандартной библиотеке определены основные математические функции (в заголовках

и
).



Существуют версии этих функций, принимающие аргументы типа

float
,
double
,
long double
и
complex
. У каждой из этих функций тип возвращаемого значения совпадает с типом аргумента.

Если стандартная математическая функция не может выдать корректный с математической точки зрения результат, она устанавливает переменную

errno
.

Б.9.3. Комплексные числа

В стандартной библиотеке определены типы для комплексных чисел

complex
,
complex
и
complex
. Класс complex
, где
Scalar
— некий другой тип, поддерживающий обычные арифметические операции, как правило, работоспособен, но не гарантирует переносимости программ.


template class complex {

  // комплексное число — это пара скалярных значений,

  // по существу — пара координат

  Scalar re, im;

public:

  complex(const Scalar & r, const Scalar & i):re(r), im(i) { }

  complex(const Scalar & r):re(r),im(Scalar ()) { }

  complex():re(Scalar ()), im(Scalar ()) { }


  Scalar real() { return re; } // действительная часть

  Scalar imag() { return im; } // мнимая часть

  // операторы : = += –= *= /=

};


Кроме этих членов, в классе

предусмотрено много полезных операций.



Кроме того, к комплексным числам можно применять стандартные математические функции (см. раздел Б.9.2). Примечание: в классе

complex
нет операций
<
или
%
(см. также раздел 24.9).

Б.9.4. Класс valarray

Объект стандартного класса

valarray
— это одномерный массив чисел; иначе говоря, он предусматривает арифметические операции для массивов (аналогично классу
Matrix
из главы 24), а также срезы (slices) и шаги по индексу (strides).

Б.9.5. Обобщенные числовые алгоритмы

 Эти алгоритмы из раздела

обеспечивают общие варианты типичных операций над последовательностями числовых значений.



Б.10. Функции стандартной библиотеки языка С

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

• Ввод-вывод в стиле языка C.

• Строки в стиле языка C.

• Управление памятью.

• Дата и время.

• Остальное.


Библиотека языка С содержит намного больше функций, чем описано в этой книге; рекомендуем читателям обратиться к хорошим учебникам по языку С, например, к книге Kernighan, Ritchie, The C Programming Language (K&R).

Б.10.1. Файлы

Система ввода-вывода, описанная в заголовке

, основана на файлах. Указатель на файл (
FILE*
) может относиться как к файлу, так и к стандартным потокам ввода и вывода,
stdin
,
stdout
и
stderr
. Стандартные потоки доступны по умолчанию; остальные файлы должны быть открыты явным образом.



Режим — это строка, содержащая одну или несколько директив, определяющих, как именно должен быть открыт файл.



В конкретной операционной системе может быть (и, как правило, так и есть) больше возможностей. Некоторые режимы могут комбинироваться, например, инструкция

fopen("foo","rb")
пытается открыть файл
foo
для чтения в бинарном режиме. Режимы ввода-вывода для потоков из библиотек
stdio
и
iostream
должны быть одинаковыми (см. раздел Б.7.1)

Б.10.2. Семейство функций printf()

Наиболее популярными функциями в стандартной библиотеке языка С являются функции ввода-вывода. Тем не менее рекомендуем использовать библиотеку

iostream
, потому что она безопасна с точки зрения типов и допускает расширение. Функция форматированного вывода
printf()
используется очень широко (в том числе и в программах на языке C++) и часто имитируется в других языках программирования.



В каждой версии число

n
— это количество записанных символов, а в случае неудачи — отрицательное число. На самом деле значение, возвращаемое функцией
printf()
, практически всегда игнорируется.

Объявление функции

printf()
имеет следующий вид:


int printf(const char* format ...);


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

%c
(вывести символ) и
%d
(вывести целое число). Рассмотрим пример.


int x = 5;

const char* p = "asdf";

printf("Значение x равно '%d', а значение p равно '%s'\n",x,s);


Символ, следующий за знаком

%
управляет обработкой аргументов. Первый знак
%
применяется к первому дополнительному аргументу (в данном примере спецификатор
%d
применяется к переменной
x
), второй знак
%
относится ко второму дополнительному аргументу (в данном примере спецификатор
%s
применяется к переменной
p
) и т.д. В частности, рассмотренный выше вызов функции
printf()
приводит к следующему результату:


Значение x равно '5', а значение p равно 'asdf'


Затем происходит переход на новую строку.

В принципе соответствие между директивой преобразования

%
и типом, к которому она применяется, проверить невозможно. Рассмотрим пример.


printf("Значение x равно '%s', а значение p равно '%d'\n",x,p); // ой!


Набор спецификаторов преобразования довольно велик и обеспечивает большую гибкость (а также много возможностей сделать ошибку). За символом

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



Нулевая или слишком маленькая ширина поля никогда не приводит к усечению вывода; дополнение вывода нулями или пробелами производится только тогда, когда заданная ширина поля превышает реальную.

Поскольку в языке C нет пользовательских типов в смысле языка C++, в нем нет возможностей для определения форматов вывода для таких классов, как

complex
,
vector
или
string
.

Стандартный поток вывода

stdout
в языке C соответствует потоку
cout
. Стандартный поток ввода
stdin
в языке С соответствует потоку
cin
. Стандартный поток сообщений об ошибках
stderr
в языке С соответствует потоку
cerr
. Эти соответствия между стандартными потоками ввода-вывода в языке C и C++ настолько близки, что потоки ввода-вывода как в стиле языка С, так и стиле языка С++ могут использовать один и тот ж буфер. Например, для создания одного и того же потока вывода можно использовать комбинацию операций над объектами
cout
и
stdout
(такая ситуация часто встречается в смешанном коде, написанном на языка С и С++). Эта гибкость требует затрат. Для того чтобы получить более высокую производительность, не смешивайте операции с потоками из библиотек
stdio
и
iostream
при работе с одним и тем же потоком, вместо этого вызывайте функцию
ios_base::sync_with_stdio(false)
перед выполнением первой операции ввода-вывода. В библиотеке
stdio
определена функция
scanf()
, т.е. операция ввода, похожая на функцию
printf()
. Рассмотрим пример.


int x;

char s[buf_size];

int i = scanf("Значение x равно '%d', а значение s равно '%s'\n",&x,s);


Здесь функция

scanf()
пытается считать целое число в переменную
x
и последовательность символов, не являющихся разделителями, в массив
s
. Неформатные символы указывают, что они должны содержаться в строке ввода. Рассмотрим пример.


"Значение x равно '123', а значение s равно 'string '\n"


Программа введет число

123
в переменную
x
и строку "
string
", за которой следует
0
, в массив
s
. Если вызов функции
scanf()
завершает работу успешно, результирующее значение (
i
в предыдущем вызове) будет равно количеству присвоенных аргументов-указателей (в данном примере это число равно
2
); в противном случае оно равно
EOF
. Этот способ индикации ввода уязвим для ошибок (например, что произойдет, если вы забудете вставить пробел после строки "
string
" в строке ввода?). Все аргументы функции
scanf()
должны быть указателями. Мы настоятельно рекомендуем не использовать эту функцию.

Как же ввести данные, если мы вынуждены использовать библиотеку

stdio
? Один и из распространенных ответов гласит: “Используйте стандартную библиотечную функцию
gets()
”.


// очень опасный код:

char s[buf_size];

char* p = gets(s); // считывает строку в массив s


Вызов

p=gets(s)
будет вводить символы в массив
s
, пока не обнаружится символ перехода на новую строку или не будет достигнут конец файла. В этом случае в конец строки
s
после последнего символа будет вставлен
0
. Если обнаружен конец файла или возникла ошибка, то указатель p устанавливается равным
NULL
(т.е.
0
); в противном случае он устанавливается равным
s
. Никогда не используйте функцию
gets(s)
или ее эквивалент
scanf("%s",s))!
За прошедшие годы создатели вирусов облюбовали их слабые места: генерируя вводную строку, переполняющую буфер ввода (в данном примере строку
s
), они научились взламывать программы и атаковать компьютеры. Функция
sprintf()
страдает от таких же проблем, связанных с переполнением буфера.

Библиотека

stdio
содержит также простые и полезные функции чтения и записи символов.



Обратите внимание на то, что результатом этих функций является число типа

int
(а не переменная типа
char
или макрос
EOF
). Рассмотрим типичный цикл ввода в программе на языке С.


int ch; /* но не char ch; */

while ((ch=getchar())!=EOF) { /* какие-то действия */ }


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

ungetc()
. Результат такого действия может оказаться непредсказуемым, а значит, программа не будет переносимой.

Мы описали не все функции из библиотеки

stdio
, более полную информацию можно найти в хороших учебниках по языку С, например в книге K&R.

Б.10.3. Строки в стиле языка С

Строки в стиле языка C представляют собой массивы элементов типа

char
, завершающиеся нулем. Эти строки обрабатываются функциями, описанными в заголовках
(или
; примечание: но не
) и
.

Эти функции оперируют строками в стиле языка С с помощью указателей

char*
(указатели
const char*
ссылаются на ячейки памяти, предназначенные исключительно для чтения).



Обратите внимание на то, что в языке C++ функции

strchr()
и
strstr()
дублируются, чтобы обеспечить безопасность типов (они не могут преобразовать тип
const char*
в тип
char*
, как их аналоги в языке C); см. также раздел 27.5.

Функции извлечения символов просматривают строку в стиле языка С в поисках соответственно форматированного представления числа, например "

124
" и "
1.4
". Если такое представление не найдено, функция извлечения возвращает
0
. Рассмотрим пример.

int x = atoi("fortytwo"); /* x становится равным 0 */

Б.10.4. Память

Функции управления памятью действуют в “голой памяти” (без известного типа) с помощью указателей типа

void*
(указатели
const void*
ссылаются на ячейки памяти, предназначенные только для чтения).



Функции

malloc()
и ей подобные не вызывают конструкторы, а функция
free()
не вызывает деструкторы. Не применяйте эти функции к типам, имеющим конструкторы или деструкторы. Кроме того, функция
memset()
также никогда не должна применяться к типам, имеющим конструктор.

Функции, начинающиеся с приставки mem, описаны в заголовке

, а функции выделения памяти — в заголовке
.

См. также раздел 27.5.2.

Б.10.5. Дата и время

В заголовке

можно найти несколько типов и функций, связанных с датами и временем.



Структура

tm
определяется примерно так:


struct tm {

  int tm_sec;   // секунда минуты [0:61]; 60 и 61 

                //"високосные" секунды

  int tm_min;   // минута часа [0,59]

  int tm_hour;  // час дня [0,23]

  int tm_mday;  // день месяца [1,31]

  int tm_mon;   // месяц года [0,11]; 0 — январь (примечание: не [1:12])

  int tm_year;  // год с 1900- го года ; 0 — 1900-й год,

                // 102 — 2002-й год

  int tm_wday;  // дни, начиная с воскресенья [0,6]; 0 — воскресенье

  int tm_yday;  // дней после 1 января [0,365]; 0 — 1 января

  int tm_isdst; // часы летнего времени

};


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


clock_t clock();  // количество тактов таймера после старта программы

time_t time(time_t* pt);  // текущее календарное

                          // время

double difftime(time_t t2, time_t t1); // t2–t1 в секундах


tm* localtime(const time_t* pt); // локальное время для *pt

tm* gmtime(const time_t* pt);    // время по Гринвичу (GMT) tm для

                                 // *pt или 0


time_t mktime(tm* ptm);       // time_t для *ptm или time_t(–1)


char* asctime(const tm* ptm); // представление *ptm в виде

                              // C-строки

char* ctime(const time_t* t) { return asctime(localtime(t)); }


Пример результата вызова функции

asctime()
:
"Sun Sep 16 01:03:52 1973\n"
.

Рассмотрим пример использования функции

clock
для измерения времени работы функции (
do_something()
).


int main(int argc, char* argv[])

{

  int n = atoi(argv[1]);


  clock_t t1 = clock(); // начало отсчета


  if (t1 == clock_t(–1)) { // clock_t(–1) означает "clock()

                           // не работает "

    cerr << "Извините, таймер не работает \n";

    exit(1);

  }

  for (int i = 0; i


  clock_t t2 = clock(); // конец отсчета

  if (t2 == clock_t(–1)) {

    cerr << "Извините, таймер переполнен \n";

    exit(2);

  }

  cout << "do_something() " << n << " работала "

<< double(t2–t1)/CLOCKS_PER_SEC << " секунд "

<< " (точность измерения: " << CLOCKS_PER_SEC

<< " секунд )\n";

}


Явное преобразование

double(t2–t1)
перед делением является необходимым, потому что число
clock_t
может быть целым. Для значений
t1
и
t2
, возвращаемых функцией
clock()
, величина
double(t2–t1)/CLOCKS_PER_SEC
является наилучшим системным приближением времени в секундах, прошедшего между двумя вызовами.

Если функция

clock()
не поддерживается процессором или временной интервал слишком длинный, то функция
clock()
возвращает значение
clock_t(–1)
.

Б.10.6. Другие функции

В заголовке

определены следующие функции.



Функция для сравнения (

cmp
), используемая функциями
qsort()
и
bsearch()
, должна иметь следующий тип:

int (*cmp)(const void* p,const void* q);

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

• оно является отрицательным, если

*p
меньше, чем
*q
;

• оно равно нулю, если

*p
равно
*q
;

• оно больше нуля, если

*p
больше, чем
*q
.


Подчеркнем, что функции

exit()
и
abort()
не вызывают деструкторы. Если хотите вызывать деструкторы для статических объектов и объектов, созданных автоматически (см. раздел A.4.2), генерируйте исключение.

Более полную информацию о функциях из стандартной библиотеки можно найти в книге K&R или другом авторитетном справочнике по языку С++.

Б.11. Другие библиотеки

Исследуя возможности стандартной библиотеки, вы, конечно, не найдете чего-то, что могло бы быть полезным для вас. По сравнению с задачами, стоящими перед программистами, и огромным количеством доступных библиотек, стандартная библиотека языка C++ является довольно скромной. Существует много библиотек, предназначенных для решения следующих задач.

• Графические пользовательские интерфейсы.

• Сложные математические вычисления.

• Доступ к базам данных.

• Работа в сети.

• XML.

• Дата и время.

• Система манипуляции файлами.

• Трехмерная графика.

• Анимация.

• Прочее


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

Приложение В