Язык программирования C++. Вводный курс

         

Алгоритм rotate()


template< class ForwardIterator >

void

rotate( ForwardIterator first,

        ForwardIterator middle, ForwardIterator last );

rotate()

перемещает элементы из диапазона [first,last) в конец контейнера. Элемент, на который указывает middle, становится первым. Например, для слова "hissboo" вращение вокруг буквы 'b' превращает слово в "boohiss".



Алгоритм rotate_copy()


template< class ForwardIterator, class OutputIterator >

OutputIterator

rotate_copy( ForwardIterator first, ForwardIterator middle,

             ForwardIterator last, OutputIterator result );

rotate_copy()

ведет себя так же, как rotate(), только новая последовательность копируется в контейнер, начиная с result. Возвращаемый итератор указывает на элемент, расположенный за последним скопированным. Исходный контейнер остается без изменения.

#include <algorithm>

#include <vector>

#include <iostream.h>

          

/* печатается:

   исходная последовательность:

   1 3 5 7 9 0 2 4 6 8 10

   вращение вокруг среднего элемента(0) ::

   0 2 4 6 8 10 1 3 5 7 9

   вращение вокруг предпоследнего элемента(8) ::

   8 10 1 3 5 7 9 0 2 4 6

   rotate_copy вокруг среднего элемента ::

   7 9 0 2 4 6 8 10 1 3 5

*/

int main()

{

           int ia[] = { 1, 3, 5, 7, 9, 0, 2, 4, 6, 8, 10 };

           vector< int, allocator > vec( ia, ia+11 );

        ostream_iterator< int >  ofile( cout, " " );

     cout << "исходная последовательность:\n";

     copy( vec.begin(), vec.end(), ofile ); cout << '\n';

           rotate( &ia[0], &ia[5], &ia[11] );

     cout << "вращение вокруг среднего элемента(0) ::\n";

     copy( ia, ia+11, ofile ); cout << '\n';

           rotate( vec.begin(), vec.end()-2, vec.end() );

                 

           cout << "вращение вокруг предпоследнего элемента(8) ::\n";

           copy( vec.begin(), vec.end(), ofile ); cout << '\n';

           vector< int, allocator > vec_res( vec.size() );

     rotate_copy( vec.begin(), vec.begin()+vec.size()/2,

                  vec.end(), vec_res.begin() );

     cout << "rotate_copy вокруг среднего элемента ::\n";

     copy( vec_res.begin(), vec_res.end(), ofile );

     cout << '\n';

}



Алгоритм search()


template< class ForwardIterator1, class ForwardIterator2 >

ForwardIterator

search( ForwardIterator1 first1, ForwardIterator1 last1,

        ForwardIterator2 first2, ForwardIterator2 last2 );

template< class ForwardIterator1, class ForwardIterator2,

          class BinaryPredicate >

ForwardIterator

search( ForwardIterator1 first1, ForwardIterator1 last1,

        ForwardIterator2 first2, ForwardIterator2 last2,

        BinaryPredicate pred );

Если даны два диапазона, то search()

возвращает итератор, указывающий на первую позицию в диапазоне [first1,last1), начиная с которой второй диапазон входит как подпоследовательность. Если подпоследовательность не найдена, возвращается last1. Например, в слове Mississippi

подпоследовательность iss

встречается дважды, и search()

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

#include <algorithm>

#include <vector>

#include <iostream.h>

/* печатается:

   Ожидаем найти подстроку 'ate': a t e

   Ожидаем найти подстроку 'vat': v a t

*/

int main()

{

     ostream_iterator< char >  ofile( cout, " " );

          

           char str[ 25 ]   = "a fine and private place";

           char substr[] = "ate";

                 

           char *found_str = search(str,str+25,substr,substr+3);

           cout << "Ожидаем найти подстроку 'ate': ";

     copy( found_str, found_str+3, ofile ); cout << '\n';

                 

           vector< char, allocator > vec( str, str+24 );

           vector< char, allocator > subvec(3);

           subvec[0]='v'; subvec[1]='a'; subvec[2]='t';

          

           vector< char, allocator >::iterator iter;

     iter = search( vec.begin(), vec.end(),

                    subvec.begin(), subvec.end(),

                    equal_to< char >() );

           cout << "Ожидаем найти подстроку 'vat': ";

     copy( iter, iter+3, ofile ); cout << '\n';

}



Алгоритм search_n()


template< class ForwardIterator, class Size, class Type >

ForwardIterator

search_n( ForwardIterator first, ForwardIterator last,

          Size count, const Type &value );

template< class ForwardIterator, class Size,

          class Type, class BinaryPredicate >

ForwardIterator

search_n( ForwardIterator first, ForwardIterator last,

          Size count, const Type &value, BinaryPredicate pred );

search_n()

ищет в последовательности [first,last) подпоследовательность, состоящую из count

повторений значения value. Если она не найдена, возвращается last. Например, для поиска подстроки ss в строке Mississippi

следует задать value

равным 's', а count

равным 2. Если же нужно найти две расположенные подряд подстроки ssi, то value

задается равным "ssi", а count

снова 2. search_n()

возвращает итератор на первый элемент со значением value. В первом варианте для сравнения элементов используется оператор равенства, во втором – указанная программистом операция сравнения.

#include <algorithm>

#include <vector>

#include <iostream.h>

/* печатается:

   Ожидаем найти два вхождения 'o': o o

   Ожидаем найти подстроку 'mou':  m o u

*/

          

int main()

{

     ostream_iterator< char >  ofile( cout, " " );

           const char blank = ' ';

           const char oh    = 'o';

           char str[ 26 ]  = "oh my a mouse ate a moose";

           char *found_str = search_n( str, str+25, 2, oh );

                 

           cout << "Ожидаем найти два вхождения 'o': ";

     copy( found_str, found_str+2, ofile ); cout << '\n';

           vector< char, allocator > vec( str, str+25 );

                 

           // найти первую последовательность из трех символов,

           // ни один из которых не равен пробелу: mou of mouse

           vector< char, allocator >::iterator iter;

     iter = search_n( vec.begin(), vec.end(), 3,

                            blank, not_equal_to< char >() );

           cout << "Ожидаем найти подстроку 'mou':  ";

     copy( iter, iter+3, ofile ); cout << '\n';

}



Алгоритм set_difference()


template< class InputIterator1, class InputIterator2,

          class OutputIterator >

OutputIterator

set_difference( InputIterator1 first1, InputIterator1 last1,

                InputIterator2 first2, InputIterator2 last2,

                OutputIterator result );

template< class InputIterator1, class InputIterator2,

          class OutputIterator, class Compare >

OutputIterator

set_difference( InputIterator1 first1, InputIterator1 last1,

                InputIterator2 first2, InputIterator2 last2,

                OutputIterator result, Compare comp );

set_difference()

строит отсортированную последовательность из элементов, имеющихся в первой последовательности [first1,last1), но отсутствующих во второй – [first2,last2). Например, разность последовательностей {0,1,2,3} и {0,2,4,6} равна {1,3}. Возвращаемый итератор указывает на элемент за последним помещенным в выходной контейнер result. В первом варианте предполагается, что обе последовательности были отсортированы с помощью оператора “меньше”, определенного для типа элементов контейнера; во втором для упорядочения используется указанная программистом операция comp.



Алгоритм set_intersection()


template< class InputIterator1, class InputIterator2,

          class OutputIterator >

OutputIterator

set_intersection( InputIterator1 first1, InputIterator1 last1,

                  InputIterator2 first2, InputIterator2 last2,

                  OutputIterator result );

template< class InputIterator1, class InputIterator2,

          class OutputIterator, class Compare >

OutputIterator

set_intersection( InputIterator1 first1, InputIterator1 last1,

                  InputIterator2 first2, InputIterator2 last2,

                  OutputIterator result, Compare comp );

set_intersection()

строит отсортированную последовательность из элементов, встречающихся в обеих последовательностях – [first1,last1) и [first2,last2). Например, пересечение последовательностей {0,1,2,3} и {0,2,4,6} равно {0,2}. Возвращаемый итератор указывает на элемент за последним помещенным в выходной контейнер result. В первом варианте предполагается, что обе последовательности были отсортированы с помощью оператора “меньше”, определенного для типа элементов контейнера; во втором для упорядочения используется указанная программистом операция comp.



Алгоритм set_symmetric_difference()


template< class InputIterator1, class InputIterator2,

          class OutputIterator >

OutputIterator

set_symmetric_difference(

       InputIterator1 first1, InputIterator1 last1,

       InputIterator2 first2, InputIterator2 last2,

       OutputIterator result );

template< class InputIterator1, class InputIterator2,

          class OutputIterator, class Compare >

OutputIterator

set_symmetric_difference(

       InputIterator1 first1, InputIterator1 last1,

       InputIterator2 first2, InputIterator2 last2,

       OutputIterator result, Compare comp );

set_symmetric_difference()

строит отсортированную последовательность из элементов, которые встречаются только в первой последовательности [first1,last1) или только во второй – [first2,last2). Например, симметрическая разность последовательностей {0,1,2,3} и {0,2,4,6} равна {1,3,4,6}. Возвращаемый итератор указывает на элемент за последним помещенным в выходной контейнер result. В первом варианте предполагается, что обе последовательности были отсортированы с помощью оператора “меньше”, определенного для типа элементов контейнера; во втором для упорядочения используется указанная программистом операция comp.



Алгоритм set_union()


template< class InputIterator1, class InputIterator2,

          class OutputIterator >

OutputIterator

set_union(InputIterator1 first1, InputIterator1 last1,

          InputIterator2 first2, InputIterator2 last2,

          OutputIterator result );

template< class InputIterator1, class InputIterator2,

          class OutputIterator, class Compare >

OutputIterator

set_union(InputIterator1 first1, InputIterator1 last1,

          InputIterator2 first2, InputIterator2 last2,

          OutputIterator result, Compare comp );

set_union()

строит отсортированную последовательность из элементов, которые встречаются либо в первой последовательности [first1,last1), либо во второй – [first2,last2), либо в обеих. Например, объединение последовательностей {0,1,2,3} и {0,2,4,6} равно {0,1,2,3,4,6}. Если элемент присутствует в обеих последовательностях, то копируется экземпляр из первой. Возвращаемый итератор указывает на элемент за последним помещенным в выходной контейнер result. В первом варианте предполагается, что обе последовательности были отсортированы с помощью оператора “меньше”, определенного для типа элементов контейнера; во втором для упорядочения используется указанная программистом операция comp.

#include <algorithm>

#include <set>

#include <string>

#include <iostream.h>

/* печатается:

   элементы множества #1:

        Иа-Иа Пух Пятачок Тигра

   элементы множества #2:

        Бука Пух Слонопотам

   элементы set_union():

        Бука Иа-Иа Пух Пятачок Слонопотам Тигра

   элементы set_intersection():

        Пух

   элементы set_difference():

        Иа-Иа Пятачок Тигра

   элементы_symmetric_difference():

       Бука Иа-Иа Пятачок Слонопотам Тигра

*/

          

int main()

{

           string str1[] = { "Пух", "Пятачок", "Тигра", "Иа-Иа" };

           string str2[] = { "Пух", "Слонопотам", "Бука" };

     ostream_iterator< string >  ofile( cout, " " );

                 

           set<string,less<string>,allocator> set1( str1, str1+4 );

           set<string,less<string>,allocator> set2( str2, str2+3 );

     cout << "элементы множества #1:\n\t";

     copy( set1.begin(), set1.end(), ofile ); cout << "\n\n";

     cout << "элементы множества #2:\n\t";

     copy( set2.begin(), set2.end(), ofile ); cout << "\n\n";

           set<string,less<string>,allocator> res;

           set_union( set1.begin(), set1.end(),

                set2.begin(), set2.end(),

                inserter( res, res.begin() ));

     cout << "элементы set_union():\n\t";

     copy( res.begin(), res.end(), ofile ); cout << "\n\n";

           res.clear();

           set_intersection( set1.begin(), set1.end(),

                       set2.begin(), set2.end(),

                       inserter( res, res.begin() ));

     cout << "элементы set_intersection():\n\t";

     copy( res.begin(), res.end(), ofile ); cout << "\n\n";

     res.clear();

     set_difference( set1.begin(), set1.end(),

                     set2.begin(), set2.end(),

                     inserter( res, res.begin() ));

     cout << "элементы set_difference():\n\t";

     copy( res.begin(), res.end(), ofile ); cout << "\n\n";

     res.clear();

     set_symmetric_difference( set1.begin(), set1.end(),

                               set2.begin(), set2.end(),

                               inserter( res, res.begin() ));

     cout << "элементы set_symmetric_difference():\n\t";

     copy( res.begin(), res.end(), ofile ); cout << "\n\n";

}



Алгоритм sort()


template< class RandomAccessIterator >

void

sort( RandomAccessIterator first,

      RandomAccessIterator last );

template< class RandomAccessIterator, class Compare >

void

sort( RandomAccessIterator first,

      RandomAccessIterator last, Compare comp );

sort()

переупорядочивает элементы в диапазоне [first,last) по возрастанию, используя оператор “меньше”, определенный для типа элементов контейнера. Во втором варианте порядок устанавливается операцией сравнения comp. (Для сохранения относительного порядка равных элементов пользуйтесь алгоритмом stable_sort().) Мы не приводим пример, специально иллюстрирующий применение алгоритма sort(), поскольку его можно найти во многих других программах, в частности в binary_search(), equal_range() и inplace_merge().



Алгоритм sort_heap()


template< class RandomAccessIterator >

void

sort_heap( RandomAccessIterator first,

           RandomAccessIterator last );

template< class RandomAccessIterator, class Compare >

void

sort_heap( RandomAccessIterator first,

           RandomAccessIterator last, Compare comp );

sort_heap()

сортирует последовательность в диапазоне [first,last), предполагая, что это правильно построенный хип; в противном случае поведение программы не определено. (Разумеется, после сортировки хип перестает быть хипом!) В первом варианте при сравнении используется оператор “меньше”, определенный для типа элементов контейнера, а во втором – операция comp.

#include <algorithm>

#include <vector>

#include <assert.h>

template <class Type>

void print_elements( Type elem ) { cout << elem << " "; }

          

int main()

{

           int ia[] = { 29,23,20,22,17,15,26,51,19,12,35,40 };

           vector< int, allocator > vec( ia, ia+12 );

                 

     // печатается: 51 35 40 23 29 20 26 22 19 12 17 15

           make_heap( &ia[0], &ia[12] );

     void (*pfi)( int ) = print_elements;

     for_each( ia, ia+12, pfi ); cout << "\n\n";

     // печатается: 12 17 15 19 23 20 26 51 22 29 35 40

     // минимальный хип: в корне наименьший элемент

           make_heap( vec.begin(), vec.end(), greater<int>() );

     for_each( vec.begin(), vec.end(), pfi ); cout << "\n\n";

     // печатается: 12 15 17 19 20 22 23 26 29 35 40 51

           sort_heap( ia, ia+12 );

     for_each(  ia, ia+12, pfi ); cout << "\n\n";

           // добавим новый наименьший элемент

           vec.push_back( 8 );

     // печатается: 8 17 12 19 23 15 26 51 22 29 35 40 20

           // новый наименьший элемент должен оказаться в корне

           push_heap( vec.begin(), vec.end(), greater<int>() );

     for_each(  vec.begin(), vec.end(), pfi ); cout << "\n\n";

     // печатается: 12 17 15 19 23 20 26 51 22 29 35 40 8

           // наименьший элемент должен быть заменен на следующий по порядку

           pop_heap( vec.begin(), vec.end(), greater<int>() );

     for_each( vec.begin(), vec.end(), pfi ); cout << "\n\n";

<
}

#

#include, директива

использование с using-директивой, 68, 427

использование с директивой связывания, 354

*

умножения оператор

комплексных чисел, 155

ч

члены класса

функции-члены

константные, 611–14

подвижные (volatile), 611–14

Д

деструктор(ы)

для элементов масс??а

освобождение динамической памяти, 693–94

abort(), функция

вызов из terminate() как подразумеваемое поведение, 541

abs(), функция

поддержка для комплексных чисел, 156

accumulate(), обобщенный алгоритм, 1104

adjacent_difference(), обобщенный алгоритм, 1106

adjacent_find(), обобщенный алгоритм, 1107

ainooi

к базовому классу, 880–88

algorithm, заголовочный файл, 584

any(), функция

в классе bitset, 167

append(), функция

конкатенация строк, 287

argc, переменная

счетчик аргументов в командной строке, 356

argv, массив

для доступа к аргументам в командной строке, 356

assert(), макрос, 51

использование для отладки, 226

at(), функция

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

atoi(), функция

применение для обработки аргументов в командной строке, 360

auto_ptr, шаблон класса, 395–400

memory, заголовочный файл, 395

инициализация, 397

подводные камни, 399

aункции

интерфейс

включение объявления исключений в, 546

B

back(), функция

поддержка очереди, 316

back_inserter(), адаптор функции

использование в операции вставки push_back(), 577

begin(), функция

итератор

возврат с помощью, 578

использование, 261

binary_search(), обобщенный алгоритм, 1108

bind1st(), адаптор функции, 573

bind2nd(), адаптор функции, 573

bitset, заголовочный файл, 168

bitset, класс, 165

size(),  функция, 167

test(),  функция, 167

to_long(),  функция, 170

to_string(),  функция, 170

заголовочный файл bitset, 168

оператор доступа к биту ([]), 167

операции, 168–71

break, 218–19

break, инструкция

использование для выхода из инструкции switch, 203

сравнение с инструкцией return, 346

C

C, язык

символьные строки

динамическое выделение памяти для, 401

необходимость доступа из класса string, 128



отсутствие завершающего нуля как программная ошибка, 402

C_str(), функция

преобразование объектов класса string в C-строки, 137

C++, язык

std, пространство имен, 426–28

введение в (глава), 12–13

компоненты

(часть 2), 319

типы данных (глава), 98–140

предопределенные операторы (таблица), 727

case, ключевое слово

использование в инструкции switch (таблица), 202

catch-обработчик, 62, 534, 537

критерий выбора, 63

определение, 537

универсальный обработчик, 543–45

cerr, 26

представление стандартного вывода для ошибок с помощью, 1041

char *, указатель

работы с C-строками символов, 92

char, тип, 76

check_range(), пример функции

как закрытая функция-член, 51

cin, 26

использование итератора istream_iterator, 579

представление стандартного ввода с помощью, 1041

class, ключевое слово

typename как синоним, 479

использование в определении класса, 594

использование в определении шаблона класса, 801

использование в параметрах-типах шаблона

класса, 800

функции, 476

const, квалификатор

вопросы разрешения перегрузки функций

параметры-типы, 432

вопросы разрешения перезагрузки функций

использование преобразования квалификаторов, 449

ранжирование преобразований, связанных с инициализацией ссылочных параметров, 473

константная функция-член, 611–14

константные объекты, динамическое выделение и освобождение памяти, 402–3

константные параметры

параметры-ссылки с квалификатором const, 330, 340

передача массива из константных элементов, 336

константный итератор, 262

контейнеры, необходимость константного итератора, 575

преобразование объектов в константы, 101

сравнение с volatile, 127

ссылка, инициализация объектом другого типа, 105

указатели на константные объекты, 101

const_cast, оператор, 180

continue, инструкция, 219

copy(), обобщенный алгоритм, 1109

использование класса inserter, 305

конкатенация векторов с помощью, 557

count(), обобщенный алгоритм, 1112

использование istream_iterator и ostream_iterator, 581

использование с контейнерами multimap и multiset, 311



использование с множествами, 306

использование с отображениями, 298

count(), функция

в классе bitset, 167

count_if(), обобщенный алгоритм, 1114

cout, 26

представление стандартного вывода с помощью, 1041

cпецификации

исключений

для документирования исключений, 546

D

default, ключевое слово

использование в инструкции switch, 202, 205

delete, оператор, 35, 162–63, 744–53

безопасное и небезопасное использование, примеры, 394

для массивов, 749–51

объектов класса, 750

синтаксис, 402

для одиночного объекта, 392

использование класса-распределителя памяти (сноска), 256

размещения, 751–53

deque (двустороння очередь, дека)

использование итераторов с произвольным доступом, 583

как последовательный контейнер, 248–301

применение для реализации стека, 314

требования к вставке и доступу, 252

do-while, инструкция, 216–18

сравнение с инструкциями for и while, 209

E

иници??изация

массива

динамически выделенных объектов классов, 691–94

копиру??ий

конструктор, 680–82

end(), функция

итератор, использование, 261

endl, манипулятор потока iostream, 27

enum, ключевое слово, 112

equal_range(), обобщенный алгоритм

использование с контейнерами multimap и multiset, 310

extern "C"

и перегруженные функции, 438–39

неприменимость безопасного связывания, 440

указатели на функции, 373–75

extern, ключевое слово

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

использование с членами пространства имен, 418

как директива связывания, 354

объявление

константы, 386

шаблона функции, 481

объявления объектов

без определения, 382

размещение в заголовочном файле, 384

F

f, суффикс

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

find(), обобщенный алгоритм

использование с контейнерами multiset и multimap, 309

поиск объектов в множестве, 306

поиск подстроки, 273

поиск элемента отображения, 298

find_first_of(), обобщенный алгоритм

нахождение знаков препинания, 280

нахождение первого символа в строке, 273

find_last_ of(), 279

find_last_not_of(), 279



for, инструкция, 209–12

использование с инструкцией if, 196

front(), функция

поддержка очереди, 316

front_inserter(), адаптор функции

использование в операции push_front(), 577

fstream, класс

файловый ввод / вывод, 1042

full(), функция

модификация алгоритма динамического роста стека, 317

functional, заголовочный файл, 568

G

get(), функция, 1063–66

getline(), функция, 270, 1066–68

goto, инструкция, 219–22

greater, объект-функция, 571

greater_equal, объект-функция, 571

I

i?enaaeaaiea

почленное для объектов класса, 925–29

i?iecaiaiua eeannu

ae?ooaeuiua ooieoee, 899–925

определение

при одиночном наследовании, 876–78

присваивание

оператор

перегруженный, 925–29

if, инструкция, 192–98

If, инструкция

условный оператор как альтернатива, 158

insert(), функция

вставка символов в строку, 286

добавление элементов в множество, 305

реализация, 266

списки, 222

inserter(), адаптор функции

для вставки с помощью insert(), 577

inserter, класс, 305

Iomanip, заголовочный файл, 136

iostream библиотека

iostream.h, заголовочный файл, пример использования, 563

ввод

istream_iterator, 579

итератор чтения, 582

вывод

ostream_iterator, 580–82

итератор записи, 582

итератор чтения, 582

итераторы, 578–82

манипуляторы

endl, 27

операторы, сцепление, 28–29

iostream.h, заголовочный файл

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

isalpha(), функция, 206

ctype, заголовочный файл, 283

isdigit(), функция

ctype, заголовочный файл, 283

ispunct(), функция

ctype, заголовочный файл, 283

isspace(), функция

ctype, заголовочный файл, 283

istream_iterator, 579–80

iterator, заголовочный файл, 578

L

less, объект-функция, 572

less_equal, объект-функция, 572

limits, заголовочный файл, 145

list, заголовочный файл, 256

locale, заголовочный файл, 283

l-значение, 81

как возвращаемое значение, подводные камни, 348

оператор присваивания, требования, 149

преобразования, 447

преобразование точного соответствия, 445

точное соответствие при разрешении перегрузки функций, 457



трансформация, 450, 469

преобразование аргументов шаблона функции, 486

M

main(), 15

обработка аргументов в командной строке, 356–65

map, заголовочный файл, 293

использование с контейнером multimap, 309

memory, заголовочный файл, 395

merge(), обобщенный алгоритм

специализированная версия для спискаов, 588

minus(), объект-функция, 570

modulus, объект-функция, 571

multimap (мультиотображение), контейнер, 309–12

map, заголовочный файл, 310

сравнение с отображением, 303

multiplies, объект-функция, 570

multiset (мультимножество), контейнер, 309–12

set, заголовочный файл, 310

N

negate, объект-функция, 571

new оператор, 162–63

для константных объектов, 403–4

для массивов, 400–402

классов, 749–51

для объектов классов, 745

для одиночных объектов, 392–95

использование класса распределителя памяти (сноска), 256

оператор размещения new, 403–4

для объектов класса, 751–53

спецификации

исключений, 546–50

и указат??и на функции, 548–50

статические члены класса, 621–27

данные-члены, 621–27

функции-члены, 626–27

not_equal_to, объект-функция

(код), 571

not1(), адаптор функции

как адаптор-отрицатель, 573

not2(), адаптор функции

как адаптор-отрицатель, 573

numeric, заголовочный файл, 584

использование численных обобщенных алгоритмов, 586

O

oaaeiiu eeannia

конкретизация, 800–811

члены

шаблонов, 826–31

ofstream, тип, 1076–86

фун??ии-члены

volatile, 611–14

функции-члены

константные, 611–14

ostream_iterator, 580–82

P

pair, класс, 127

использование для возврата нескольких значений, 197

plus, объект-функция, 568, 570

pop_back(), функция

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

использование для реализации динамического роста стека, 317

push_back(), функция

векторы, вставка элементов, 123

поддержка в контейнерах, 257

стеки, использования для динамического выделения памяти, 317

push_front(), функция

поддержка в списковых контейнерах, 257

pаголовочные файлы

содержимое

объявления функций, с включением явной спецификации исключений, 546



Q

queue, заголовочный файл, 315

R

register, ключевое слово, 389–90

reinterpret_cast, оператор

опасности, 181

reinterpret_cast, оператор, 181

release()б функция

управление объектами с помощью класса auto_ptr, 400

reserve(), функция

использование для установки емкости контейнера, 255

reset(), функция

в классе bitset, 167

установка указателя auto_ptr, 398

resize(), функция

использование для изменения размера контейнера, 258

return, инструкция

завершение функции с помощью, 346

неявное преобразование типа в, 176

сравнение с выражением throw, 531

r-значение, 81

использование при вычислении выражений, 141

S

set, заголовочный файл, 304, 310

size(), функция

для модификации алгоритма выделения памяти в стеке, 317

sizeof, оператор, 159–62

использование с типом ссылки, 161

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

как константное выражение, 162

sort(), обобщенный алгоритм

вызов, 120

передача объекта=функции в качестве аргумента, 569

stack, заголовочный файл, 312

static_cast

сравнение с неявным преобразованием, 180

static_cast, оператор

опасности, 181

std, пространство имен, 426–28

string, заголовочный файл, 67

string, строковый тип, 95–98

substr(), функция, 275

пустая строка, 96

смешение объектов типа string и C-строк, 97

switch, инструкция, 207

использование ключевого слова case, 202

использование ключевого слова default, 202, 205

T

terminate(), функция, 541

this, указатель, 616–20

tolower(), функция

locale, заголовочный файл, 283

преобразование заглавных букв в строчные, 283

toupper(), функция

ctype, заголовочный файл, 283

locale, заголовочный файл, 283

true, ключевое слово, 108

typedef

для объявления указателя на функцию, 372

для улучшения читабельности, 295, 369

как синоним существующего имени типа, 431

массива указателей на функции, 369

typename, 242

использование с параметрами шаблона функции, 480

U

unexpected(), функция

для обработки нераспознанных исключений, 547

unique(), обобщенный алгоритм

удаление дубликатов из вектора, 557



unique_copy(), обобщенный алгоритм

запись целых чисел из вектора в стандартный вывод, 579

using-директивы, 423–26

влияние на разрешение перегрузки функции, 463

для объявления перегруженных функций, 437–38

сравнение с using-объявлениями, 423–26

using-объявления, 422–23

влияние на разрешение перегрузки функции, 462

для объявления перегруженных функций, 434–36

сравнение с using-директивами, 423–26

utility, заголовочный файл, 127

V

vector, заголовочный файл, 70, 121, 256

void

в списке параметров функции, 325

указатель, 179

void*

преобразование в void* как стандартное преобразование, 456

volatile, квалификатор, 127

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

для функции-члена, 611–14

использование преобразования квалификаторов, 471

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

квалификаторов, 449

W

while, инструкция, 213–16

сравнение с инструкциями for и do-while, 209

А

абстракция

объекта, класс комплексных чисел как пример, 154

стандартная библиотека, преимущества использования, 165

автоматические объекты, 388–90

объявление с ключевым словом register, 389–90

особенности хранения, 388

адапторы

функций, для объектов-функций, 573

адапторы функций, 573

адрес(а)

как значение указателя, 88

конкретизированных шаблонов функций, 484

алгоритм(ы)

функция

выведение аргумента шаблона, 489

разрешение перегрузки, 511

шаблон как, 475

аргумент(ы), 321

передача, 345

использование указателей для, 87

передача по значению, 327

по умолчанию, 340–43

должны быть хвостовыми, 341

и виртуальные функции, 913

и устоявшие функции, 472–73

тип

преобразования, разрешение перегрузки функции, 444–60

преобразования, расширение типа, 451–53

преобразования, ссылок, 457–59

преобразования, стандартные, 453–57

шаблона класса

для параметров-констант, 805–9

для параметров-типов, 800–811

шаблонов функции

явные, 490–93

шаблонов функций

выведение аргументов, 485–90

явная спецификация, мотивировка, 492

явная спецификация, недостатки, 492

явное специфицирование, 490

арифметические



исключения, 143

объекты-функции, 570

операторы, 142–45

таблица, 142

операции, поддержка для комплексных чисел, 126

преобразования, 175, 142–45

bool в int, 109

неявное выполнение при вычислении выражений, 175

типов, расширение типа перечисления, 112

указатели, 90

ассоциативность

операторов, влияние на вычисление выражений, 171–74

порядок вычисления подвыражений, 142

ассоциативные контейнеры, 248–301

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

ассоциирование

значений, использование класса pair, 127

Б

базовые классы

абстрактные базовые классы, 865–69, 908

видимость классов

при виртуальном наследовании, 983–84

видимость членов

при множественном наследовании, 968–71

при одиночном наследовании, 966–68

виртуальные базовые классы, 974–87

деструкторы, 896–99

доступ

к базовым классам, 958–64

к закрытым базовым классам, 963

к защищенным членам, 871

к членам, 880–88

доступ к элементам отображения с помощью, 299

конструирование

виртуальное наследование, 974–82

множественное наследование, 950–51

одиночное наследование, 889–96

почленная инициализация, 925–27

конструкторы, 889–99

определение базового класса

при виртуальном наследовании, 976–78

при множественном наследовании, 950–55

при одиночном наследовании, 871–75

преобразование к базовому классу, 865–69

при выведении аргументов шаблона функции, 487

присваивание, почленное присваивание, 927–29

байты

запись с помощью put(), 1063

чтение с помощью get(), 1063–66

безопасное связывание, 384

перегруженных функций, 440

бесконечный

рекурсия, 351

цикл, избежание в операциях поиска в строке, 274

бинарные

операторы, 141

битовое поле

как средство экономии памяти, 643–45

битовый вектор, 164

в сравнении с классом bitset, 164

блок

try-блок, 533–37

инструкций, 188

комментария, 24

функции, 321

функциональный try-блок, 533–37

и конструкторы, 1024–26

больше (>), оператор

поддержка в арифметических типах данных, 30

булевский(е)

константы, операторы, дающие в результате, 146



стандартные преобразования при разрешении перегрузки функции, 453

тип bool, 108–10

В

вектор(ы)

find(), обобщенный алгоритм, 554

емкость, связь с размером, 258

идиоматическое употребление в STL, 123

объектов класса, 689–96

присваивание, сравнение со встроенными массивами, 122

сравнение со списками, 251–52

требования к вставке и доступу, 252

увеличение размера, 253–56

вертикальная табуляция ()

как escape-последовательность, 77

взятия адреса (&) оператор

использование в определении ссылки, 104, 105

использование с именем функции, 367

как унарный оператор, 141

взятия индекса оператор ([]), 736

использование в векторах, 121

использование в классе bitset, 168

использование в отображениях, 294

отсутствие поддержки в контейнерах multimap и multiset, 312

взятия остатка, оператор (%), 142

видимость

определения символической константы, 386

переменных в условии цикла, 211, 379–81

роль в выборе функции-кандидата при разрешении перегрузки функции, 460

требование к встроенным функциям, 353, 387

членов класса, 607, 645–52

висячий

проблемы висячего else, описание и устранение, 195

указатель, 389

как проблема динамически выделенного объекта, 394

возврат каретки (\\r)

как escape-последовательность, 77

время жизни, 381

auto_ptr, влияние на динамически выделенные объекты, 395

автоматических объектов, 388

динамически выделенных объектов, 392

сравнение с указателями на них, 394

и область видимости (глава), 376–428

локальных объектов

автоматических и статических, 388

влияние раскрутки стека на объекты типа класса, 541

проблема возврата ссылки на локальный объект, 348

вставка элементов

в вектор, 123

в контейнер, с помощью адапторов функций, 577

в контейнеры multimap и multiset, 311

в отображение, 294

в последовательные контейнеры, 265

в стек, 314

использование push_back(), 257

итераторы, обозначение диапазона, 575–77

различные механизмы для разных типов контейнеров, 252

встроенные функции, 133, 322

объекты-функции, 559, 566

объявление, 352–53



шаблонов функций как, 481

определение, размещение в заголовочном файле, 385

перегруженные операторы вызова, 559

преимущества, 352

сравнение с не-встроенными функциями-членами, 605–7

встроенный(е)

массивы

запрет иниициализации другим массивом, 115

запрет использования в качестве возвращаемого значения функции, 324

запрет присваивания другому массиву, 324

запрет ссылаться на, 115

инициализация при выделении из хипа, 400

отсутствие поддержки операции erase(), 557

поддержка в обобщенных алгоритмах, 553

сравнение с векторами, 122

типы данных

арифметические, 30–33

выполнение

непоследовательные инструкции, 20

условное, 20

выражения

(глава), 141–87

использование аргументов по умолчанию, 342

порядок вычисления подвыражений, 142

разрешение имен, 377

вычисление

логических операторов, 146

порядок вычисления подвыражений, 142

вычитание

minus, объект-функция, 570

комплексных чисел, 154

Г

глобальное пространство имен

проблема засорения, 66, 406

глобальные объекты

и функции, 381–87

сравнение с параметрами и возвращаемыми значениями функций, 349–50

глобальные функции, 381

горизонтальная табуляция (\\t)

как escape-последовательность, 77

Д

данные члены, 595–96

данные-члены

битовые поля, 643–45

изменчивые (mutable), 614–16

статические, 621–28

в шаблонах классов, 821–24

указатель this, 616–21

члены базового и производного классов, 870–79

двойная кавычка (\\ ")

как escape-последовательность, 77

двойная обратная косая черта (\\)

как escape-последовательность, 77

двунаправленный итератор, 583

декремента оператор (--)

встроенный, 153–54

перегруженный, 740–44

постфиксная форма, 153, 743

префиксная форма, 153, 742

деление

комплексных чисел, 155

целочисленное, 143

деления по модулю оператор (%), 142

деструктор(ы), 682–89

для элементов массива, 690

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

для массива, 162, 400–402

исчерпание памяти, исключение bad_alloc, 393

как требование к динамически растущему вектору, 253

объектов, 392–406

управление с помощью класса auto_ptr, 395



динамическое освобождение памяти

для массивов, 400–402

объектов, 392–406

константных, 402–3

одиночных объектов, 392–95

оператор delete, 134, 392, 394, 744–53

управление с помощью класса auto_ptr, 395

утечка памяти, 395

директивы, 21–24

директивы связывания, 353–55

в связи с  перегрузкой, 438

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

для элементов массива

динамическое выделение памяти, 691–94

доступ

к контейнеру

использование итератора для, 261

последовательный доступ как критерий выбора типа, 252

к массиву, 31

индекс, 45

индексирование, 113

к пространству имен

механизмы, компромиссные решения, 68

к членам, 598–99, 607–8

оператор доступа к членам ->, 740

произвольный, итератор с произвольным доступом, 583

уровни, protected, 49

друзья, 730–33

и специальные права доступа, 137, 599–600

перегруженные операторы, 730–33

См. также доступ, класс(ы), наследование, 815–21

Е

емкость контейнерных типов

в сравнении с размером, 253

начальная, связь с размером, 258

З

забой (, 77

заголовочные файлы

как средство повторного использования объявлений функций, 323

по имени

algorithm, 72, 584

bitset, 167

complex, 125

fstream, 1042

functional, 568

iomanip, 136

iterator, 578

limits, 145

locale, 283

map, 293

memory, 395

numeric, 584, 586

queue, 315

set, 304

sstream, 1044

stack, 312

string, 68

vector, 70, 121, 256

предкомпилированные, 385

содержимое

включение определения шаблона функции, преимущества и недостатки, 495

встроенные функции, 353

директивы связывания, 354

объявления, 82, 385–87

объявления явных специализаций шаблонов, 503

спецификация аргументов по умолчанию, 341

запись активации, 327

автоматическое включение объектов в, 388

запятая (,)

неправильное использование для индексации массива, 117

оператор, 163

звонок ()

как escape-последовательность, 77

знак вопроса ()

как escape-последовательность, 77

И

И, оператор, 142

идентификатор, 83

использования в качестве спецификатора типа класса, 129

как часть определения массива, 113



соглашения по именованию, 83

иерархии

определение, 862–69

идентификация членов, 870–80

исключений, в стандартной библиотеке C++, 1026–29

поддержка мезанизма классов, 128

изменчивый (mutable) член, 614–16

именование

соглашения об именовании идентификаторов, 83

имя, 83

typedef, как синоним, 126–27

именование членов класса, 607–8

квалифицированные имена, 410–12

статических членов класса, 622–23

членов вложенных пространств имен, 412–14

шаблонов функций как членов пространства имен, 524

область видимости объявления, 376

параметра шаблона

функции, 478

перегруженные операторы, 727–28

переменной, 83

псевдонимы пространства имен, как альтернативные имена, 420–21

разрешение, 377

в локальной области видимости, 379

в области видимости класса, 649–52

в определении шаблона функции, 514–20

инициализация

векторов, 121

сравнение с инициализацией встроенных массивов, 122

комплексного числа, 154

массива

динамически выделенного, 400

динамически выделенных объектов классов, 749

многомерного, 116

указателей на функции, 369

недопустимость инициализации другим массивом, 115

объектов

автоматических, 388

автоматических, по сравнению с локальными статическими, 391

глобальных, инициализация по умолчанию, 382

динамически выделенных, 393

константных, 101

статических локальных, 390, 391

поведение auto_ptr, 397

сравнение с присваиванием, 148

ссылок, 104

указателя на функцию, 367

влияние на спецификацию исключений, 549

вопросы, связанные с перегруженными функциями, 439

инкремента оператор (++)

встроенный, 154

перегруженный, 740–44

постфиксная форма, 153, 743

префиксная форма, 153, 742

инструкции, 188–98

break

для выхода из инструкции switch, 203

break, инструкция, 218–19

continue, 219

do-while, 216–17

сравнение с инструкциями for и while, 209

for, 209–13

goto, 219–21

if, 20, 192–98

if-else, условный оператор как альтернатива, 158

switch, 201–3

использование ключевого слова default, 202, 205

while, 213–16

сравнение с инструкциями for и do-while, 209



блок, 188

объявления, 189–92

простые, 188–89

составные, 188–89

инструкция

while, 21

использование преобразования квалификаторов, 449

использование шаблонов, 62

итератор с произвольным доступом, 583

итератор(ы), 123, 261

begin(), доступ к элементам контейнера, 261

end(), доступ к элементам контейнера, 261

iterator, заголовочный файл, 578

абстракция, использование а обобщенных алгоритмах для обхода, 552

адаптор, 557

вставка элементов в последовательные контейнеры, 266

доступ к подмножеству контейнера с помощью, 262

использование в обобщенных алгоритмах, 575–83

категории, 582–83

двунаправленный итератор, 583

итератор записи, 582

итератор с произвольным доступом, 583

итератор чтения, 582

однонаправленный итератор, 583

обозначение интервала с включенной левой границей, 583

обратные итераторы, 578

потоковык итераторы ввода/вывода, 578–82

istream_iterator, 579–80

ostream_iterator, 580–82

запись целых чисел из вектора в стандартный вывод, 578

чтение целых чисел из стандартного ввода в вектор, 579

требования к поведению, выдвигаемые обобщенными алгоритмами, 584

удаление элементов из последовательного контейнера, 267

К

китайский язык

поддержка двухбайтовых символьных литералов, 77

класс(ы)

возвращаемые значения, 347–49

вопросы эффективности, 712–18

друзья, 599–600, 731

заголовок, 594

объединение, 638–43

объявление, сравнение с определением класса, 600–601

определение, 594–601

сравнение с объявлением класса, 600–601

параметры

вопросы эффективности, 330, 712–18

для возврата сразу нескольких  значений, 350

для передачи сразу нескольких параметров, 350

тело, 594

командная строка

класс, 363–65

опции, 356–65

argc, argv - аргументы main(), 356

использование встроенного массива для обработки, 356

пример программы, 361–63

комментарии, 24–26

блочные, 25

комплексные числа, 18, 125–26

выражения с участием, 155

заголовочный файл complex, 125

как абстракция класса, 30

операции, 154–58

представление, 156

типы данных, 30

композиция



объектов, 963–65

сравнение с наследованием, 960–62

конкретизация

шаблона функции, 482

явное объявление специализации

шаблона функции, 497–98

Конкретизация

шаблона функции

разрешение перегрузки, 506–13

конктеризация

точка конкретизации, 518

константы

константные выражения

sizeof() как пример, 162

размер массива должен быть, 113

литерал, 76–78

подстановка, 386

преобразование объектов в, 101

ссылки, рассматриваемые как, 104

конструктор(ы)

вызовы виртуальных функций в, 923–25

для базовых классов, 899

почленная инициализация, 925–30

при виртуальном наследовании, 974–82

при единичном наследовании, 896

при множественном наследовании, 950–51

для элементов массива

список инициализации массива, 689–91

и функциональные try-блоки, 1024–26

как коверторы, 761–64

конструкторы по умолчанию, 678–79

для элементов вектора, 694–96

копирующие конструкторы, 237, 680–82

почленная инициализация, 703–9, 925–30

ограничение возможности созданий объектов, 680

список инициализации членов, 696–703

контейнерные типы

определение, 256–61

контейнерные типы, 248–301

вопросы выделения памяти при копировании, 577

емкость, 253

связь с размером, 253–58

и итераторы, 261–65

инициализация, с помощью пары итераторов, 263

очереди с приоритетами, 315

параметры, 338–40, 350

преимущества, автоматическое управление памятью, 402

размер, 258

связь с емкостью, 253–56

требования к типам, с которыми конкретизируется контейнер, 259

копирование

вопросы выделения памяти, 577

использование ссылок для избежания, 330

как операция инициализации, 258

массивов, 115

сравнение со стоимостью произвольного доступа, 252

строк, 96

копирующий

конструктор, 43, 131

для динамического увеличения размера вектора, 255

оператор присваивания, реализация, 237

Л

лексикографическое упорядочение, 289

в обобщенных алгоритмах перестановок, 586

в обобщенныых алгоритмах сравнения, 586

при сортировке строк, 366–75

литеральные константы, 76–78

C-строки

сравнение с символьными литералами, 114



f суффикс, 77

U суффикс, 76

с плавающей точкой, 77

логические встроенные операторы, 145–48

оператор ИЛИ (||), 146

оператор НЕ (!), 147

логические объекты-функции

logical_and, 572

logical_not, 572

logical_or, 572

локализация

влияние глобального объекта на, 349

константной переменной или объекта, 100

локальность объявления, 190, 385

на уровне файла, использование безымянного пространства имен, 419

локальная область видимости, 376, 378–81

try-блок, 535

доступ к членам в глобальной области видимости, скрытым за локальными объектами, 411

имена в пространстве имен, скрытые за локальными объектами, 414

переменная, неинициализированная, 388

разрешение имени, 379

локальные объекты, 388–92

проблема возврата ссылки на, 348

статические, 388, 390–92

М

массив(ы), 113–20

в сравнении с векторами, 122

динамическое выделение и освобождение, 400–402

массивов объектов классов, 691–94, 744–53

индексирование, 31, 113–16

многомерных массивов, 116–17

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

инициализация, 31, 114–15

динамически выделенных массивов, 400

динамически выделенных массивов объектов класса, 690–94

многомерных массивов, 116–17

недопустимость инициализации другим массивом, 115

использование оператора sizeof(), 159

как параметры функций, 335–39

для передачи нескольких параметров, 350

многомерные, 338

преобразование массива в указатель, 448

многомерные, 116–17

недопустимость использования auto_ptr, 395

недопустимость использования в качестве возвращаемого значения функции, 324

недопустимость присваивания другому массиву, 115

недопустимость ссылок на массив, 115

обход

с помощью манипуляции указателем, 118

с помощью пары итераторов, 263–64

объектов класса, 689–96

определение, 30, 113

перегруженный оператор

delete[], 749–51

new[], 749–51

поддержка обобщенными алгоритмами, 553

размер, не является частью типа параметра, 335

связь с типом указателей, 118–20

указателей на функции, 369–70

меньше, оператор

поддержка в арифметических типах данных, 30



требование о поддержке типом элементов контейнера, 259

минус(-)

для выделения опций в командной строке, 357

многоточие (...), 343–44

использование в типах функций, 367

множество (set), контейнерный тип

set, заголовочный файл, 304

size(), 307

обход, 306–7

ограничение на изменение порядка, 587

определени, 304–6

поиск элементов, 306

сравнение с отображением, 292

модели компиляции

с разделением, 834–37

шаблонов класса

с включением, 833

с разделением, 834–36

шаблонов классов, 831–38

шаблонов функций, 494–98

с включением, 494–95

с разделением, 495–97

Н

наилучшая из устоявших функций, 442

неинициализированный

автоматический объект, 388

глобальный объект, 382

локальный статический объект, 391

неоднозначность

перегруженных

функций, диагносцирование во время разрешения перегрузки, 454

указателя, стандартные преобразования, 456

шаблона функции

аргумента, разрешение с помощью явной спецификации, 492

конкретизации, ошибка, 484

конкретизация, опасность перегрузки, 505

неявные преобразования типов, 176

новая строка ()

как escape-последовательность, 77

О

область видимости, 376–81

видимость класса, 645–52

и определение класса, 594

разрешение имен в, 649–52

глобальная область видимости, 376

и время жизни (глава), 376–428

и перегрузка, 434–38

локальная область видимости, 378–81

обращение к скрытым членам глобальной области видимости, 411

разрешение имен в, 379

объявлений исключений в catch-обработчиках, 540

параметра шаблона

функции, 478–81

пространства имен, 376

управляющих переменных в инструкции for, 379

область видимости глобального пространства имен, 376, 406

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

обобщенные алгоритмы

(глава), 552–92

algorithm, заголовочный файл, 584

numeric, заголовочный файл, 584

алфавитный указатель (приложение), 1103–94

генерирования, 586

использование итераторов, 575–83

категории и описания, 583–87

когда не надо использовать, 587–92

модификации, 586

независимость от типа, 552, 553



нотация для диапазона элементов, 583

обзор, 552–56

объекты-функции как аргументы, 567

использование предопределенных объектов-функций, 569

перестановки, 586

подстановки, 585

пример использования, 556–66

работа с хипом, 587

сравнения, 586

удаления, 585

численные, 586

обработка исключений

bad_alloc, исключение нехватки памяти, 393

обратная косая черта (

как escape-символ, 280

как префикс escape-последовательности, 77

обратные итераторы, 578

обход

заполнение множества с помощью, 305

использование с контейнерами multimap и multiset, 309

множества, 306–7

невозможность обхода перечислений, 112

обход отображения, 303

отображения текста на вектор позиций, 298–301

параллельный обход двух векторов, 296

объединение

разновидность класса, 638–43

объект(ы)

автоматические, 388–89

объявление с ключевым словом register, 389–90

глобальные

и функции, 381–87

сравнение с параметрами и возвращаемыми значениями функций, 349–50

использование памяти, 82

локальные, 388–92

определение, 87

переменные как, 81

члены пространства имен, 407–8

объектное программирование, 593

объектно-ориентированное программирование

проектирование

(пример), 46–55

объекты-функции, 566–75

functional, заголовочный файл, 568

арифметические, 570

использование в обобщенных алгоритмах, 552

источники, 568

логические, 572

предопределенные, 568–70

преимущества по сравнению с указателями на функции, 567

реализация, 573–75

сравнительные, 571

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

адапторы функций для, 573

объявление

инструкция, 14

объявления

базового класса, виртуальное, 976–78

в части инициализации цикла for, 210

видимость имени, вводимого объявлением, 376

друзей, в шаблоне класса, 815–21

и определение, 382–83

инструкция, 189–92

исключения, 538

класса bitset, 167

объектов, 169

класса, сравнение с определением, 600–601

локальность, 190

перегруженное

оператора, 131

функции, 429

пространства имен, 407

сопоставление объявлений в разных файлах, 383

указателя на функцию, 366

включение спецификации исключений в, 548



функции, 322

задание аргументов по умолчанию, 341

как часть шаблона функции, 477

размещение в заголовочном файле, 385

функции-члена, перегруженное, 776–78

шаблона функции

определение используемых имен, 516

связь с определением, 515

требования к размещению явных объявлений конкретизации, 497

явная специализация, 499

явной конкретизации

шаблона класса, 837–38

шаблона функции, 497–98

одиночная кавычка (_)

как escape-последовательность, 77

однонаправленный итератор, 583

оператор "меньше"

характеристики и синтаксис, 146

оператор ввода, 27

оператор вывода, 1045

перегрузка, 1069. См. cout. См. cout

оператор вызова функции, 736–38

операторы

встроенные

(глава), 141–87, 141–87

sizeof, 159–62

арифметические, 142–45

бинарные, 141

декремента (--), 153–54

доступа к членам класса (. и ->), 607–8

запятая, 163

инкремента (++), 153–54

логические, 145–48

побитовые, 164–66

приоритеты, 171–74

равенства, 145–48

разрешения области видимости (

), 410–12

составного присваивания, 152

сравнения, 145–48

перегруженные

delete, 744–49

delete(), размещения, 751–53

delete[], 749–51

new, 744–49

new(), размещения, 751–53

new[], 749–51

взятия индекса ([]), 736

вопросы проектирования, 728–30

вызова функции (()), 736–38

вызова функции для объектов-функций, 567

декремента (--), 740–44

доступа к членам (->), 738–40

имена, 727–28

инкремента (++), 740–44

объявленные как друзья, 730–33

присваивания (=), 733–35

с параметрами-ссылками, преимущества, 335

члены и не-члены класса, 723–27

определения, 15

typedef, 126

базового класса, 871–75

иерархии классов, 862–69

исключений, как иерархий классов, 1013–14

класса, 594–601

сравнение с определением класса, 600–601

класса-диспетчера запросов (пример), 934–39

массива, 113

многомерных массивов, 116

множеств, 304–6

недопустимость размещения в заголовочном файле, 385

объекта, 382

объектов класса bitset, 169

объектов класса complex, 125

последовательных контейнеров, 256–61

производного класса, 876–78



пространств имен, 406–20

членов, 415–17

сравнение с объявлениями, 381–83

функции

и локальная область видимости, 378

как часть шаблона функции, 477

шаблона класса, 791–800

разрешение имен в, 844–46

опции

в командной строке, 356–65

отображения, 292–309

map, заголовочный файл, 293

заполнение, 293

невозможность переупорядочения, 587

недопустимость использования итераторов с произвольным доступом, 583

сравнение с множествами, 292

текста

заполнение, 292–98

определение, 292–98

отрицатели

как адапторы функций, 573

очереди, 315–16

queue, заголовочный файл, 315

size(), 315

top(), функция, 316

очереди с приоритетами, 315, 316

очередь с приоритетами, 315

size(), 315

top(), функция, 316

ошибки

assert(), макрос, 226

бесконечная рекурсия, 351

в инструкции if, 193

в циклах, 197

зацикливание, 93

висячие указатели, 389

как избежать, 394

динамического выделения памяти, 395

итератор, использование, 226

компиляции, конфликты в области видимости using-объявления, 437

массив

индекс за концом, 94

области видимости, подводные камни using-директивы, 426

оператор присваивания вместо оператора равенства, 100

порядка вычисления подвыражений, 142

проблема висячего else, 195

проблемы константных ссылок и указателей, 106

проблемы побитовых операторов, 166

проблемы, связанные с глобальными объектами, 349

пропуска

завершающего нуля в C-строке, 402

скобок при освобождении динамически выделенного массива, 402

редактора связей

повторные определения, 386

смещения на единицу при доступе к массиву, 31

фазы связывания при наличии объявления в нескольких файлах, 383

Ошибки

конкретизации шаблона функции, 484

П

память

утечка, 35

параметр(ы)

объявление, сравнение с объявлением исключений, 540

размер, важность для передачи по значению, 327

списки параметров

переменной длины, многоточие, 343

различия перегруженных функций, 431

ссылочные, 329–33

влияние на преобразования при разрешении перегрузки функции, 457

преимущества эффективности, 330, 540



ранжирование, 471

сравнение с параметрами-указателями, 333–35

шаблона

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

не являюшиеся типами, 476

являюшиеся типами, проверка, 325–26

параметры функций

аргументы по умолчаниюю, 340–43

использования многоточия, 343–44

массивы, 335–39

при разрешении перегруженных функций, 430

проверка типов, 325–26

списки параметров, 325

сравнение параметров указательного и ссылочного типов, 333–35

сравнение с глобальными объектами, 349–50

ссылки, 107, 329–33

использование для возврата нескольких значений, 197

на константы, 331

преимущества в эффективности, 330

сравнение с параметрами-указателями, 333–35

тип возвращаемого значения

тип pair, 197

указатели, 329

указатели на функции, 370–73

переменные

глобальные параметры и возвращаемые значения, 349–50

константные, 100

объявление как член пространства имен, 408

переносимость

знак остатка, 143

перестановки, обобщенные алгоритмы, 589

перечисления, 110–13

основания для включения в язык, 110

расширение типа при разрешении перегрузки функции, 452

точное соответствие при разрешении перегрузки функции, 445

по умолчанию

аргументы, 340–43

и виртуальные функции, 910–13

влияние на выбор устоявших функций, 472

и устоявшие функции, 472–73

конструктор, см. конструктор, 678–79

побитовый(е)

оператор И (&), 164

оператор И с присваиванием (&=), 152, 164

оператор ИЛИ (!), 165

оператор ИСКЛЮЧАЮЩЕЕ ИЛИ (^), 165

оператор НЕ (~), 164

оператор сдвига (<<,>>), 165

операторы, 164–66

поддержка в классе bitset, 170

повторное возбуждение

исключения, 542–43

позиция

разрешение аргумента по позиции в списке, 341

поиск

rfind(), 278

подстрок, 280

элементов

множества, 306

отображения текста, 298–99

ПОО (правило одного определения), 382, 416–18

последовательные контейнеры, 248–319

вставка элементов, 265

критерии выбора, 252

обобщенные алгоритмы, 269–70

определение, 256

перестановка элементов, 269

присваивание, 268

удаление элементов, 267

предостережения



использование знакового бита в битовых векторах, 166

неопределенность порядка вычисления бинарных операторов сравнения, 147

опасности приведения типов, 178

подводные камни

using-директивы, 426

возврата l-значение, 348

возврата ссылки на объект, 348

глобальные объекты, 349

приведения типов, 181

шаблона класса auto_ptr, 399

представление

влияние на расширение типа перечисления, 452

информация о реализации в заголовочном файле limits, 145

строк, 92

целых чисел,

143

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

bool в int, 109

l-значения в r-значение, 446–47

арифметическое, 177–78

бинарного объекта-функции в унарный, использование адаптора-связывателя, 573

выбор преобразования между типами классов, 764–76

выведение аргументов шаблона функции, 486

как точное соответствие при разрешении перегрузки функции, 459

квалификаторов

влияние на последовательность преобразований, 470

при выведении аргументов шаблона функции, 487

ранжирование при разрешении перегрузки функции, 470

конверторы, 445

конструкторы

конструкторы как конверторы, 761–64

множественные, разрешение неоднозначности приведения, 468

недопустимость преобразований между типами указателей на функции, 439

неявные преобразования типов, 176

определенное пользователем, 445

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

определенных пользователем преобразований, 764–67

определенных пользователем, ранжирование при разрешении перегрузки функций, 771–76

определенных пользователем, с учетом наследования, 1034–36

стандартных преобразований, 468–72

ранжирование инициализации ссылок при разрешении перегрузки функции, 457

расширения типа, 175

аргументов, 451–53

типа перечисления в арифметические типы, 112

с потерей точности, предупреждение компилятора, 326

стандартное, 453–57

типа аргумента, 444–60

трансформации l-значений, 450

трансформация I-значений

преобразования при выведении аргументов шаблона функции, 486

трансформация I-значения

ранжирование при разрешении перегрузки функции, 468

указателей

в тип void* и обратно, 179

преобразования квалификаторов, 449



стандартные преобразования указателей, 456

трансформации l-значений, массива в указатель, 448

трансформации l-значений, функции в указатель, 448

явные преобразования типов, 144, 175, 178

препроцессор

комментарий

парный(/**/), 25

константы

__cplusplus__, 23

макросы

шаблоны функций как более безопасная альтернатива, 474

предкомпилированные заголовочные файлы, 385

приведение(я), 144

const_cast, оператор, опасность применения,, 180

dynamic_cast (), оператор, 1001–7

dynamic_cast()

идентификация класса объекта во время выполнения, 182

reinterpret_cast

опасности, 181

reinterpret_cast, оператор, 181

static_cast

сравнение с неявными преобразованиями, 180

static_cast, оператор, 181

выбор конкретизируемого шаблона функции, 485

для принудительного установления точного соответствия, 450

опасности, 181

сравнение нового синтаксиса со старым, 182

старый синтаксис, 182–83

применение для подавления оптимизации, 127

примеры

класс IntArray, 45

IntSortedArray, производный класс, 54

класс iStack, 183–87

поддержка динамического выделения памяти, 316–17

преобразование в шаблон stack, 318–19

класс String, 128–39

класс связанного списка, 221–47

обработка аргументов в командной строке, 356–57

система текстового поиска

(глава 6), 248–319

функция sort, 365

шаблон класса Array, 55–62, 849–57

SortedArray, производный класс, 993–98

примитивные типы

(глава), 98–139

присваивание

векторам, сравнение с встроенными массивами, 122

и поведение auto_ptr, 397

комплексных чисел, 155

массиву, недопустимость присваивания другого массива, 115

оператор

и требования к l-значению, 81

перегруженный, 709–12, 733–35

составной, 152

последовательному контейнеру, 268–69

почленное для объектов класса, 709–12

ссылке, 107

указателю на функцию, 367

вопросы, связанные с перегруженностью функции, 439

проверка

выхода за границы диапазона, 289

не выолняется для массивов, 116

типа

назначение и опасности приведения типов, 182

неявные преобразования, 326

объявления, разнесенного по нескольким файлам, 384



отмена с помощью многоточия в списке параметров, 343

параметра, 325–27

сохранения в шаблоне функции, 476

указателя, 88

программа, 14–21

производительность

auto_ptr, 397

классы, локальность ссылок, 191

компиляции

зависимость от размера заголовочного файла, 385

при конкретизации шаблонов функций, 497

контейнеров

емкость, 255

компромиссы при выборе контейнера, 252

сравнение списка и вектора, 254

определения шаблона функции в заголовочном файле, 495

сравнение обработки исключений и вызовов функций, 550

ссылок

объявление исключений в catch-обработчиках, 540

параметры, 330

параметры и типы возвращаемых значений, 389

указателей на функции

проигрыш по сравнению с параметрами-ссылками, 540

проигрыш по сравнению со встроенными функциями, 559

сравнение с объектами-функциями, 567

функций

вопросы, связанные с возвращаемыми значениями, 324

накладные расходы на вызов рекурсивных функций, 351

недостатки, 352

передачи аргументов по значению, 328

преимущества встроенных функций, 133

производные классы

деструкторы, 896–99

конструирование, 889–96

почленная инициализация, 925–27

конструкторы, 892–93

определение

при виртуальном наследовании, 976–78

при множественном наследовании, 950–55

присваивание почленное, 927–28

пространства имен, 406–20

безымянные, 418–20

инкапсуляция сущностей внутри файлов, 419

отличие от других пространств имен, 419

вложенные, 412–14

и using-объявления,

435

объявления перегруженных функций внутри, 434–38

глобальное, 376

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

проблема загрязнения пространства имен, 406

область видимости, 376

std, 426–28

определения, 408–10

определенные пользователем, 407

псевдонимы, 420–21

члены

определения, 416

требование правила одного определения, 416–18

шаблоны функций, 521–24

процедурное программирование

(часть 3), 592–782

псевдоним(ы)

имен типов, typedef, 127

пространства имен, 66, 420–21

Р

равенство

оператор(ы), 145–48

потенциальная возможность выхода за границы, 116



разрешение перегрузки функции, 443

(глава), 429–73

выбор преобразования, 767

детальное описание процедуры, 460–73

наилучшая из устоявших функция, 453

для вызовов с аргументами типа класса, 771–76

и перегрузка, 468–72

ранжирование

последовательностей определенных пользователем преобразований, 1034–36

последовательностей стандартных преобразований, 468–72

устоявшие функции, 465–68

для вызовов операторных функций, 787–88

для вызовов функций-членов, 779–82

и аргументы по умолчанию, 472–73

и наследование, 1034–36

функции-кандидаты, 461–65

для вызовов в области видимости класса, 770–71

для вызовов операторных функций, 783–87

для вызовов с аргументами типа класса, 767–70

для вызовов функций-членов, 778

и наследование, 1031–34

явные приведения как указания компилятору, 451

разрешения области видимости оператор (

)

доступ к членам глобальной области видимости, 411

), 410–12

)

доступ к членам вложенного пространства имен, 412–14

Разрешения области видимости оператор (

)

доступ к шаблону функции как члену пространства имен, 524

разыменования оператор (*)

использование с возвращенным типом указателя, 367

как унарный оператор, 141

не требуется для вызова функции, 368

опасности, связанные с указателями, 333

приоритет, 118

ранжирование

определений шаблона функции, 505

последовательностей стандартных преобразований, 468–72

рассказ об Алисе Эмме, 250

и реализация класса string, 137

рекурсивные функции, 352

С

С, язык

символьные строки

использование итератора istream_iterator, 579

функции

указатели на функции, 373

связыватель

как класс адаптора функции, 573

сигнатура, 325

символ(ы)

литералы

синтаксис записи, 77

массив символов, инициализация, 114, 115

нулевой, для завершения строкового литерала, 78

символы

& (амперсанд)

оператор взятия адреса

использование в определении ссылки, 104

&& (двойной амперсанд)

оператор логического И, 146

символы

(двойное двоеточие)

оператор разрешения области видимости класса, 42



(двойное двоеточие)

оператор разрешения области видимости, 410–12

-- (двойной минус)

оператор декремента, 153, 740–44

- (минус)

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

! (восклицательный знак)

оператор "логическое НЕ"

вычисление, 147

характеристики и синтаксис, 145

% (процент)

оператор  деления по модулю, 142

оператор вычисления остатка, характеристики и синтаксис, 143

%= (процент равно)

оператор вычисления остатка с присваиванием, 152

& (амперсанд)

оператор взятия адреса

использование с именем функции, 164

как унарный оператор, 141

оператор побитового И, 164

&& (двойной амперсанд)

оператор логического И, 142

&= (амперсанд равно)

оператор побитового И с присваиванием, 164

как оператор составного присваивания, 152

() (круглые скобки)

использование оператора вызова для передачи объекта-функции, 567

оператор вызова, перегрузка в объектах-функциях, 559

(обратная косая черта a)

escape-последовательность "звонок", 77

(обратная косая черта n)

escape-последовательность "новая строка", 77

(обратная косая черта v)

escape-последовательность "вертикальная табуляция", 77

(обратная косая черта знак вопроса)

escape-последовательность "знак вопроса", 77

(обратная косая черта)

как escape-символ, 280

* (звездочка)

оператор разыменования

доступ к объектам с помощью, 89

использование для задания типа возвращаемого значения, 366

как унарный оператор, 141

не требуется для вызова функции, 368

определение указателей с помощью, 87

приоритет, 118

оператор умножения

характеристики и синтаксис, 142

*= (звездочка равно)

оператор умножения с присваиванием, 152

, (запятая)

неправильное применение для индексации массива, 117

оператор, 163

. (точка)

оператор "точка", 38

... (многоточие), 343–44

для обозначения универсального catch-обработчика, 544

использование в типах функций, 367

/ (косая черта)

оператор деления

характеристики и синтаксис, 142



/= (косая черта равно)

оператор деления с присваиванием, 152

; (точка с запятой)

для завершения инструкций, 188

?: (знак вопроса двоеточие)

условный оператор, 133, 158

сокращенная запись if-else, 199

[,) (левая квадрнатная, правая круглая скобки)

для обозначения интервала с включенной левой границей, 583

[] (квадратные скобки)

для динамического выделения памяти под массив, 400

для освобождения выделенной под массив памяти, 402

оператор взятия индекса

для доступа к вектору, 121

для проверки битов в битовом векторе, 168

инициализация отображения с помощью, 294

не поддерживается для контейнеров multiset и multimap, 312

оператор взятия индекса, 736

оператор индексирования массива, перегрузка в определении класса массива, 45

\\ " (обратная косая черта двойная кавычка)

escape-последовательность двойной кавычки, 77

\\ (двойная обратная косая черта)

escape-последовательность "обратная косая черта", 77

\\t (обратная косая черта t)

escape-последовательность горизонтальнаятабуляция, 77

^ (крышка)

оператор побитового ИСКЛЮЧАЮЩЕГО ИЛИ, 164

^= (крышка равно)

оператор побитового ИСКЛЮЧАЮЩЕГО ИЛИ с присваиванием, 164

как оператор составного присваивания, 152

__STDC__, 23

_обратная косая черта одиночная кавычка)

escape-последовательность "одиночная кавычка", 77

{} (фигурные скобки)

использование в объявлениях пространств имен, 408

использование в предложении catch, 536

использование в составной директиве связывания, 354

как ограничители составной инструкции, 188

при инициализации вложенного массива, 117

| (вертикальная черта)

оператор побитового ИЛИ, 164

|| (двойная вертикальная черта)

оператор логического  ИЛИ

характеристики и синтаксис, 145

оператор логического ИЛИ

вычисление, 146

|=  (вертикальная черта равно)

оператор побитового ИЛИ с присваиванием, 164

как оператор составного присваивания, 152

~ (тильда)

оператор побитового НЕ, 164

+ (плюс)

оператор сложения

поддержка в арифметических типах данных, 30



++ (двойной плюс)

оператор инкремента, 153, 740–44

+= (плюс равно)

оператор сложения с присваиванием, 146

+= (плюс равно)оператор сложения с присваиванием

как оператор составного присваивания, 152

< (левая угловая скобка)

оператор "меньше"

вопросы поддержки, 566

использование при сортировке по длине, 558

перегруженный оператор в определении контейнера, 259

<< (двойная левая угловая скобка)

оператор вывода, 26

оператор сдвига влево, 164

<<=(двойная левая угловая скобка равно)

оператор левого сдвига с присваиванием, 152

<> (угловые скобки)

явный шаблон

применение в специализациях, 499

спецификации аргументов, 490

-= (минус равно)

оператор вычитания с присваиванием, 152

= (равно)

оператор присваивания, 100, 733–35

и l-значение, 81

использование с объектами классов, 39

использование с псевдонимами пространств имен, 420

== (двойное равно)

оператор равенства, 100

поддержка в арифметических типах данных, 30

оператор равенства, необходимость наличия в определении контейнера, 259

-> (минус правая угловая скобка)

оператор "стрелка"

перегруженный оператор доступа к членам, 740

>> (двойная правая угловая скобка)

оператор ввода, 1051–63

перегрузка. cin. cin

оператор сдвига вправо, 164

>>=(двойная правая угловая скобка равно)

оператор правого сдвига с присваиванием, 152

символы:, 77

сложения (+) оператор

комплексных чисел, 155

сокрытие информации, 39, 598

вопросы, связанные с вложенными пространствами имен, 414

доступ к

закрытым членам класса, 607

имена в локальной области видимости, 378

объявление члена пространства имен, обход с помощью оператора разрешения области видимости, 411

параметры шаблона, имена в глобальной области видимости, 478

сравнение с перегрузкой, 434

во вложенных областях видимости, 461

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

составные

выражения, 142

инструкции, 188–89

директивы связывания, 354



присваивания

оператор, 152

операторы над комплексными числами, 156

состояния условий

в применении к библиотеке iostream, 1086–88

спецификации

явные, аргументов шаблона функции, 490

списки

list, заголовочный файл, 256

merge(), обобщенный алгоритм

специализированная реализация для списка, 588

push_front(), поддержка, 257

size(), 221

влияние размера объекта на производительность, 254

как последовательный контейнер, 256–61

неприменимость итераторов с произвольным доступом, 583

неприменимость обобщенных алгоритмов, требующих произвольного доступа, 588

обобщенные, 241–47

поддержка операций merge() и sort(), 269

сравнение с векторами, 251–52

требования к вставке и доступу, 252

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

использование многоточия, 343

сравнения

объекты-функции, 571

операторы, 145–48

поддержка в контейнерах, 258

ссылки

для объявления исключения в catch-обработчике, 543

инициализация

как преобразование точного соответствия, 457–59

ранжирование при разрешении перегрузки функции, 471–72

ссылки на const, 105–8

использование с sizeof(), 161

как тип возвращаемого значения функции, 348

недопустимость массив ссылок, 115

параметры-ссылки, 107, 329–33

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

преимущества эффективности, 330

парамтеры-ссылки

по сравнению с параметрами-указателями, 333–35

сравнение с указателями, 104

статические объекты

объявление локальных объектов как, 390–92

объявление, сравнение с безымянным пространством имен, 419

статические члены класса

указатели на, 636–37

статическое выделение памяти, 33

стек, контейнерный тип, 312–15

stack, заголовочный файл, 312

top(), функция, 154, 313

динамическое выделение памяти, 317

операции (таблица), 313

реализация с помощью контейнера deque, 314

стека, пример класса, 183–87, 183–87

строки

append(), 287–88

assign(), 287

compare(), 289

erase(), 267, 285

insert(), 266

replace(), 290–91

swap(), 268, 288

поиск подстроки, 273–79, 285–86, 290

присваивание, 266

Т

тело



функции, 321

тип

точное соответствие, 445–51

тип(ы)

bool, 108–10

C-строка, 92–95

typedef, синоним типа, 126

арифметические, 30–33

базовые

(глава), 98–139

для определения нескольких объектов одного и того же типа pair, 128

имя класса как, 595

использование с директивой препроцессора include, 68

поверка

назначение и опасности приведения, 182

проверка

неявные преобразования, 326

объявления в нескольких файлах, 384

подавление, многоточие в списке параметров функции, 343

сравнение, функция strcmp(), 133

С-строка

динамическое выделение памяти, 401

точка конкретизации

шаблона функции, 518

точное соответствие, 445–51

У

угловые скобки (<>)

шаблон

использование для определения, 56

спецификации аргументов, 490

явные

специализации шаблона, 498

спецификации аргументов шаблона, 490

указатели, 87–90

sizeof(), использование с, 161

void*, 89

преобразование в тип void* и обратно, 179

адресация

C-строк, 92

объектов, 89

объектов класса, использование оператора ->, 603

элементов массива, 118

вектор указателей, преимущества, 255

висячий

возвращенное значение, указывающее на автоматический объект, 389

указывающий на освобожденную память, 394

использование в обобщенных алгоритмах, 120

как значение, возвращаемое функцией, 370

как итераторы для встроенного массива, 264

константные указатели, 101

на константные объекты, 101

нулевой указатель, 455

как операнд оператора delete, 394

параметры, 329, 334

сравнение с параметрами-ссылками, 333–35

сравнение с массивами, 118–20

сравнение со ссылками, 43, 106

указатели на функции, 365–75

вызов по, 368–69

и спецификации исключений, 548–50

инициализация, 367

как возвращаемые значения, 370–73

как параметры, 370–73

массивы, 369–70

на перегруженные функции, 439–40

на функции, объявленные как extern "C", 373–75

написанные на других языках, 374

недостатки по сравнению со встроенными функциями, 559

присваивание, 367

сравнение с указателями на данные (сноска), 87

указатели на члены, 628–38



указатели на данные-члены, 634

указатели на статические члены, 636–38

указатели на функции-члены, 632

умножения оператор (*)

поддержка в арифметических типах данных, 30

унарные операторы, 141

условный

директивы препроцессора, 21

инструкции

if, 192–98

инструкция

switch, 201–3

оператор (?

)

сравнение с функциями, 352

оператор (?:), 133

сокращение для if-else, 199

условный оператор

инструкция, 188

Ф

файл(ы)

ввод/вывод, 28–29

входной

открытие, 28

выходной

открытие, 29

несколько

размещение определения пространства имен в, 410

сопоставление объявлений в, 383

объявления локальных сущностей

использование безымянного пространства имен, 419

фигурные скобки ({})

использование в объявлениях пространств имен, 408

использование в предложении catch, 535

использование в составной директиве связывания, 354

как ограничители составной инструкции, 188

при инициализации вложенного массива, 117

функции

(глава), 320–75

function, заголовочный файл, 568

try-блок, 536

возвращаемые значения, 346–50

локальный объект, проблема возвращения ссылки на, 348

объект класса, 348–50

объект класса как средство вернуть несколько значений, 350

параметр-ссылка как средство возврата дополнительного значения, 329

сравнение с глобальными объектами, 349–50

указатель на функцию, 372

вызовы, 322

заключенные в try-блок, 536

недостатки, 352

сравнение с обработкой исключений, 542

и глобальные объекты, 381–87

и локальная область видимости, 378

имя функции

перегрузка, 429

преобразуется в указатель, 367

интерфейс

объявление функции как, 323

прототип функции как описание, 323

конверторы, 757–61

конструкторы как, 761–64

локальное хранение, 327

на другом языке, директивы связывания, 353–55

обращение к, 322

объявления

как часть шаблона функции, 477

как члена пространства имен, 407

сравнение с определениями, 382

объявления перегруженных функций, 429–32

и область видимости, 434–38

как перегружаются, 429–32

когда не надо перегружать, 432–34

причины для перегрузки функций, 429



оператор вызова функции (()), 736–38

определение, 321

как часть шаблона функции, 477

сравнение с объявлениями, 382

преимущества, 352

преобразование функции в указатель, 448

прототип, 323–27

рекурсивные, 350–52

сигнатура, 325

списки параметров, 325

тип

недопустимость возврата из функции, 324

преобразование в указатель на функцию, 347

тип возвращаемого значения, 324–25

недопустимость указания для конструкторов, 671

недостаточен для разрешения перегруженных функций, 431

ссылка, 348

указатель на функцию, 370–73

функции-кандидаты, 442, 460–65

вызов с аргументами типа класса, 767–70

для вызовов в области видимости класса, 770–71

для вызовов функций-членов, 778

для перегруженных операторов, 783–87

для шаблонов функций,, 507

наследование и, 1031–34

функции-члены, 129, 596–98, 604–14

встроенные функции

сравнение с не-встроенными, 605–7

вызов, 131

модификация для обработки исключений, 531

независимые от типа, 50

определение, 132

открытые

доступ к закрытым членам с помощью, 40

сравнение с закрытыми, 608–10

перегруженные

и разрешение, 776–82

объявление, 777–78

проблемы, 434

функции-кандидаты, 778

специальные, 610–11

статические, 626–27

устоявшие, перегрузка и, 779–82

Х

хип, 162, 392, 587

выделение памяти для классов в, 749–51

выделение памяти для массива в, 400

выделение памяти для объекта в, 392

исключение bad_alloc, 393

обобщенные алгоритмы, 587, 1191

См. также обобщенные алгоритмы, 1192

Ц

целые

константы, перечисления как средство группировки, 110

расширение булевских константы до целых, 146

расширение типа, 177

стандартные преобразования, 177

при разрешении перегрузки функции, 453

типы данных, 75

цикл(ы), 20

завершение

break, инструкция, 218

continue, инструкция, 219

инструкции

for, 196

while, 213–16

инструкции

do-while, 216–17

for, 209–13

while, 21

ошибки программирования, 198

бесконечные циклы, 274

условие останова, 32

Ч

числа с плавающей точкой

арифметика, характеристики и смежные темы, 145

правила преобразования типов, 177



стандартные преобразования при разрешении перегрузки функции, 453

численные обобщенные алгоритмы, 586

numeric, заголовочный файл, 586

читабельность

typedef, 126

в объявлениях указателей на функции, 369

как синоним контейнерных типпов, 295

имен параметров, 325

имен перегруженных функций, 432

квалификатор const для объявления констант, 100

параметров-ссыслок, 335

разделение обработчиков исключений, 534

рекурсивных функций, 351

члены класса

this

использование в перегруженном операторе присваивания, 710

когда использовать в функциях-членах, 619–21

указатель this, 616–20

битовые поля, 643–45

данные-члены, 594–96

защищенные, 871

изменчивые (mutable), 614–16

статические, 621–25

тип члена, 631–36

доступ, 599–600, 607–8

друзья, 599–600

статические, 621–28

функции-члены, 596–98, 604–16

встроенные и не-встроенные, 605–7

закрытые и открытые, 608–10

конверторы, 757–61

перегруженные, объявления, 776–78

специальные функции-члены, 610–11

спецификации исключений для, 1021–24

статические, 626–28

тип члена, 631–33

члены-классы

открытые и закрытые, 598–99

шаблоны, 826–31

Ш

шаблон класса Array

Array_RC, производный класс, 990–92

шаблоны классов

(глава), 791–857

вложенные типы, 824–26

и пространства имен, 846–48

модели компиляции, 831–38

с включением, 833

с разделением, 834–37

объявления друзей в, 815–21

определения, 791–800

разрешение имен в, 844–46

параметры, 794–97, 805–11

параметры-константы, 805–11

параметры-типы, 800–805

статические члены классов, 821–24

точка конкретизации, для функций-членов, 846

частичные специализации, 842–44

члены

функций, 811–15

явные

объявления конкретизации, 837–38

специализации, 838–42

шаблоны функций

(глава), 592–782

и пространства имен, 521–24

конкретизации, 592–782

модели компиляции, 494–98

с включением, 494–95

с разделением, 495–97

определение, 474–82

параметры, 475–82

для повышения гибкости обобщенных алгоритмом, 566

параметры-константы, 476

параметры-типы, 476

перегрузка, 503–6



передача объектов-функций шаблону, 569

разрешение имен в определениях, 514–20

разрешение перегрузки при конкретизации, 506–14

тип возвращаемого значения и выведение аргументов шаблона, 491

точка конкретизации, 518

явные

аргументы, 490–93

объявления конкретизации, 497–98

спецаиализации, 498–503

Э

эффективность

сравнение с гибкостью при выделении памяти, 33

Я

явное

преобразование, 178–82

преобразование типа, 144, 175

[1]

Во время написания этой книги не все компиляторы С++ поддерживали пространства имен. Если ваш компилятор таков, откажитесь от данной директивы. Большинство программ, приводимых нами, используют компиляторы, не поддерживающие пространство имен, поэтому директива using

в них отсутствует.

[2]

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

[3] Объявление функции inline – это всего лишь подсказка компилятору. Однако компилятор не всегда может сделать функцию встроенной, существуют некоторые ограничения. Подробнее об этом сказано в разделе 7.6.

[4] Вот как выглядит общее решение этой проблемы:

Example2( elemType nval = elemType() ) " _val( nval ) {}

[5]

На самом деле для указателей на функции это не совсем так: они отличаются от указателей на данные (см. раздел 7.9).

[6] STL расшифровывается как Standard Template Library. До появления стандартной библиотеки С++ классы vector, string и другие, а также обобщенные алгоритмы входили в отдельную библиотеку с названием STL.

[7]

Проверку на неравенство 0 можно опустить. Полностью эквивалентна приведенной и более употребима следующая запись: ptr && *ptr.

[8]

До принятия стандарта языка С++ видимость объектов, определенных внутри круглых скобок for, простиралась на весь блок или функцию, содержащую данную инструкцию. Например, употребление двух циклов for внутри одного блока

{

    // верно для стандарта С++

    // в предыдущих версиях C++ - ошибка:

ival определена дважды



    for (int ival = 0; ival < size; ++iva1 ) // ...

    for (int ival = size-1; ival > 0; ival ) // ...

}

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

[9]

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

(War || Rights)

Civil&&(War||Rights)

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

[10]

Иллюстрация Елены Дрискилл (Elena Driskill).

[11]

Отметим, что deque не поддерживает операцию reserve()

[12]

Существующие на сегодняшний день реализации не поддерживают шаблоны с  параметрами по умолчанию. Второй параметр – allocator – инкапсулирует способы выделения и освобождения памяти. В С++ он имеет значение по умолчанию, и его задавать не обязательно. Стандартная реализация использует операторы new и delete. Применение распределителя памяти преследует две цели: упростить реализацию контейнеров путем отделения всех деталей, касающихся работы с памятью, и позволить программисту при желании реализовать собственную стратегию выделения памяти. Определения объектов для компилятора, не поддерживающего значения по умолчанию параметров шаблонов,  выглядят следующим образом:

vector< string, allocator > svec;

list< int, allocator >      ilist;

[13]

Если функция-член push_front()

используется часто, следует применять тип deque, а не vector: в deque

эта операция реализована наиболее эффективно.

[14]

Последняя форма insert()

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



[15]

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

vector<string,allocator> *lines_of_text;

Для компилятора, полностью соответствующего стандарту С++, достаточно отметить тип элементов:

vector<string> *lines_of_text;

[16]

Конечно, в английском языке существуют исключения из правил. Наш эвристический алгоритм превратит crises

(множ. число от crisis – прим. перев.) в cris. Ошибочка!

[17] Таким образом, как мы видим, определения встроенных функций могут встретиться в программе несколько раз! – Прим. ред.

[18]

Полный текст реализации класса CommandOpt можно найти на Web-сайте издательства Addison-Wesley.

1. Если имеющийся у Вас компилятор пока не поддерживает параметр шаблонов по умолчанию, то конструктору istream_iterator необходимо будет явно передать также и второй аргумент: тип difference_type, способный хранить результат вычитания двух итераторов контейнера, куда помещаются элементы. Например, в разделе 12.2 при изучении программы, которая должна транслироваться компилятором, не поддерживающим параметры шаблонов по умолчанию, мы писали:

typedef vector<string,allocator>::difference_type diff_type

istream_iterator< string, diff_type > input_set1( infile1 ), eos;

istream_iterator< string, diff_type > input_set2( infile2 );

Если бы компилятор полностью удовлетворял стандарту C++, достаточно было бы написать так:

istream_iterator< string > input_set1( infile1 ), eos;

istream_iterator< string > input_set2( infile2 );

1 Более подробное обсуждение этой темы с примерами и приблизительными оценками производительности см. в [LIPPMAN96a].

2 В реальной программе мы объявили бы член _name как имеющий тип string. Здесь он объявлен как C-строка, чтобы отложить рассмотрение вопроса об инициализации членов класса до раздела 14.4.

3 Для тех, кто раньше программировал на C: приведенное выше определение класса Account на C выглядело бы так:



typedef struct {

   char         *_name;

   unsigned int _acct_nmbr;

   double       _balance;

} Account;

4 См. статью Джерри Шварца в [LIPPMAN96b], где приводится дискуссия по этому поводу и описывается решение, остающееся пока наиболее распространенным.

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

explicit vector( size_type n, const T& value=T(), const Allocator&=Allocator());

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

2 В объявлении унаследованной виртуальной функции, например eval(), в производном классе ключевое слово virtual необязательно. Компилятор делает правильное заключение на основе сравнения с прототипом функции.

3 Увы! Правые скобки не распознаются, пока OrQuery не выведет все ассоциированное с ним частичное решение.

[19]

Полный текст программы можно найти на FTP-сайте издательства Addison-Wesley по адресу, указанному на задней стороне обложки.  

1 Здесь есть потенциальная опасность появления висячей ссылки, если пользователь сохранит адрес какого-либо элемента исходного массива перед тем, как grow() скопирует массив в новую область памяти. См. статью Тома Каргилла в [LIPPMAN96b].

1 Кроме того, программист может устанавливать и сбрасывать флаги состояния формата с помощью функций-членов setf() и unsetf(). Мы их рассматривать не будем; исчерпывающие ответы на вопросы, относящиеся к этой теме,  можно получить в [STROUSTRUP97].

 [O.A.1]Как должны быть оформлены ссылки на книги, указанные в библиографии? Пришлите ваши пожелания.

 [O.A.2]Нумерация сносок сбита, как и вся остальная. Необходима проверка.

 [O.A.3]Нумерация сносок сбита.

 [O.A.4]Нумерация сносок сбита.

 [O.A.5]Нумерация сносок сбита.

 [O.A.6]Нумерация сносок сбита.


Алгоритм stable_partition()


template< class BidirectionalIterator, class Predicate >

BidirectionalIterator

stable_partition( BidirectionalIterator first,

                  BidirectionalIterator last,

                  Predicate pred );

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

#include <algorithm>

#include <vector>

#include <iostream.h>

/* печатается:

   исходная последовательность:

   29 23 20 22 17 15 26 51 19 12 35 40

   устойчивое разбиение по четным элементам:

   20 22 26 12 40 29 23 17 15 51 19

   устойчивое разбиение по элементам, меньшим 25:

   23 20 22 17 15 19 12 29 26 51 35 40

*/

          

class even_elem {

public:

           bool operator()( int elem ) {

          return elem%2 ? false : true;

           }

};

          

int main()

{

           int ia[] = { 29,23,20,22,17,15,26,51,19,12,35,40 };

           vector< int, allocator > vec( ia, ia+12 );

     ostream_iterator< int >  ofile( cout, " " );

                 

     cout << "исходная последовательность:\n";

     copy( vec.begin(), vec.end(), ofile ); cout << '\n';

           stable_partition( &ia[0], &ia[12], even_elem() );

     cout << "устойчивое разбиение по четным элементам:\n";

     copy( ia, ia+11, ofile ); cout << '\n';

           stable_partition( vec.begin(), vec.end(),

                       bind2nd(less<int>(),25)  );

     cout << "устойчивое разбиение по элементам, меньшим 25:\n";

     copy( vec.begin(), vec.end(), ofile ); cout << '\n';

}



Алгоритм stable_sort()


template< class RandomAccessIterator >

void

stable_sort( RandomAccessIterator first,

      RandomAccessIterator last );

template< class RandomAccessIterator, class Compare >

void

stable_sort( RandomAccessIterator first,

      RandomAccessIterator last, Compare comp );

stable_sort()

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

#include <algorithm>

#include <vector>

#include <iostream.h>

/* печатается:

   исходная последовательность:

   29 23 20 22 12 17 15 26 51 19 12 23 35 40

   устойчивая сортировка - по умолчанию в порядке возрастания:

   12 12 15 17 19 20 22 23 23 26 29 35 40 51

   устойчивая сортировка: в порядке убывания:

   51 40 35 29 26 23 23 22 20 19 17 15 12 12

*/

          

int main()

{

           int ia[] = { 29,23,20,22,12,17,15,26,51,19,12,23,35,40 };

           vector< int, allocator > vec( ia, ia+14 );

     ostream_iterator< int >  ofile( cout, " " );

     cout << "исходная последовательность:\n";

     copy( vec.begin(), vec.end(), ofile ); cout << '\n';

           stable_sort( &ia[0], &ia[14] );

     cout << "устойчивая сортировка - по умолчанию "

          << "в порядке возрастания:\n";

     copy( ia, ia+14, ofile ); cout << '\n';

                 

           stable_sort( vec.begin(), vec.end(), greater<int>() );

           cout << "устойчивая сортировка: в порядке убывания:\n";

           copy( vec.begin(), vec.end(), ofile ); cout << '\n';

}



Алгоритм swap()


template< class Type >

void

swap ( Type &ob1, Type &ob2 );

swap()

обменивает значения объектов ob1 и ob2.

#include <algorithm>

#include <vector>

#include <iostream.h>

/* печатается:

   исходная последовательность:

   3 4 5 0 1 2

   после применения swap() в процедуре пузырьковой сортировки:

   0 1 2 3 4 5

*/

          

int main()

{

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

           vector< int, allocator > vec( ia, ia+6 );

                 

           for ( int ix = 0; ix < 6; ++ix )

                  for ( int iy = ix; iy < 6; ++iy ) {

                         if ( vec[iy] < vec[ ix ] )

                              swap( vec[iy], vec[ix] );

                  }

           ostream_iterator< int >  ofile( cout, " " );

     cout << "исходная последовательность:\n";

     copy( ia, ia+6, ofile ); cout << '\n';

     cout << "после применения swap() в процедуре "

          << "пузырьковой сортировки:\n";

     copy( vec.begin(), vec.end(), ofile ); cout << '\n';

}



Алгоритм swap_ranges()


template< class ForwardIterator1, class ForwardIterator2 >

ForwardIterator2

swap_ranges( ForwardIterator1 first1, ForwardIterator1 last,

             ForwardIterator2 first2 );

swap_ranges()

обменивает элементы из диапазона [first1,last) с элементами другого диапазона, начиная с first2. Эти последовательности могут находиться в одном контейнере или в разных. Поведение программы не определено, если они находятся в одном контейнере и при этом частично перекрываются, а также в случае, когда вторая последовательность короче первой. Алгоритм возвращает итератор, указывающий на элемент за последним переставленным.

#include <algorithm>

#include <vector>

#include <iostream.h>

          

/* печатается:

   исходная последовательность элементов первого контейнера:

   0 1 2 3 4 5 6 7 8 9

   исходная последовательность элементов второго контейнера:

   5 6 7 8 9

   массив после перестановки двух половин:

   5 6 7 8 9 0 1 2 3 4

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

   5 6 7 8 9 5 6 7 8 9

   второй контейнер после перестановки двух векторов:

   0 1 2 3 4

*/

int main()

{

           int ia[]  = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };

           int ia2[] = { 5, 6, 7, 8, 9 };

                 

           vector< int, allocator > vec( ia, ia+10 );

           vector< int, allocator > vec2( ia2, ia2+5 );

           ostream_iterator< int >  ofile( cout, " " );

                 

     cout << "исходная последовательность элементов первого контейнера:\n";

     copy( vec.begin(), vec.end(), ofile ); cout << '\n';

           cout << "исходная последовательность элементов второго контейнера:\n";

     copy( vec2.begin(), vec2.end(), ofile ); cout << '\n';

           // перестановка внутри одного контейнера

           swap_ranges( &ia[0], &ia[5], &ia[5] );

     cout << "массив после перестановки двух половин:\n";

     copy( ia, ia+10, ofile ); cout << '\n';

           // перестановка разных контейнеров

           vector< int, allocator >::iterator last =

     find( vec.begin(), vec.end(), 5 );

           swap_ranges( vec.begin(), last, vec2.begin() );

     cout << "первый контейнер после перестановки двух векторов:\n";

     copy( vec.begin(), vec.end(), ofile ); cout << '\n';

     cout << "второй контейнер после перестановки двух векторов:\n";

     copy( vec2.begin(), vec2.end(), ofile ); cout << '\n';

}



Алгоритм transform()


template< class InputIterator, class OutputIterator,

          class UnaryOperation >

OutputIterator

transform( InputIterator first, InputIterator last,

           OutputIterator result, UnaryOperation op );

template< class InputIterator1, class InputIterator2,

          class OutputIterator, class BinaryOperation >

OutputIterator

transform( InputIterator1 first1, InputIterator1 last,

           InputIterator2 first2, OutputIterator result,

           BinaryOperation bop );

Первый вариант transform()

генерирует новую последовательность, применяя операцию op к каждому элементу из диапазона [first,last). Например, если есть последовательность {0,1,1,2,3,5} и объект-функция Double, удваивающий свой аргумент, то в результате получим {0,2,2,4,6,10}.

Второй вариант генерирует новую последовательность, применяя бинарную операцию bop к паре элементов, один из которых взят из диапазона [first1,last1), а второй – из последовательности, начинающейся с first2. Поведение программы не определено, если во второй последовательности меньше элементов, чем в первой. Например, для двух последовательностей {1,3,5,9} и {2,4,6,8} и объекта-функции AddAndDouble, которая складывает два элемента и удваивает их сумму, результатом будет {6,14,22,34}.

Оба варианта transform()

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

#include <algorithm>

#include <vector>

#include <math.h>

#include <iostream.h>

/*

* печатается:

  исходный массив: 3 5 8 13 21

  преобразование элементов путем удваивания: 6 10 16 26 42

  преобразование элементов путем взятия разности: 3 5 8 13 21

*/

          

int double_val( int val ) { return val + val; }

int difference( int val1, int val2 ) {

    return abs( val1 - val2 ); }

          

int main()

{

           int ia[]  = { 3, 5, 8, 13, 21 };

           vector<int, allocator> vec( 5 );

           ostream_iterator<int> outfile( cout, " " );

           cout << "исходный массив: ";

           copy( ia, ia+5, outfile ); cout << endl;

     cout << "преобразование элементов путем удваивания: ";

           transform( ia, ia+5, vec.begin(), double_val );

           copy( vec.begin(), vec.end(), outfile ); cout << endl;

                 

     cout << "преобразование элементов путем взятия разности: ";

           transform( ia, ia+5, vec.begin(), outfile, difference );

           cout << endl;

}



Алгоритм unique()


template< class ForwardIterator >

ForwardIterator

unique( ForwardIterator first,

        ForwardIterator last );

template< class ForwardIterator, class BinaryPredicate >

ForwardIterator

unique( ForwardIterator first,

        ForwardIterator last, BinaryPredicate pred );

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

будет преобразовано в misisipi. Обратите внимание, что три буквы 'i' не являются соседними, поэтому они не заменяются одной, как и две пары несоседних 's'. Если нужно, чтобы все одинаковые элементы были заменены одним, придется сначала отсортировать контейнер.

На самом деле поведение unique()

интуитивно не совсем очевидно и напоминает remove(). В обоих случаях размер контейнера не изменяется: каждый уникальный элемент помещается в очередную позицию, начиная с first.

В нашем примере физически будет получено слово misisipippi, где ppi – остаток, “отходы” алгоритма. Возвращаемый итератор указывает на начало этого остатка и обычно передается алгоритму erase() для удаления ненужных элементов. (Поскольку для встроенного массива операция erase() не поддерживается, то лучше воспользоваться алгоритмом unique_copy().)



Алгоритм unique_copy()


template< class InputIterator, class OutputIterator >

OutputIterator

unique_copy( InputIterator first, InputIterator last,

             OutputIterator result );

template< class InputIterator, class OutputIterator,

          class BinaryPredicate >

OutputIterator

unique_copy( InputIterator first, InputIterator last,

             OutputIterator result, BinaryPredicate pred );

unique_copy()

копирует входной контейнер в выходной, заменяя группы одинаковых соседних элементов на один элемент с тем же значением. О том, что понимается под равными элементами, говорилось при описании алгоритма unique(). Чтобы все дубликаты были гарантированно удалены, входной контейнер необходимо предварительно отсортировать. Возвращаемый итератор указывает на элемент за последним скопированным.

#include <algorithm>

#include <vector>

#include <string>

#include <iterator>

#include <assert.h>

          

template <class Type>

void print_elements( Type elem ) { cout << elem << " "; }

void (*pfi)( int ) = print_elements;

void (*pfs)( string ) = print_elements;

int main()

{

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

           vector<int,allocator> vec( ia, ia+10 );

           vector<int,allocator>::iterator vec_iter;

                 

           // последовательность не изменяется: нули не стоят рядом

     // печатается: 0 1 0 2 0 3 0 4 0 5

           vec_iter = unique( vec.begin(), vec.end() );

           for_each( vec.begin(), vec.end(), pfi ); cout << "\n\n";

                 

           // отсортировать вектор, затем применить unique: модифицируется

     // печатается: 0 1 2 3 4 5 2 3 4 5

           sort( vec.begin(), vec.end() );

           vec_iter = unique( vec.begin(), vec.end() );

     for_each( vec.begin(), vec.end(), pfi ); cout << "\n\n";

                 

           // удалить из контейнера ненужные элементы

     // печатается: 0 1 2 3 4 5

           vec.erase( vec_iter, vec.end() );

     for_each( vec.begin(), vec.end(), pfi ); cout << "\n\n";

           string sa[] = { "enough", "is", "enough",

                     "enough", "is", "good" };

           vector<string,allocator> svec( sa, sa+6 );

           vector<string,allocator> vec_result( svec.size() );

           vector<string,allocator>::iterator svec_iter;

     sort( svec.begin(), svec.end() );

           svec_iter = unique_copy( svec.begin(), svec.end(),

                              vec_result.begin() );

                 

     // печатается: enough good is

     for_each( vec_result.begin(), svec_iter, pfs );

           cout << "\n\n";

}



Алгоритм upper_bound()


template< class ForwardIterator, class Type >

ForwardIterator

upper_bound( ForwardIterator first,

             ForwardIterator last, const Type &value );

template< class ForwardIterator, class Type, class Compare >

ForwardIterator

upper_bound( ForwardIterator first,

             ForwardIterator last, const Type &value,

             Compare comp );

upper_bound()

возвращает итератор, указывающий на последнюю позицию в отсортированной последовательности [first,last), в которую еще можно вставить значение value, не нарушая упорядоченности. Значения всех элементов, начиная с этой позиции и далее, будут больше, чем value. Например, если дана последовательность:

int ia[] = {12,15,17,19,20,22,23,26,29,35,40,51};

то обращение к upper_bound() с value=21

вернет итератор, указывающий на значение 22, а обращение с value=22 – на значение 23. В первом варианте для сравнения используется оператор “меньше”, определенный для типа элементов контейнера; во втором – заданная программистом операция comp.

#include <algorithm>

#include <vector>

#include <assert.h>

#include <iostream.h>

          

template <class Type>

void print_elements( Type elem ) { cout << elem << " "; }

void (*pfi)( int ) = print_elements;

int main()

{

           int ia[] = {29,23,20,22,17,15,26,51,19,12,35,40};

           vector<int,allocator> vec(ia,ia+12);

                 

           sort(ia,ia+12);

           int *iter = upper_bound(ia,ia+12,19);

           assert( *iter == 20 );

                 

           sort( vec.begin(), vec.end(), greater<int>() );

           vector<int,allocator>::iterator iter_vec;

           iter_vec = upper_bound( vec.begin(), vec.end(),

                             27, greater<int>() );

           assert( *iter_vec == 26 );

                 

           // печатается: 51 40 35 29 27 26 23 22 20 19 17 15 12

           vec.insert( iter_vec, 27 );

           for_each( vec.begin(), vec.end(), pfi ); cout << "\n\n";

}



Алгоритмы для работы с хипом


В стандартной библиотеке используется макс-хип. Макс-хип– это представленное в виде массива двоичное дерево, для которого значение ключа в каждом узле больше либо равно значению ключа в каждом из узлов-потомков. (Подробное обсуждение макс-хипа можно найти в [SEDGEWICK88]. Альтернативой ему является мин-хип, для которого значение ключа в каждом узле меньше либо равно значению ключа в каждом из узлов-потомков.) В реализации из стандартной библиотеки самое большое значение (корень дерева) всегда оказывается в начале массива. Например, приведенная последовательность букв удовлетворяет требованиям, накладываемым на хип:

X T O G S M N A E R A I

В данном примере X – это корневой узел, слева от него находится T, а справа – O. Обратите внимание, что потомки не обязательно должны быть упорядочены (т.е. значение в левом узле не обязано быть меньше, чем в правом). G и S – потомки узла T, а M и N – потомки узла O. Аналогично A и E – потомки G, R и A – потомки S, I – левый потомок M, а N – листовой узел без потомков.

Четыре обобщенных алгоритма для работы с хипом: make_heap(), pop_heap(), push_heap() и sort_heap() – поддерживают его создание и различные манипуляции. В последних трех алгоритмах предполагается, что последовательность, ограниченная парой итераторов, – действительно хип (в противном случае поведение программы не определено). Заметим, что список нельзя использовать как контейнер для хранения хипа, поскольку он не поддерживает произвольный доступ. Встроенный массив для размещения хипа использовать можно, но в этом случае трудно применять алгоритмы pop_heap() и push_heap(), так как они требуют изменения размера контейнера. Мы опишем все четыре алгоритма, а затем проиллюстрируем их работу на примере небольшой программы.



Алгоритмы генерирования и модификации


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

fill(), fill_n(), for_each(), generate(),generate_n(), transform()



Алгоритмы перестановки


Рассмотрим последовательность из трех символов: {a,b,c}. Для нее существует шесть различных перестановок: abc, acb, bac, bca, cab и cba, лексикографически упорядоченных на основе оператора “меньше”. Таким образом, abc– это первая перестановка, потому что каждый элемент меньше последующего. Следующая перестановка – acb, поскольку в начале все еще находится a – наименьший элемент последовательности. Соответственно перестановки, начинающиеся с b, предшествуют тем, которые начинаются с с. Из bac и bca

меньшей является bac, так как последовательность ac лексикографически меньше, чем ca. Если дана перестановка bca, то можно сказать, что предшествующей для нее будет bac, а последующей – cab. Для перестановки abc нет предшествующей, а для cba – последующей.

next_permutation(), prev_permutation()



Алгоритмы поиска


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

adjacent_find(), binary_search(), count(),count_if(), equal_range(),

find(), find_end(), find_first_of(), find_if(), lower_bound(),

upper_bound(), search(), search_n()



Алгоритмы работы с хипом


Хип (heap) – это разновидность двоичного дерева, представленного в массиве. Стандартная библиотека предоставляет такую реализацию хипа, в которой значение ключа в любом узле больше либо равно значению ключа в любом потомке этого узла.

make_heap(), pop_heap(), push_heap(), sort_heap()



Алгоритмы работы с множествами


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

set_union(), set_intersection(), set_difference(),

set_symmetric_difference()



Алгоритмы сортировки и упорядочения


Четырнадцать алгоритмов сортировки и упорядочения предлагают различные способы упорядочения элементов контейнера. Разбиение (partition)– это разделение элементов контейнера на две группы: удовлетворяющие и не удовлетворяющие некоторому условию. Так, можно разбить контейнер по признаку четности/нечетности чисел или в зависимости от того, начинается слово с заглавной или со строчной буквы. Устойчивый (stable) алгоритм сохраняет относительный порядок элементов с одинаковыми значениями или удовлетворяющих одному и тому же условию. Например, если дана последовательность:

{ "pshew", "honey", "Tigger", "Pooh" }

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

{ "Tigger", "Pooh", "pshew", "honey" }

При использовании неустойчивой версии алгоритма сохранение порядка не гарантируется. (Отметим, что алгоритмы сортировки нельзя применять к списку и ассоциативным контейнерам, таким, как множество (set) или отображение (map).)

inplace_merge(), merge(), nth_element(), partial_sort(),

partial_sort_copy(), partition(), random_shuffle(), reverse(),

reverse_copy(), rotate(), rotate_copy(), sort(), stable_sort(),

stable_partition()



Алгоритмы сравнения


Семь алгоритмов дают разные способы сравнения одного контейнера с другим (алгоритмы min() и max() сравнивают два элемента). Алгоритм lexicographical_compare()

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

equal(), includes(), lexicographical_compare(), max(), max_element(),

min(), min_element(), mismatch()



Алгоритмы удаления и подстановки


Пятнадцать алгоритмов удаления и подстановки предоставляют различные способы замены или исключения одного элемента или целого диапазона. unique()

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

copy(), copy_backwards(), iter_swap(), remove(), remove_copy(),

remove_if(),remove_if_copy(), replace(), replace_copy(),

replace_if(), replace_copy_if(), swap(), swap_range(), unique(),

unique_copy()



Альтернативная иерархия классов


Хотя наша иерархия классов Query

представляется вполне приемлемой, она вовсе не является единственно возможной. Например, AndQuery и OrQuery

связаны с бинарной операцией, поэтому они в какой-то степени дублируют друг друга. Можно вынести все данные и функции-члены, общие для них, в абстрактный базовый класс BinaryQuery. Поддерево новой иерархии Query изображено на рисунке 17.2:

 

Query

 

 

BinaryQuery

 

 

AndQuery                        OrQuery

 

Рис. 17.2. Альтернативная иерархия классов

Класс BinaryQuery – это тоже абстрактный базовый класс, следовательно, его фактические объекты в приложении не появляются. Разумной реализации eval() для него предложить нельзя, поэтому чисто виртуальная функция, объявленная в Query, в классе BinaryQuery

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

Две функции-члена для доступа – lop() и rop(), общие для обоих классов, переносятся выше, в BinaryQuery, и определяются как нестатические встроенные. Аналогично два члена _lop и _rop, объявленные в обоих классах, также переносятся в BinaryQuery и становятся нестатическими и защищенными. Открытые конструкторы обоих производных классов объединяются в один защищенный конструктор BinaryQuery:

class BinaryQuery : public Query {

public:

   const Query *lop() { return _lop; }

   const Query *rop() { return _rop; }

protected:

   BinaryQuery( Query *lop, Query *rop )

              : _lop( lop ), _rop( rop )

   {}

   Query *_lop;

   Query *_rop;

};

Складывается впечатление, что теперь оба производных класса должны предоставить лишь подходящие реализации eval():

// увы! эти определения классов некорректны

class OrQuery : public BinaryQuery {

public:

   virtual void eval();

};

class AndQuery : public BinaryQuery {

public:

   virtual void eval();

};

Однако в том виде, в котором мы их определили, эти классы неполны. При компиляции самих определений ошибок не возникает, но если мы попытаемся определить фактический объект:




// ошибка: отсутствует конструктор класса AndQuery

AndQuery proust( new NameQuery( "marcel" ),
                 new NameQuery( "proust " ));

то компилятор выдаст сообщение об ошибке: в классе AndQuery нет конструктора, готового принять два аргумента.

Мы предположили, что AndQuery и OrQuery

наследуют конструктор BinaryQuery

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

применять уже нельзя. Однако программист может этого не осознавать. Ошибка проявится не при конструировании объекта AndQuery, а позже, при его использовании. Кстати говоря, перегруженные операторы new и delete наследуются, что иногда приводит к аналогичным проблемам.)

Каждый производный класс должен предоставлять собственный набор конструкторов. В случае классов AndQuery и OrQuery

единственная цель конструкторов – обеспечить интерфейс для передачи двух своих операндов конструктору BinaryQuery. Так выглядит исправленная реализация:



// правильно: эти определения классов корректны

class OrQuery : public BinaryQuery {

public:

   OrQuery( Query *lop, Query *rop )

          : BinaryQuery( lop, rop ) {}

   virtual void eval();

};

class AndQuery : public BinaryQuery {

public:

   AndQuery( Query *lop, Query *rop )

          : BinaryQuery( lop, rop ) {}

   virtual void eval();
};

Если мы еще раз взглянем на рис. 17.2, то увидим, что BinaryQuery – непосредственный базовый класс для AndQuery и OrQuery, а Query –для BinaryQuery. Таким образом, Query не является непосредственным базовым классом для AndQuery и OrQuery.

Конструктору производного класса разрешается напрямую вызывать только конструктор своего непосредственного предшественника в иерархии (виртуальное наследование является исключением из этого правила, да и из многих других тоже: см. раздел 18.5). Например, попытка включить конструктор Query в список инициализации членов объекта AndQuery

приведет к ошибке.

При определении объектов классов AndQuery и OrQuery

теперь вызываются три конструктора: для базового Query, для непосредственного базового класса BinaryQuery и для производного AndQuery или OrQuery. (Порядок вызова конструкторов базовых классов отражает обход дерева иерархии наследования в глубину.) Дополнительный уровень иерархии, связанный с BinaryQuery, практически не влияет на производительность, поскольку мы определили его конструкторы как встроенные.

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


Аргументы шаблона для параметров-констант


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

template <int hi, int wid>

class Screen {

public:

   Screen() : _height( hi ), _width( wid ), _cursor ( 0 ),

              _screen( hi * wid, '#' )

            { }

   // ...

private:

   string            _screen;

   string::size_type _cursor;

   short             _height;

   short             _width;

};

typedef Screen<24,80> termScreen;

termScreen hp2621;

Screen<8,24> ancientScreen;

Выражение, с которым связан параметр, не являющийся типом, должно быть константным, т.е. вычисляемым во время компиляции. В примере выше typedef termScreen ссылается на экземпляр шаблона Screen<24,80>, где аргумент шаблона для hi равен 24, а для wid – 80. В обоих случаях аргумент – это константное выражение.

Однако для шаблона BufPtr

конкретизация приводит к ошибке, так как значение указателя, получающееся при вызове оператора new(), становится известно только во время выполнения:

template <int *ptr> class BufPtr { ... };

// ошибка: аргумент шаблона нельзя вычислить во время компиляции

BufPtr< new int[24] > bp;

Не является константным выражением и значение неконстантного объекта. Его нельзя использовать в качестве аргумента для параметра-константы шаблона. Однако адрес любого объекта в области видимости пространства имен, в отличие от адреса локального объекта, является константным выражением (даже если спецификатор const

отсутствует), поэтому его можно применять в качестве аргумента для параметра-константы. Константным выражением будет и значение оператора sizeof:

template <int size> Buf { ... };

template <int *ptr> class BufPtr { ... };

int size_val = 1024;

const int c_size_val = 1024;

Buf< 1024 > buf0;   // правильно

Buf< c_size_val > buf1; // правильно

Buf< sizeof(size_val) > buf2; // правильно: sizeof(int)

BufPtr< &size_val > bp0; // правильно

// ошибка: нельзя вычислить во время компиляции

<
Screen< shi, swi > bpObj2;  // расширения типа short до int

·                  преобразования целых типов:



template <unsigned int size> Buf{ ... };

Buf< 1024 > bpObj;  // преобразование из int в unsigned int

(Более подробно они описаны в разделе 9.3.)

Рассмотрим следующие объявления:



extern void foo( char * );

extern void bar( void * );

typedef void (*PFV)( void * );

const unsigned int x = 1024;

template <class Type,

          unsigned int size,

          PFV handler> class Array { ... };

Array<int, 1024U, bar> a0;   // правильно: преобразование не нужно

Array<int, 1024U, foo> a1;   // ошибка: foo != PFV

Array<int, 1024, bar> a2;    // правильно: 1024 преобразуется в unsigned int

Array<int, 1024, bar> a3;    // ошибка: foo != PFV

Array<int, x, bar> a4;       // правильно: преобразование не нужно
Array<int, x, foo> a5;       // ошибка: foo != PFV

Объекты a0 и a4

класса Array

определены правильно, так как аргументы шаблона точно соответствуют типам параметров. Объект a2

также определен правильно, потому что аргумент 1024

типа int

приводится к типу unsigned int параметра-константы size с помощью преобразования целых типов. Объявления a1, a3 и a5 ошибочны, так как не существует преобразования между любыми двумя типами функций.

Приведение значения 0

целого типа к типу указателя недопустимо:



template <int *ptr>

class BufPtr { ... };

// ошибка: 0 имеет тип int

// неявное преобразование в нулевой указатель не применяется
BufPtr< 0 > nil;

Упражнение 16.3

Укажите, какие из данных конкретизированных шаблонов действительно приводят к конкретизации:



template < class Type >

   class Stack { };

void f1( Stack< char > );  // (a)

class Exercise {

   // ...

   Stack< double > &rsd;   // (b)

   Stack< int >    si;     // (c)

};

int main() {

   Stack< char > *sc;      // (d)

   f1( *sc );              // (e)

   int iObj = sizeof( Stack< string > );  // (f)
<


}

Упражнение 16.4

Какие из следующих конкретизаций шаблонов корректны? Почему?



template < int *ptr > class Ptr ( ... };

template < class Type, int size > class Fixed_Array { ... };
template < int hi, int wid > class Screen { ... };



(a) const int size = 1024;

    Ptr< &size > bp1;



(b) int arr[10];

    Ptr< arr > bp2;

 (c) Ptr < 0 > bp3;



(d) const int hi = 40;

    const int wi = 80;

    Screen< hi, wi+32 > sObj;



(e) const int size_val = 1024;

    Fixed_Array< string, size_val > fa1;



(f) unsigned int fasize = 255;

    Fixed_Array< int, fasize > fa2;



(g) const double db = 3.1415;

    Fixed_Array< double, db > fa3;


Аргументы со значениями по умолчанию


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

extern void ff( int );

extern void ff( long, int = 0 );

int main() {

   ff( 2L );   // соответствует ff( long, 0 );

   ff( 0, 0 ); // соответствует ff( long, int );

   ff( 0 );    // соответствует ff( int );

   ff( 3.14 ); // ошибка: неоднозначность

}

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

1.      для второго формального параметра есть значение по умолчанию;

2.      первый параметр типа long

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

Последний вызов является неоднозначным, поскольку обе устоявших функции могут быть выбраны, если применить стандартное преобразование к первому аргументу. Функции ff(int) не отдается предпочтение только потому, что у нее один параметр.

Упражнение 9.9

Объясните, что происходит при разрешении перегрузки для вызова функции compute()

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

namespace primerLib {

   void compute();

   void compute( const void * );

}

using primerLib::compute;

void compute( int );

void compute( double, double = 3.4 );

void compute( char*, char* = 0 );

int main() {

   compute( 0 );

   return 0;

}

Что будет, если using-объявление поместить внутрь main() перед вызовом compute()? Ответьте на те же вопросы.

10



Арифметические объекты-функции


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

·         Сложение: plus<Type>

plus<string> stringAdd;

// вызывается string::operator+()

sres = stringAdd( sval1, sval2 );

dres = BinaryFunc( plus<double>(), dval1, dval2 );

·         Вычитание: minus<Type>

minus<int> intSub;

ires = intSub( ival1, ival2 );

dres = BinaryFunc( minus<double>(), dval1, dval2 );

·         Умножение: multiplies<Type>

multiplies<complex> complexMultiplies;

cres = complexMultiplies( cval1, cval2 );

dres = BinaryFunc( multiplies<double>(), dval1, dval2 );

·         Деление: divides<Type>

divides<int> intDivides;

ires = intDivides( ival1, ival2 );

dres = BinaryFunc( divides<double>(), dval1, dval2 );

·         Взятие остатка: modulus<Type>

modulus<Int> IntModulus;

Ires = IntModulus( Ival1, Ival2 );

ires = BinaryFunc( modulus<int>(), ival1, ival2 );

·         Вычисление противоположного значения: negate<Type>

negate<int> intNegate;

ires = intNegate( ires );

Ires = UnaryFunc( negate<Int>(), Ival1 );



Арифметические операции


Таблица 4.1. Арифметические операции

Символ операции

Значение

Использование

*

Умножение

expr * expr

/

Деление

expr / expr

%

Остаток от деления

expr % expr

+

Сложение

expr + expr

-

Вычитание

expr – expr

Деление целых чисел дает в результате целое число. Дробная часть результата, если она есть, отбрасывается:

int ivall = 21 / 6;

int iva12 = 21 / 7;

И ival1, и ival2 в итоге получат значение 3.

Операция остаток (%), называемая также делением по модулю, возвращает остаток от деления первого операнда на второй, но применяется только к операндам целого типа (char, short, int, long). Результат положителен, если оба операнда положительны. Если же один или оба операнда отрицательны, результат зависит от реализации, то есть машинно-зависим. Вот примеры правильного и неправильного использования деления по модулю:

3.14 % 3; // ошибка: операнд типа double

21 % 6;   // правильно:  3

21 % 7;   // правильно:  0

21 % -5;  // машинно-зависимо: -1 или 1

int iva1 = 1024;

double dval = 3.14159;

iva1 % 12;   // правильно:

iva1 % dval; // ошибка: операнд типа double

Иногда результат вычисления арифметического выражения может быть неправильным либо не определенным. В этих случаях говорят об арифметических исключениях (хотя они не вызывают возбуждения исключения в программе). Арифметические исключения могут иметь чисто математическую природу (скажем, деление на 0) или происходить от представления чисел в компьютере – как переполнение

(когда значение превышает величину, которая может быть выражена объектом данного типа). Например, тип char

содержит 8 бит и способен хранить значения от 0 до 255

либо от -128 до 127 в зависимости от того, знаковый он или беззнаковый. В следующем примере попытка присвоить объекту типа char значение 256

вызывает переполнение:

#include <iostream>

int main() {

    char byte_value = 32;

    int ival = 8;

    // переполнение памяти, отведенной под byte_value

    byte_value = ival * byte_value;

    cout << "byte_value: " <<static_cast<int>(byte_value) << endl;

<
}

Для представления числа 256 необходимы 9 бит. Переменная byte_value получает некоторое неопределенное (машинно-зависимое) значение. Допустим, на нашей рабочей станции SGI мы получили 0. Первая попытка напечатать это значение с помощью:

cout << "byte_va1ue: " << byte_va1ue << endl;

привела к результату:

byte_value:

После некоторого замешательства мы поняли, что значение 0 – это нулевой символ ASCII, который не имеет представления при печати. Чтобы напечатать не представление символа, а его значение, нам пришлось использовать весьма странно выглядящее выражение:

static_cast<int>(byte_value)

которое называется явным приведением типа. Оно преобразует тип объекта или выражения в другой тип, явно заданный программистом. В нашем случае мы изменили byte_value на int. Теперь программа выдает более осмысленный результат:

byte_value: 0

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

представляются ASCII-символами (а не кодами), в то время как для объектов типа int мы увидим содержащиеся в них значения. (Преобразования типов рассмотрены в разделе 4.14.)

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

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



также содержат эту информацию. ( Об использовании этих заголовочных файлов для того, чтобы избежать переполнения и потери значимости, см. главы 4 и 6 [PLAUGER[O.A.1] 92]).

Арифметика вещественных чисел создает еще одну проблему, связанную с округлением. Вещественное число представляется фиксированным количеством разрядов (разным для разных типов – float, double и long double), и точность значения зависит от используемого типа данных. Но даже самый точный тип long double не может устранить ошибку округления. Вещественная величина в любом случае представляется с некоторой ограниченной точностью. (См. [SHAMPINE97] о проблемах округления вещественных чисел.)

Упражнение 4.1

В чем разница между приведенными выражениями с операцией деления?



double dvall = 10.0, dva12 = 3.0;

int ivall =

10, iva12 = 3;

dvall / dva12;
ivall / iva12;

Упражнение 4.2

Напишите выражение, определяющее, четным или нечетным является данное целое число.

Упражнение 4.3

Найдите заголовочные файлы limits, climits и cfloat и посмотрите, что они содержат.


Арифметические преобразования типов


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

·

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

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

·                  Мы рассмотрим иерархию правил преобразований, начиная с наибольшего типа long double.

Если один из операндов имеет тип long double, второй приводится к этому же типу в любом случае. Например, в следующем выражении символьная константа 'a'

трансформируется в long double (значение 97 для представления ASCII) и затем прибавляется к литералу того же типа: 3.14159L + 'a'.

Если в выражении нет операндов long double, но есть операнд double, все преобразуется к этому типу. Например:

int    iva1;

float fval;

double dval;

// fva1 и iva1 преобразуются к double перед сложением

dval + fva1 + ival;

В том случае, если нет операндов типа double и long double, но есть операнд float, тип остальных операндов меняется на float:

char cvat;

int iva1;

float fva1;

// iva1 и cval преобразуются к float перед сложением

cvat + fva1 + iva1;

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

При приведении к целому типы char, signed char, unsigned char и short int

преобразуются в int. Тип unsigned short int трансформируется в int, если этот тип достаточен для представления всего диапазона значений unsigned short int


(обычно это происходит в системах, отводящих полслова под short и целое слово под int), в противном случае unsigned short int заменяется на unsigned int.

Тип wchar_t и перечисления приводятся к наименьшему целому типу, способному представить все их значения. Например, в перечислении

enum status { bad, ok };

значения элементов равны 0 и 1. Оба эти значения могут быть представлены типом char, значит char и станет типом внутреннего представления данного перечисления. Приведение к целому преобразует char в int.

В следующем выражении



char cval;

bool found;

enum mumble { ml, m2, m3 }      mval;

unsigned long ulong;

cval + ulong; ulong + found; mval + ulong;

перед определением типа результата cval, found и mval

преобразуются в int.

После приведения к целому сравниваются получившиеся типы операндов. Если один из них имеет тип unsigned long, то остальные будут того же типа. В нашем примере все три объекта, прибавляемые к ulong, приводятся к типу unsigned long.

Если в выражении нет объектов unsigned long, но есть объекты типа long, тип остальных операндов меняется на long. Например:



char cval;

long lval;

// cval и 1024 преобразуются в long перед сложением
cval + 1024 + lval;

Из этого правила есть одно исключение: преобразование unsigned int в long

происходит только в том случае, если тип long способен вместить весь диапазон значений unsigned int. (Обычно это не так в 32-битных системах, где и long, и int

представляются одним машинным словом.) Если же тип long не способен представить весь диапазон unsigned int, оба операнда приводятся к unsigned long.

В случае отсутствия операндов типов unsigned long и long, используется тип unsigned int. Если же нет операндов и этого типа, то к int.

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


Автоматические объекты


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

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

#include "Matrix.h"

Matrix* trouble( Matrix *pm )

{

    Matrix res;

    // какие-то действия

    // результат присвоим res

    return &res; // плохо!

}

int main()

{

    Matrix m1;

    // ...

    Matrix *mainResult = trouble( &m1 );

    // ...

}

mainResult

получает значение адреса автоматического объекта res. К несчастью, память, отведенная под res, освобождается по завершении функции trouble(). После возврата в main() mainResult

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

Передача в функцию trouble()

адреса m1 автоматического объекта функции main()

безопасна. Память, отведенная main(), во время вызова trouble()находится в стеке, так что m1

остается доступной внутри trouble().

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



Безопасное связывание A


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

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

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

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

Упражнение 9.1

Зачем может понадобиться объявлять перегруженные функции?

Упражнение 9.2

Как нужно объявить перегруженные варианты функции error(), чтобы были корректны следующие вызовы:

int index;

int upperBound;

char selectVal;

// ...

error( "Array out of bounds: ", index, upperBound );

error( "Division by zero" );

<
error( "Invalid selection", selectVal );

Упражнение 9.3

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



(a) int calc( int, int );

    int calc( const int, const int );

(b) int get();

    double get();

(c) int *reset( int * );

    double *reset( double * ):

(d) extern "C" int compute( int *, int );
    extern "C" double compute( double *, double );

Упражнение 9.4

Какая из следующих инициализаций приводит к ошибке? Почему?



(a) void reset( int * );

    void (*pf)( void * ) = reset;

(b) int calc( int, int );

    int (*pf1)( int, int ) = calc;

(c) extern "C" int compute( int *, int );

    int (*pf3)( int*, int ) = compute;

 (d) void (*pf4)( const matrix & ) = 0;


Безымянные пространства имен


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

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

// ----- SortLib.h -----

void quickSort( double *, double * );

void bubbleSort( double *, double * );

void mergeSort( double *, double * );

void heapSort( double *, double * );

Все они используют одну и ту же функцию swap() для того, чтобы менять местами элементы вектора. Однако она не должна быть видна во всей программе, поскольку нужна только четырем названным функциям. Локализуем ее в файле SortLib.C. Приведенный код не дает желаемого результата. Как вы думаете, почему?

// ----- SortLib.C -----

void swap( double *dl, double *d2 ) { /* ... */ }

// только эти функции используют swap()

void quickSort( double *d1, double *d2 ) { /* ... */ }

void bubbleSort( double *d1, double *d2 ) { /* ... */ }

void mergeSort( double *d1, double *d2 ) { /* ... */ }

void heapSort( double *d1, double *d2 ) { /* ... */ }

Хотя функция swap()

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

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




// ----- SortLib.C -----

namespace {

    void swap( double *dl, double *d2 ) { /* ... */ }

}
// определения функций сортировки не изменяются

Функция swap()

видна только в файле SortLib.C. Если в другом файле в безымянном пространстве имен содержится определение swap(), то это другая функция. Наличие двух функций swap() не является ошибкой, поскольку они различны. Безымянные пространства имен отличаются от прочих: определение такого пространства локально для одного файла и не может размещаться в нескольких.

Имя swap()

может употребляться в неквалифицированной форме в файле SortLib.C

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



void quickSort( double *d1, double *d2 ) {

    // ...

    double* elem = d1;

    // ...

    // ссылка на член безымянного пространства имен swap()

    swap( d1, elem );

    // ...
}

Члены безымянного пространства имен относятся к сущностям программы. Поэтому функция swap()

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

До того как в стандарте С++ появилось понятие пространства имен, наиболее удачным решением проблемы локализации было использование ключевого слова static, унаследованного из С. Член безымянного пространства имеет свойства, аналогичные глобальной сущности, объявленной как static. В языке С такая сущность невидима вне файла, в котором объявлена. Например, текст из SortLib.C

можно переписать на С, сохранив свойства swap():



// SortLib.C

// swap() невидима для других файлов программы

static void swap( double *d1, double *d2 ) { /* ... */ }
// определения функций сортировки такие же, как и раньше

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

Упражнение 8.11

Зачем нужно определять собственное пространство имен в программе?

Упражнение 8.12

Имеется следующее объявление operator*(), члена вложенного пространства имен cplusplus_primer::MatrixLib:



namespace cplusplus_primer {

    namespace MatrixLib {

        class matrix { /*...*/ };

        matrix operator* ( const matrix &, const matrix & );

        // ...

    }
}

Как определить эту функцию в глобальной области видимости? Напишите только прототип.

Упражнение 8.13

Объясните, зачем нужны безымянные пространства имен.


Библиотека iostream


Частью стандартной библиотеки C++ является библиотека iostream– объектно-ориентированная иерархия классов, где используется и множественное, и виртуальное наследование. В ней реализована поддержка для файлового ввода/вывода данных встроенных типов. Кроме того, разработчики классов могут расширять эту библиотеку для чтения и записи новых типов данных.

Для использования библиотеки iostream в программе необходимо включить заголовочный файл

#include <iostream>

Операции ввода/вывода выполняются с помощью классов istream (потоковый ввод) и ostream

(потоковый вывод). Третий класс, iostream, является производным от них и поддерживает двунаправленный ввод/вывод. Для удобства в библиотеке определены три стандартных объекта-потока:

·         cin – объект класса istream, соответствующий стандартному вводу. В общем случае он позволяет читать данные с терминала пользователя;

·         cout – объект класса ostream, соответствующий стандартному выводу. В общем случае он позволяет выводить данные на терминал пользователя;

·         cerr – объект класса ostream, соответствующий стандартному выводу для ошибок. В этот поток мы направляем сообщения об ошибках программы.

Вывод осуществляется, как правило, с помощью перегруженного оператора сдвига влево (<<), а ввод – с помощью оператора сдвига вправо (>>):

#include <iostream>

#include <string>

int main()

{

   string in_string;

   // вывести литерал на терминал пользователя

   cout << "Введите свое имя, пожалуйста: ";

   // прочитать ответ пользователя в in_string

   cin >> in_string;

   if ( in_string.empty() )

      // вывести сообщение об ошибке на терминал пользователя

      cerr << "ошибка: введенная строка пуста!\n";

   else cout << "Привет, " << in_string << "!\n";

<
}

Назначение операторов легче запомнить, если считать, что каждый “указывает” в сторону перемещения данных. Например,

>> x

перемещает данные в x, а

<< x

перемещает данные из x. (В разделе 20.1 мы покажем, как библиотека iostream

поддерживает ввод данных, а в разделе 20.5 – как расширить ее для ввода данных новых типов. Аналогично раздел 20.2 посвящен поддержке вывода, а раздел 20.4 – расширению для вывода данных определенных пользователем типов.)

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

·         ifstream, производный от istream, связывает ввод программы с файлом;

·         ofstream, производный от ostream, связывает вывод программы с файлом;

·         fstream, производный от iostream, связывает как ввод, так и вывод программы с файлом.

Чтобы использовать часть библиотеки iostream, связанную с файловым вводом/выводом, необходимо включить в программу заголовочный файл

#include <fstream>

(Файл fstream уже включает iostream, так что включать оба файла необязательно.) Файловый ввод/вывод поддерживается теми же операторами:



#include <fstream>

#include <string>

#include <vector>

#include <algorithm>

int main()

{

   string ifile;

   cout << "Введите имя файла для сортировки: ";

   cin >> ifile;

   // сконструировать объект класса ifstream для ввода из файла

   ifstream infile( ifile.c_str() );

   if ( ! infile ) {

      cerr << "ошибка: не могу открыть входной файл: "

           << ifile << endl;

      return -1;

   }

   string ofile = ifile + ".sort";

   // сконструировать объект класса ofstream для вывода в файл

   ofstream outfile( ofile.c_str() );

   if ( ! outfile) {

      cerr << "ошибка: не могу открыть выходной файл: "

           << ofile << endl;

      return -2;

   }

   string buffer;

   vector< string, allocator > text;

   int cnt = 1;

   while ( infile >> buffer ) {

         text.push_back( buffer );

         cout << buffer << (cnt++ % 8 ? " " : "\n" );

   }

   sort( text.begin(), text.end() );

   // выводим отсортированное множество слов в файл

   vector< string >::iterator iter = text.begin();

   for ( cnt = 1; iter != text.end(); ++iter, ++cnt )

       outfile << *iter

               << (cnt % 8 ? " " : "\n" );

   return 0;
<


}

Вот пример сеанса работы с этой программой. Нас просят ввести файл для сортировки. Мы набираем alice_emma

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

Введите имя файла для сортировки: alice_emma

Alice Emma has long flowing red hair. Her

Daddy says when the wind blows through her

hair, it looks almost alive, like a fiery

bird in flight. A beautiful fiery bird, he

tells her, magical but untamed. "Daddy, shush, there

is no such creature," she tells him, at

the same time wanting him to tell her

more. Shyly, she asks, "I mean, Daddy, is

there?"

Далее программа выводит в файл outfile

отсортированную последовательность строк. Конечно, на порядок слов влияют знаки препинания; в следующем разделе мы это исправим:

"Daddy, "I A Alice Daddy Daddy, Emma Her

Shyly, a alive, almost asks, at beautiful bird

bird, blows but creature," fiery fiery flight. flowing

hair, hair. has he her her her, him

him, in is is it like long looks

magical mean, more. no red same says she

she shush, such tell tells tells the the

there there?" through time to untamed. wanting when

wind

(В разделе 20.6 мы познакомимся с файловым вводом/выводом более подробно.)

Библиотека iostream

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

·         istringstream, производный от istream, читает из строки;

·         ostringstream, производный от ostream, пишет в строку;

·         stringstream, производный от iostream, выполняет как чтение, так и запись.

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



#include <sstream>

( Файл sstream уже включает iostream, так что включать оба файла необязательно.) В следующем фрагменте объект класса ostringstream

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



#include <sstream>

string program_name( "our_program" );

string version( 0.01 );

// ...

string mumble( int *array, int size )

{

   if ( ! array ) {

      ostringstream out_message;

      out_message << "ошибка: "

                  << program_name << "--" << version

                  << ": " << __FILE__ << ": " << __LINE__

                  << " -- указатель равен 0; "

                  << " а должен адресовать массив.\n";

      // возвращаем строку, в которой находится сообщение

      return out_message.str();

   }

   // ...
}

(В разделе 20.8 мы познакомимся со строковым вводом/выводом более подробно.)

Потоки ввода/вывода поддерживают два предопределенных типа: char и wchar_t. В этой главе мы расскажем только о чтении и записи в потоки данных типа char. Помимо них, в библиотеке iostream

имеется набор классов и объектов для работы с типом wchar_t. Они отличаются от соответствующих классов, использующих тип char, наличием префикса ‘w’. Так, объект стандартного ввода называется wcin, стандартного вывода – wcout, стандартного вывода для ошибок – wcerr. Но набор заголовочных файлов для char и wchar_t

один и тот же.

Классы для ввода/вывода данных типа wchar_t называются wostream, wistream, wiostream, для файлового ввода/вывода – wofstream, wifstream, wfstream, а для строкового – wostringstream, wistringstream, wstringstream.


Битовое поле– член, экономящий память


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

class File {

   // ...

   unsigned int modified : 1;   // битовое поле

};

После идентификатора битового поля следует двоеточие, а за ним – константное выражение, задающее число битов. К примеру, modified – это поле из одного бита.

Битовые поля, определенные в теле класса подряд, по возможности упаковываются в соседние биты одного целого числа, делая хранение объекта более компактным. Так, в следующем объявлении пять битовых полей будут содержаться в одном числе типа unsigned int, ассоциированном с первым полем mode:

typedef unsigned int Bit;

class File {

public:

   Bit mode: 2;

   Bit modified: 1;

   Bit prot_owner: 3;

   Bit prot_group: 3;

   Bit prot_world: 3;

   // ...

};

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

void File::write()

{

   modified = 1;

   // ...

}

void File::close()

{

   if ( modified )

      // ... сохранить содержимое

}

Вот простой пример использования битового поля длиной больше 1 (примененные здесь побитовые операции рассматривались в разделе 4.11):

enum { READ = 01, WRITE = 02 };   // режимы открытия файла

int main() {

   File myFile;

   myFile.mode |= READ;

   if ( myFile.mode & READ )

      cout << "myFile.mode is set to READ\n";

}

Обычно для проверки значения битового поля-члена определяются встроенные функции-члены. Допустим, в классе File

можно ввести члены isRead() и isWrite():

inline int File::isRead() { return mode & READ; }

inline int File::isWrite() { return mode & WRITE; }

if ( myFile.isRead() ) /* ... */

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

К битовому полю нельзя применять оператор взятия адреса (&), поэтому не может быть и указателя на подобные поля-члены. Кроме того, полю запрещено быть статическим членом.

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

Упражнение 13.17

Перепишите примеры из этого подраздела так, чтобы в классе File вместо объявления и прямого манипулирования битовыми полями использовался класс bitset и его операторы.



Частичные специализации шаблонов классов A


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

Рассмотрим шаблон класса Screen, введенный в разделе 16.2. Частичная специализации Screen<hi,80>

дает более эффективную реализацию для экранов с 80 столбцами:

template <int hi, int wid>

class Screen {

   // ...

};

// частичная специализация шаблона класса Screen

template <int hi>

class Screen<hi, 80> {

public:

   Screen();

   // ...

private:

   string            _screen;

   string::size_type _cursor;

   short             _height;

   // для экранов с 80 колонками используются специальные алгоритмы

};

Частичная специализация шаблона класса – это шаблон, и ее определение похоже на определение шаблона. Оно начинается с ключевого слова template, за которым следует список параметров, заключенный в угловые скобки. Список параметров здесь отличается от соответствующего списка параметров общего шаблона. Для частичной специализации шаблона Screen

есть только один параметр-константа hi, поскольку значение второго аргумента равно 80, т.е. в данном списке представлены только те параметры, для которых фактические аргументы еще неизвестны.

Имя частичной специализации совпадает с именем того общего шаблона, которому она соответствует, в нашем случае Screen. Однако за ее именем всегда следует список аргументов. В примере выше этот список выглядит как <hi,80>. Поскольку значение аргумента для первого параметра шаблона неизвестно, то на этом месте в списке стоит имя параметра шаблона; вторым же аргументом является значение 80, которым частично специализирован шаблон.


Частичная специализация шаблона класса неявно конкретизируется при использовании в программе. В следующем примере частичная специализация конкретизируется аргументом шаблона 24

вместо hi:

Screen<24,80> hp2621;

Обратите внимание, что экземпляр Screen<24,80>

может быть конкретизирован не только из частично специализированного, но и из общего шаблона. Почему же тогда компилятор остановился именно на частичной специализации? Если для шаблона класса объявлены частичные специализации, компилятор выбирает то определение, которое является наиболее специализированным для заданных аргументов. Если же ни одно из них не подходит, используется общее определение шаблона. Например, при конкретизации экземпляра Screen<40,132>  соответствующей аргументам шаблона специализации нет. Наш вариант применяется только для конкретизации типа Screen с 80 колонками.

Определение частичной специализации не связано с определением общего шаблона. У него может быть совершенно другой набор членов, а также собственные определения функций-членов, статических членов и вложенных типов. Содержащиеся в общем шаблоне определения членов никогда не употребляются для конкретизации членов его частичной специализации. Например, для частичной специализации Screen<hi,80>

должен быть определен свой конструктор:



// конструктор для частичной специализации Screen<hi,80>

template <int hi>

Screen<hi,80>::Screen() : _height( hi ), _cursor( 0 ),

                          _screen( hi * 80, bk )
                        { }

Если для конкретизации некоторого класса применяется частичная специализация, то определение конструктора из общего шаблона не используется даже тогда, когда определение конструктора Screen<hi,80>

отсутствует.


Численные алгоритмы


Следующие четыре алгоритма реализуют численные операции с контейнером. Для их использования необходимо включить заголовочный файл <numeric>.

accumulate(), partial_sum(), inner_product(), adjacent_difference()



Чисто виртуальные функции


С точки зрения кодирования основная задача, стоящая перед нами в связи с поддержкой пользовательских запросов,– это реализация зависимых от типа операций для каждого из возможных операторов. Для этого мы определили четыре конкретных типа классов: AndQuery, OrQuery и т.д. Однако с точки зрения проектирования наша цель – инкапсулировать обработку каждого вида запроса, спрятать за не зависящим от типа интерфейсом. Это позволит построить ядро приложения, которое не потребует изменений при добавлении или удалении типов.

Чтобы добиться этого, определим абстрактный тип класса Query. При этом мы не будем программировать разные типы пользовательских запросов, а лишь абстрактные операции, применимые к ним:

void doit_and_bedone( vector< Query* > *pvec )

{

   vector<Query*>::iterator

      it = pvec->begin(),

      end_it = pvec->end();

   for ( ; it != end_it; ++it )

   {

       Query *pq = *it;

       cout << "обрабатывается " << *pq << endl;

       pq->eval();

       pq->display();

       delete pq;

   }

}

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

достаточен для поддержки новых запросов.

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

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


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



class Query {

public:

   // объявляется чисто виртуальная функция

   virtual ostream& print( ostream&=cout ) const = 0;

   // ...
};

Заметьте, что за объявлением функции следует присваивание нуля.

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



// В классе Query объявлены одна или несколько виртуальных функций,

// поэтому программист не может создавать независимые объекты

// класса Query

// правильно: подобъект Query в составе NameQuery

Query *pq = new NameQuery( "Nostromo" );

// ошибка: оператор new создает объект класса Query
Query *pq2 = new Query;

Абстрактный базовый класс может существовать только как подобъект в составе объекта некоторого производного от него класса. Это именно та семантика, которая нужна нам для базового Query.


Читаем текстовый файл


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

Как получить одну строку текста? Стандартная библиотека предоставляет для этого функцию getline():

istream&

getline( istream &is, string str, char delimiter );

getline()берет из входного потока все символы, включая пробелы, и помещает их в объект типа string, до тех пор пока не встретится символ delimiter, не будет достигнут конец файла или количество полученных символов не станет равным величине, возвращаемой функцией-членом max_size()класса string.

Мы будем помещать каждую такую строку в вектор.

Мы вынесли код, читающий файл, в функцию, названную retrieve_text(). В объекте типа pair

дополнительно сохраняется размер и номер самой длинной строки. (Полный текст программы приводится в разделе 6.14.)

Вот реализация функции ввода файла:[15]

// возвращаемое значение - указатель на строковый вектор

vector<string,allocator>*

retrieve_text()

{

    string file_name;

    cout << "please enter file name: ";

    cin >> file_name;

    // откроем файл для ввода ...

    ifstream 1nfile( file_name.c_str(), ios::in );

    if ( ! infile ) {

        cerr << "oops! unable to open file "

             << file_name << " -- bailing out!\n";

        exit( -1 );

    }

    else cout << '\n';

    vector<string, allocator> *1ines_of_text =

        new vector<string, allocator>;

    string textime;

    typedef pair<string::size_type, int> stats;

    stats maxline;

    int   linenum = 0;

    while ( getline( infile, textline, '\n' )) {

        cout << "line read: " << textline << '\n';

        if ( maxline.first < textline.size() ) {

            maxline.first = textline.size() ;

            maxline.second = linenum;

        }

        1ines_of_text->push_back( textline );

        linenum++;

    }

    return lines_of_text;

<
}

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

please enter file name: a1ice_emma

line read: Alice Emma has long flowing red hair. Her Daddy says

line read: when the wind blows through her hair, it looks

           almost alive,

line read: like a fiery bird in flight. A beautiful fiery bird,

           he tells her,

line read: magical but untamed. "Daddy, shush, there is no such

           thing, "

line read: she tells him, at the same time wanting him to tell

           her more.

line read: Shyly, she asks, "I mean. Daddy, is there?"

number of lines: 6

maximum length: 66

longest line: like a fiery bird in flight. A beautiful fiery

              bird, he tells her,

После того как все строки текста сохранены, нужно разбить их на слова. Сначала мы отбросим знаки препинания. Например, возьмем строку из части “Anna Livia Plurrabelle” романа “Finnegans Wake”.

"For every tale there's a telling,

and that's the he and she of it."

В приведенном фрагменте есть следующие знаки препинания:

"For

there's

telling,

that's

it."

А хотелось бы получить:

For

there

telling

that

it

Можно возразить, что

there's

должно превратиться в

there is

но мы-то движемся в другом направлении: следующий шаг – это отбрасывание семантически нейтральных слов, таких, как is, that, and, it и т.д. Так что для данной строчки из “Finnegans Wake” только два слова являются значимыми: tale и telling, и только по этим словам будет выполняться поиск. (Мы реализуем набор стоп-слов с помощью контейнерного типа set, который подробно рассматривается в следующем разделе.)

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

Home is where the heart is.

A home is where they have to let you in.

Несомненно, запрос слова home

должен найти обе строки.

Мы должны также обеспечить минимальную поддержку учета словоформ: отбрасывать окончания слов, чтобы слова dog и dogs, love, loving и loved рассматривались системой как одинаковые.

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


Члены и не члены класса


Рассмотрим операторы равенства в нашем классе String более внимательно. Первый оператор позволяет устанавливать равенство двух объектов, а второй – объекта и C-строки:

#include "String.h"

int main() {

   String flower;

   // что-нибудь записать в переменную flower

   if ( flower == "lily" )  // правильно

      // ...

   else

   if ( "tulip" == flower )  // ошибка

      // ...

}

При первом использовании оператора равенства в main() вызывается перегруженный operator==(const char *)

класса String. Однако на второй инструкции if компилятор выдает сообщение об ошибке. В чем дело?

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

Но можно же создать объект класса String из C-строки с помощью конструктора класса. Почему компилятор не выполнит неявно такое преобразование:

if ( String( "tulip" ) == flower )  //правильно: вызывается оператор-член

Причина в его неэффективности. Перегруженные операторы не требуют, чтобы оба операнда имели один и тот же тип. К примеру, в классе Text определяются следующие операторы равенства:

class Text {

public:

   Text( const char * = 0 );

   Text( const Text & );

   // набор перегруженных операторов равенства

   bool operator==( const char * ) const;

   bool operator==( const String & ) const;

   bool operator==( const Text & ) const;

   // ...

};

и выражение в main()

можно переписать так:

if ( Text( "tulip" ) == flower )  // вызывается Text::operator==()

Следовательно, чтобы найти подходящий для сравнения оператор равенства, компилятору придется просмотреть все определения классов в поисках конструктора, способного привести левый операнд к некоторому типу класса. Затем для каждого из таких типов нужно проверить все ассоциированные с ним перегруженные операторы равенства, чтобы понять, может ли хоть один из них выполнить сравнение. А после этого компилятор должен решить, какая из найденных комбинаций конструктора и оператора равенства (если таковые нашлись) лучше всего соответствует операнду в правой части! Если потребовать от компилятора выполнения всех этих действий, то время трансляции программ C++ резко возрастет. Вместо этого компилятор просматривает только перегруженные операторы, определенные как члены класса левого операнда (и его базовых классов, как мы покажем в главе 19).


Разрешается, однако, определять перегруженные операторы, не являющиеся членами класса. При анализе строки в main(), вызвавшей ошибку компиляции, подобные операторы принимались во внимание. Таким образом, сравнение, в котором C-строка стоит в левой части, можно сделать корректным, если заменить операторы равенства, являющиеся членами класса String, на операторы равенства, объявленные в области видимости пространства имен:



bool operator==( const String &, const String & );
bool operator==( const String &, const char * );

Обратите внимание, что эти глобальные перегруженные операторы имеют на один параметр больше, чем операторы-члены. Если оператор является членом класса, то первым параметром неявно передается указатель this. То есть для операторов-членов выражение

flower == "lily"

переписывается компилятором в виде:

flower.operator==( "lily" )

и на левый операнд flower в определении перегруженного оператора-члена можно сослаться с помощью this. (Указатель this

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

Тогда выражение

flower == "lily"

вызывает оператор

bool operator==( const String &, const char * );

Непонятно, какой оператор вызывается для второго случая использования оператора равенства:

"tulip" == flower

Мы ведь не определили такой перегруженный оператор:

bool operator==( const char *, const String & );

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

operator==( String("tulip"), flower );

и вызывает для выполнения сравнения следующий перегруженный оператор:

bool operator==( const String &, const String & );

Но тогда зачем мы предоставили второй перегруженный оператор:



bool operator==( const String &, const char * );

Преобразование типа из C-строки в класс String

может быть применено и к правому операнду. Функция main()

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

bool operator==( const String &, const String & );

Предоставлять ли только этот оператор или еще два:



bool operator==( const char *, const String & );
bool operator==( const String &, const char * );

зависит от того, насколько велики затраты на преобразование из C-строки в String во время выполнения, то есть от “стоимости” дополнительных вызовов конструктора в программах, пользующихся нашим классом String. Если оператор равенства будет часто использоваться для сравнения C-строк и объектов , то лучше предоставить все три варианта. (Мы вернемся к вопросу эффективности в разделе, посвященном друзьям.

Подробнее о приведении к типу класса с помощью конструкторов мы расскажем в разделе 15.9; в разделе 15.10 речь пойдет о разрешении перегрузки функций с помощью описанных преобразований, а в разделе 15.12 – о разрешении перегрузки операторов.)

Итак, на основе чего принимается решение, делать ли оператор членом класса или членом пространства имен? В некоторых случаях у программиста просто нет выбора:

·                  если перегруженный оператор является членом класса, то он вызывается лишь при условии, что левым операндом служит член этого класса. Если же левый операнд имеет другой тип, оператор обязан быть членом пространства имен;

·                  язык требует, чтобы операторы присваивания ("="), взятия индекса ("[]"), вызова ("()") и доступа к членам по стрелке ("->") были определены как члены класса. В противном случае выдается сообщение об ошибке компиляции:





// ошибка: должен быть членом класса
char& operator[]( String &, int ix );

(Подробнее оператор присваивания рассматривается в разделе 15.3, взятия индекса – в разделе 15.4, вызова – в разделе 15.5, а оператор доступа к члену по стрелке – в разделе 15.6.)

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

Прежде чем закончить этот подраздел, определим операторы равенства для класса String в пространстве имен:



bool operator==( const String &str1, const String &str2 )

{

   if ( str1.size() != str2.size() )

      return false;

   return strcmp( str1.c_str(), str2.c_str() ) ? false : true ;

}

inline bool operator==( const String &str, const char *s )

{

   return strcmp( str.c_str(), s ) ? false : true ;
}


Что такое переменная


Переменная, или объект– это именованная область памяти, к которой мы имеем доступ из программы; туда можно помещать значения и затем извлекать их. Каждая переменная С++ имеет определенный тип, который характеризует размер и расположение этой области памяти, диапазон значений, которые она может хранить, и набор операций, применимых к этой переменной. Вот пример определения пяти объектов разных типов:

int    student_count;

double salary;

bool   on_loan;

strins street_address;

char   delimiter;

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

·                  собственно значение, или r-значение (от read value – значение для чтения), которое хранится в этой области памяти и присуще как переменной, так и литералу;

·                  значение адреса области памяти, ассоциированной с переменной, или l-значение (от location value – значение местоположения) – место, где хранится r-значение; присуще только объекту.

В выражении

ch = ch - '0';

переменная ch

находится и слева и справа от символа операции присваивания. Справа расположено значение для чтения (ch и символьный литерал '0'): ассоциированные с переменной данные считываются из соответствующей области памяти. Слева – значение местоположения: в область памяти, соотнесенную с переменной ch, помещается результат вычитания. В общем случае левый операнд операции присваивания должен быть l-значением. Мы не можем написать следующие выражения:

// ошибки компиляции: значения слева не являются l-значениями

// ошибка: литерал - не l-значение

0 = 1;

// ошибка: арифметическое выражение - не l-значение

salary + salary * 0.10 = new_salary;

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




// файл module0.C
// определяет объект fileName

string fileName;

// ... присвоить fileName значение

// файл module1.C

// использует объект fileName

// увы, не компилируется:

// fileName не определен в module1.C

ifstream input_file( fileName );

С++ требует, чтобы объект был известен до первого обращения к нему. Это вызвано необходимостью гарантировать правильность использования объекта в соответствии с его типом. В нашем примере модуль module1.C

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

переменной:



// файл module1.C
// использует объект fileName

// fileName объявляется, то есть программа получает

// информацию об этом объекте без вторичного его определения

extern string fileName;

ifstream input_file( fileName )

Объявление переменной сообщает компилятору, что объект с данным именем, имеющий данный тип, определен где-то в программе. Память под переменную при ее объявлении не отводится. (Ключевое слово extern рассматривается в разделе 8.2.)

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


Что такое выражение?


Выражение состоит из одного или более операндов, в простейшем случае –  из одного литерала или объекта. Результатом такого выражения является r-значение его операнда. Например:

void mumble() {

    3.14159;

    "melancholia";

    upperBound;

}

Результатом вычисления выражения 3.14159

станет 3.14159

типа double, выражения "melancholia" – адрес первого элемента строки типа const char*. Значение выражения upperBound – это значение объекта upperBound, а его типом будет тип самого объекта.

Более общим случаем выражения является один или более операндов и некоторая операция, применяемая к ним:

salary + raise

ivec[ size/2 ] * delta

first_name + " " + 1ast_name

Операции обозначаются соответствующими знаками. В первом примере сложение применяется к salary и raise. Во втором выражении size

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

Операции, применяемые к одному операнду, называются унарными

(например,

взятие адреса (&) и разыменование (*)), а применяемые к двум операндам – бинарными. Один и тот же символ может обозначать разные операции в зависимости от того, унарна она или бинарна. Так, в выражении

*ptr

* представляет собой унарную операцию разыменования. Значением этого выражения является значение объекта, адрес которого содержится в ptr. Если же написать:

var1 * var2

то звездочка будет обозначать бинарную операцию умножения.

Результатом вычисления выражения всегда, если не оговорено противное, является r-значение. Тип результата арифметического выражения определяется типами операндов. Если операнды имеют разные типы, производится преобразование типов в соответствии с предопределенным набором правил. (Мы детально рассмотрим эти правила в разделе 4.14.)

Выражение может являться составным, то есть объединять в себе несколько подвыражений. Вот, например, выражение, проверяющее на неравенство нулю указатель и объект, на который он указывает (если он на что-то указывает)[7]:


ptr != 0 && *ptr != 0

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



int ival = 1024;
int *ptr = &ival;

то результатом разыменования будет 1024 и оба сравнения дадут истину. Результатом всего выражения также будет истина (оператор &&

обозначает логическое И).

Если посмотреть на этот пример внимательно, можно заметить, что порядок выполнения операций очень важен. Скажем, если бы операция разыменования ptr

производилась до его сравнения с 0, в случае нулевого значения ptr это скорее всего вызвало бы крах программы. В случае операции И порядок действий строго определен: сначала оценивается левый операнд, и если его значение равно false, правый операнд не вычисляется вовсе. Порядок выполнения операций определяется их приоритетами, не всегда очевидными, что вызывает у начинающих программистов на С и С++ множество ошибок. Приоритеты будут приведены в разделе 4.13, а пока мы расскажем обо всех операциях, определенных в С++, начиная с наиболее привычных.


Данные-члены


Данные-члены класса объявляются так же, как переменные. Например, у класса Screen

могут быть следующие данные-члены:

#include <string>

class Screen {

   string             _screen;   // string( _height * _width )

   string::size_type  _cursor;   // текущее положение на экране

   short              _height;   // число строк

   short              _width;    // число колонок

};

Поскольку мы решили использовать строки для внутреннего представления объекта класса Screen, то член _screen

имеет тип string. Член _cursor– это смещение в строке, он применяется для указания текущей позиции на экране. Для него использован переносимый тип string::size_type.

(Тип size_type

рассматривался в разделе 6.8.)

Необязательно объявлять два члена типа short по отдельности. Вот объявление класса Screen, эквивалентное приведенному выше:

class Screen {

/*

 * _ screen адресует строку размером _height * _width

 * _cursor указывает текущую позицию на экране

 * _height и _width - соответственно число строк и колонок

 */

   string             _screen;

   string::size_type  _cursor;

   short              _height, _width;

};

Член класса может иметь любой тип:

class StackScreen {

   int topStack;

   void (*handler)();     // указатель на функцию

   vector<Screen> stack;  // вектор классов

};

Описанные данные-члены называются нестатическими. Класс может иметь также и статические

данные-члены. (У них есть особые свойства, которые мы рассмотрим в разделе 13.5.)

Объявления данных-членов очень похожи на объявления переменных в области видимости блока или пространства имен. Однако их, за исключением статических членов, нельзя явно инициализировать в теле класса:

class First {

   int    memi = 0;    // ошибка

   double memd = 0.0;  // ошибка

};

Данные-члены класса инициализируются с помощью конструктора класса. (Мы рассказывали о конструкторах в разделе 2.3; более подробно они рассматриваются в главе 14.)



Деструктор класса


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

выделяется память для массива символов и присваивается уникальный номер счету. Можно также представить ситуацию, когда нужно получить монопольный доступ к разделяемой памяти или к критической секции потока. Для этого необходима симметричная операция, обеспечивающая автоматическое освобождение памяти или возврат ресурса по завершении времени жизни объекта, – деструктор. Деструктор – это специальная определяемая пользователем функция-член, которая автоматически вызывается, когда объект выходит из области видимости или когда к указателю на объект применяется операция delete. Имя этой функции образовано из имени класса с предшествующим символом “тильда” (~). Деструктор не возвращает значения и не принимает никаких параметров, а следовательно, не может быть перегружен. Хотя разрешается определять несколько таких функций-членов, лишь одна из них будет применяться ко всем объектам класса. Вот, например, деструктор для нашего класса Account:

class Account {

public:

           Account();

     explicit Account( const char*, double=0.0 );

           Account( const Account& );

     ~Account();

     // ...

private:

           char         *_name;

           unsigned int _acct_nmbr;

           double       _balance;

};

inline

Account::~Account()

{

      delete [] _name;

      return_acct_number( _acct_nnmbr );

}

Обратите внимание, что в нашем деструкторе не сбрасываются значения членов:

inline

Account::~Account()

{

      // необходимо

      delete [] _name;

      return_acct_number( _acct_nnmbr );

      // необязательно

      _name = 0;

      _balance = 0.0;

      _acct_nmbr = 0;

}

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

class Point3d {

public:

   // ...

private:

   float x, y, z;

<
};

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

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

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



{

   // начало критического участка программы

#ifdef PROFILE

   Timer t;

#endif

   // критический участок

   // t уничтожается автоматически

   // отображается затраченное время ...
}

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



(1)    #include "Account.h"

(2)    Account global( "James Joyce" );

(3)    int main()

(4)    {

(5)       Account local( "Anna Livia Plurabelle", 10000 );

(6)       Account &loc_ref = global;

(7)       Account *pact = 0;

(8)

(9)       {

(10)          Account local_too( "Stephen Hero" );

(11)          pact = new Account( "Stephen Dedalus" );

(12)      }

(13)

(14)      delete pact;
<


(15)    }

Сколько здесь вызывается конструкторов? Четыре: один для глобального объекта global в строке (2); по одному для каждого из локальных объектов local и local_too в строках (5) и (10) соответственно, и один для объекта, распределенного в хипе, в строке (11). Ни объявление ссылки loc_ref на объект в строке (6), ни объявление указателя pact в строке (7) не приводят к вызову конструктора. Ссылка – это псевдоним для уже сконструированного объекта, в данном случае для global. Указатель также лишь адресует объект, созданный ранее (в данном случае распределенный в хипе, строка (11)), или не адресует никакого объекта (строка (7)).

Аналогично вызываются четыре деструктора: для глобального объекта global, объявленного в строке (2), для двух локальных объектов и для объекта в хипе при вызове delete в строке (14). Однако в программе нет инструкции, с которой можно связать вызов деструктора. Компилятор просто вставляет эти вызовы за последним использованием объекта, но перед закрытием соответствующей области видимости.

Конструкторы и деструкторы глобальных объектов вызываются на стадиях инициализации и завершения выполнения программы. Хотя такие объекты нормально ведут себя при использовании в том файле, где они определены, но их применение в ситуации, когда производятся ссылки через границы файлов, становится в C++ серьезной проблемой.4

Деструктор не вызывается, когда из области видимости выходит ссылка или указатель на объект (сам объект при этом остается).

С++ с помощью внутренних механизмов препятствует применению оператора delete к указателю, не адресующему никакого объекта, так что соответствующие проверки кода необязательны:



// необязательно: неявно выполняется компилятором
if (pact != 0 ) delete pact;

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



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

может быть явно переустановлен для адресации другого объекта только присваиванием его другому auto_ptr):



#include <memory>

#include "Account.h"

Account global( "James Joyce" );

int main()

{

   Account local( "Anna Livia Plurabelle", 10000 );

   Account &loc_ref = global;

   auto_ptr<Account> pact( new Account( "Stephen Dedalus" ));

   {

      Account local_too( "Stephen Hero" );

   }

   // объект auto_ptr уничтожается здесь
}


Деструкторы


Когда заканчивается время жизни объекта производного класса, автоматически вызываются деструкторы производного и базового классов (если они определены), а также деструкторы всех объектов-членов. Например, если имеется объект класса NameQuery:

NameQuery nq( "hyperion" );

то порядок вызова деструкторов следующий: сначала деструктор NameQuery, затем деструктор string для члена _name и наконец деструктор базового класса. В общем случае эта последовательность противоположна порядку вызова конструкторов.

Вот деструкторы нашего базового Query и производных от него (все они объявлены открытыми членами соответствующих классов):

inline Query::

~Query(){ delete _solution; }

inline NotQuery::

~NotQuery(){ delete _op; }

inline OrQuery::

~OrQuery(){ delete _lop; delete _rop; }

inline AndQuery::

~AndQuery(){ delete _lop; delete _rop; }

Отметим два аспекта:

·                  мы не предоставляем явного деструктора NameQuery, потому что никаких специальных действий по очистке его объекта предпринимать не нужно. Деструкторы базового класса и класса string для члена _name

вызываются автоматически;

·                  в деструкторах производных классов оператор delete

применяется к указателю типа Query*. Чтобы вызвать не деструктор Query, а деструктор класса того объекта, который фактически адресуется этим указателем, мы должны объявить деструктор базового Query

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

В нашей реализации неявно подразумевалось, что память для операндов, указатели на которые имеются в объектах классов NotQuery, OrQuery и AndQuery, выделена из хипа. Именно поэтому в деструкторах мы применяли к этим указателям оператор delete. Но язык не позволяет обеспечить истинность такого предположения, так как в нем нет различий между адресами в хипе и вне его. С этой точки зрения наша реализация не застрахована от ошибок.


В разделе 17. 7 мы инкапсулируем выделение памяти и конструирование объектов иерархии Query в управляющий класс UserQuery. Это гарантирует выполнение нашего предположения. На уровне программы в целом следует перегрузить операторы new и delete для классов иерархии. Например, можно поступить следующим образом. Оператор new устанавливает в объекте флажок, говорящий, что память для него выделена из хипа. Перегруженный оператор delete

проверяет этот флажок: если он есть, то память освобождается с помощью стандартного оператора delete.

Упражнение 17.7

Идентифицируйте конструкторы и деструкторы базового и производных классов для той иерархии, которую вы выбрали в упражнении 17.2 (раздел 17.1).

Упражнение 17.8

Измените реализацию класса OrQuery

так, чтобы он был производным от BinaryQuery.

Упражнение 17.9

Найдите ошибку в следующем определении класса:



class Object {

public:

   virtual ~Object();

   virtual string isA();

protected:

   string _isA;

private:

   Object( string s ) : _isA( s ) {}
};

Упражнение 17.10

Дано определение базового класса:



class ConcreteBase

{

public:

   explicit ConcreteBase( int );

   virtual ostream& print( ostream& );

   virtual ~Base();

   static int object_count();

protected:

   int _id;

   static int _object_count;
};

Что неправильно в следующих фрагментах:



(a) class C1 : public

ConcreteBase {

    public:

       C1( int val )

         : _id( _object_count++ ) {}

       // ...
    };



(b) class C2 : public C1 {

    public:

       C2( int val )

         : ConcreteBase( val ), C1( val ) {}

       // ...

    };



(c) class C3 : public C2 {

    public:

       C3( int val )

         : C2( val ), _object_count( val ) {}

       // ...

    };



(d) class C4 : public

ConcreteBase {

    public:

       C4( int val )

         : ConcreteBase ( _id+val ){}

       // ...

    };

Упражнение 17.11

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


Детали разрешения перегрузки функций


В разделе 9.2 мы уже упоминали, что процесс разрешения перегрузки функций состоит из трех шагов:

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

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

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

Теперь мы готовы к тому, чтобы изучить эти шаги более детально.



Динамически размещаемые объекты


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

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

размещаемыми

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

В этом разделе, посвященном динамическим объектам, мы рассмотрим три формы оператора new: для размещения единичного объекта, для размещения массива и третью форму, называемую оператором размещения new (placement new expression). Когда хип исчерпан, этот оператор возбуждает исключение. (Разговор об исключениях будет продолжен в главе 11. В главе 15 мы расскажем об операторах new и delete

применительно к классам.)



Динамическое создание и уничтожение единичных объектов


Оператор new

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

new int;

размещает в хипе один объект типа int. Аналогично в результате выполнения инструкции

new iStack;

там появится один объект класса iStack.

Сам по себе оператор new не слишком полезен. Как можно реально воспользоваться созданным объектом? Одним из аспектов работы с памятью из хипа является то, что размещаемые в ней объекты не имеют имени. Оператор new

возвращает не сам объект, а указатель на него. Все манипуляции с этим объектом производятся косвенно через указатели:

int *pi = new int;

Здесь оператор new

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

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

if ( *pi == 0 )

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

int *pi = new int( 0 );

Константа в скобках задает начальное значение для создаваемого объекта; теперь pi

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

Оператор new

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




int ival = 0;    // создаем объект типа int и инициализируем его 0
int *pi = &ival; // указатель ссылается на этот объект

не считая, конечно, того, что объект, адресуемый pi, создается библиотечной функцией new() и размещается в хипе. Аналогично

iStack *ps = new iStack( 512 );

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

Описанные операторы new

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

(Обработка исключений рассматривается в главе 11.)

Время жизни объекта, на который указывает pi, заканчивается при освобождении памяти, где этот объект размещен. Это происходит, когда pi

передается оператору delete. Например,

delete pi;

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

Глядя на предыдущий пример, вы можете спросить: а что случится, если значение pi по какой-либо причине было нулевым? Не следует ли переписать этот код таким образом:



// необходимо ли это?

if ( pi != 0 )
    delete pi;

Нет. Язык С++ гарантирует, что оператор delete не будет вызывать функцию delete() в случае нулевого операнда. Следовательно, проверка на 0

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



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

объекта, который создается с помощью оператора new во время выполнения. Область памяти, на которую указывает pi, выделена динамически, следовательно, pi является указателем на динамически размещенный объект типа int. Когда в программе встретится оператор delete, эта память  будет освобождена. Однако память, отведенная самому указателю pi, не освобождается, а ее содержимое не изменяется. После выполнения delete

объект pi

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

Оператор delete

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

Ниже приведены примеры опасных и безопасных операторов delete:



void f() {

    int i;

    string str = "dwarves";

    int *pi = &i;

    short *ps = 0;

    double *pd = new doub1e(33);

    delete str; // плохо: str не является динамическим объектом

    delete pi; // плохо: pi ссылается на локальный объект

    delete ps; // безопасно

    delete pd; // безопасно
}

Вот три основные ошибки, связанные с динамическим выделением памяти:

·                  не освободить выделенную память. В таком случае память не возвращается в хип. Эта ошибка получила название утечки



памяти;

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

·                  изменять объект после его удаления. Такое часто случается, поскольку указатель, к которому применяется оператор delete, не обнуляется.

Эти ошибки при работе с динамически выделяемой памятью гораздо легче допустить, нежели обнаружить и исправить. Для того чтобы помочь программисту, стандартная библиотека С++ представляет класс auto_ptr. Мы рассмотрим его в следующем подразделе. После этого мы покажем, как динамически размещать и уничтожать массивы, используя вторую форму операторов new и delete.


Динамическое создание и уничтожение константных объектов


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

const int *pci = new const int(1024);

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

Во-вторых, указатель, возвращаемый выражением  new, должен адресовать константу. В предыдущем примере pci

служит указателем на const int.

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

delete pci;

Хотя операнд оператора delete

имеет тип указателя на const int, эта инструкция является корректной и освобождает область памяти, на которую ссылается pci.

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

const int *pci = new const int[100]; // ошибка



Динамическое создание и уничтожение массивов


Оператор new

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

возвращает указатель на первый элемент массива. Например:

// создание единственного объекта типа int

// с начальным значением 1024

int *pi = new int( 1024 );

// создание массива из 1024 элементов

// элементы не инициализируются

int *pia = new int[ 1024 ];

// создание двумерного массива из 4x1024 элементов

int (*pia2)[ 1024 ] = new int[ 4 ][ 1024 ];

pi содержит адрес единственного элемента типа int, инициализированного значением 1024; pia – адрес первого элемента массива из 1024

элементов; pia2 – адрес начала массива, содержащего четыре массива по 1024

элемента, т.е. pia2

адресует 4096

элементов.

В общем случае массив, размещаемый в хипе, не может быть инициализирован. (В разделе 15.8 мы покажем, как с помощью конструктора по умолчанию присвоить начальное значение динамическому массиву объектов типа класса.) Задавать инициализатор при выделении оператором new памяти под массив не разрешается. Массиву элементов встроенного типа, размещенному в хипе, начальные значения присваиваются с помощью цикла for:

for (int index = 0; index < 1024; ++index )

    pia[ index ] = 0;

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

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


Оператор new

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



const char *noerr = "success";

// ...

const char *err189 = "Error: a function declaration must "
                     "specify a function return type!";

Размер создаваемого с помощью оператора new

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



#include <cstring>

const char *errorTxt;

if (errorFound)

    errorTxt = errl89;

else

    errorTxt = noerr;

int dimension = strlen( errorTxt ) + 1;

char *strl = new char[ dimension ];

// копируем текст ошибки в strl
strcpy( strl, errorTxt );

dimension

разрешается заменить выражением:



// обычная для С++ идиома,

// иногда удивляющая начинающих программистов
char *strl = new char[ str1en( errorTxt ) + 1 ];

Единица, прибавляемая к значению, которое возвращает strlen(), необходима для учета завершающего нулевого символа в C-строке. Отсутствие этой единицы – весьма распространенная ошибка, которую достаточно трудно обнаружить, поскольку она проявляет себя косвенно: происходит затирание какой-либо другой области программы. Почему? Большинство функций, которые обрабатывают массивы, представляющие собой С-строки символов, пробегают по элементам, пока не встретят завершающий нуль.

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

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



int getDim();

// создание двумерного массива

int (*pia3)[ 1024 ] = new int[ getDim() ][ 1024 ]; // правильно

// ошибка: второе измерение задано не константой
<


int **pia4 = new int[ 4 ][ getDim() ];

Оператор delete для уничтожения массива имеет следующую форму:

delete[] str1;

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

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

Чтобы избежать проблем, связанных с управлением динамически выделяемой памятью для массивов, рекомендуется пользоваться контейнерными типами из стандартной библиотеки, такими, как vector, list или string. Они управляют памятью автоматически. (Тип string был представлен в разделе 3.4, тип vector – в разделе 3.10. Подробное описание контейнерных типов см. в главе 6.)


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


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

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

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

До сих пор во всех наших примерах использовалось статическое выделение памяти. Скажем, определение переменной ival

int ival = 1024;

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

С объектом ival

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

int ival2 = ival + 1;

то обращаемся к значению, содержащемуся в переменной ival: прибавляем к нему 1 и инициализируем переменную ival2 этим новым значением, 1025. Каким же образом обратиться к адресу, по которому размещена переменная?

С++ имеет встроенный тип “указатель”, который используется для хранения адресов объектов. Чтобы объявить указатель, содержащий адрес переменной ival, мы должны написать:

int *pint; // указатель на объект типа int


int *pint = new int(1024);

Здесь оператор new

выделяет память под безымянный объект типа int, инициализирует его значением 1024 и возвращает адрес созданного объекта. Этот адрес используется для инициализации указателя pint. Все действия над таким безымянным объектом производятся путем разыменовывания данного указателя, т.к. явно манипулировать динамическим объектом невозможно.

Вторая форма оператора new

выделяет память под массив заданного размера, состоящий из элементов определенного типа:

int *pia = new int[4];

В этом примере память выделяется под массив из четырех элементов типа int. К сожалению, данная форма оператора new не позволяет инициализировать элементы массива.

Некоторую путаницу вносит то, что обе формы оператора new возвращают одинаковый указатель, в нашем примере это указатель на целое. И pint, и pia

объявлены совершенно одинаково, однако pint указывает на единственный объект типа int, а pia – на первый элемент массива из четырех объектов типа int.

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



// освобождение единичного объекта

delete pint;

// освобождение массива
delete[] pia;

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

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



Упражнение 2.3

Объясните разницу между четырьмя объектами:



(a) int ival = 1024;

(b) int *pi = &ival;

(c) int *pi2 = new int(1024);
(d) int *pi3 = new int[1024];

Упражнение 2.4

Что делает следующий фрагмент кода? В чем состоит логическая ошибка? (Отметим, что операция взятия индекса ([]) правильно применена к указателю pia. Объяснение этому факту можно найти в разделе 3.9.2.)



int *pi = new int(10);

int *pia = new int[10];

while ( *pi < 10 ) {

  pia[*pi] = *pi;

  *pi = *pi + 1;

}

delete pi;
delete[] pia;


Директива extern "C" и перегруженные функции A


В разделе 7.7 мы видели, что директиву связывания extern "C"

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

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

// ошибка: для двух перегруженных функций указана директива extern "C"

extern "C" void print( const char* );

extern "C" void print( int );

Приведенный ниже пример перегруженной функции calc() иллюстрирует типичное применение директивы extern "C":

class SmallInt ( /* ... */ );

class BigNum ( /* ... */ );

// написанная на C функция может быть вызвана как из программы,

// написанной на C, так и из программы, написанной на C++.

// функции C++ обрабатывают параметры, являющиеся классами

extern "C" double calc( double );

extern SmallInt calc( const SmallInt& );

extern BigNum calc( const BigNum& );

Написанная на C функция calc()

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

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

Smallint si = 8;

int main() {

   calc( 34 );     // вызывается C-функция calc( double )

   calc( si );     // вызывается функция C++ calc( const SmallInt & )

   // ...

   return 0;

}