Стек — это несложно


Стек — это адаптер (container adaptor), который предоставляет ограниченное подмножество всей функциональности контейнера. Термин адаптер в применении к структуре данных STL означает, что она реализована на основе какой-то другой структуры. По умолчанию стек основан на контейнере типа deque, но при объявлении можно явно указать и другой тип контейнера. Стек поддерживает вставку, удаление и инспекцию элемента, расположенного в первой (top) позиции контейнера. Стек не допускает итераций прохода по своим элементам. Говорят, что стек является структурой данных с дисциплиной доступа "last in first out" (LIFO). Вверху стека расположен элемент, который был помещен в него последним. Только он и может быть выбран в настоящий момент. При отладке следующего фрагмента не забудьте вставить директиву #include <stack>:
void main()
{
//========= Создаем стек целых
stack<Man> s;
s.push(joy);
s.push(joe);
s.push(charlie);
//========= Проверяем очевидные вещи
assert (s.size () == 3);
assert(s.top() == Charlie);
cout « "Stack contents:\n\n";
while (s.size())
{
cout « s.top() « "; ";
//========= Уничтожает top-элемент
s.pop(); }
assert(s.empty());
}
  
Контейнеры типа queue
Очередь — это тоже,адаптер, который предоставляет ограниченное подмножество функциональности контейнера. Говорят, что очередь — это структура данных с дисциплиной доступа "first in first out" (FIFO). Элементы, вставляемые в конец очереди, могут быть выбраны спереди. Это означает, что метод queue:: front () возвращает самый «старый» элемент, то есть тот, который был вставлен в очередь least recently — первым из тех, что еще живы. Очередь, так же как и стек, не допускает итераций прохода по своим элементам. По умолчанию она основана на контейнере типа deque. Сравнение стека и очереди приведены в следующем фрагменте (Подключите <queue>):
void main ()
{
//========== Массив объектов класса Man
Man ar[] =
{
joy, mаrу, win
};
uint size = sizeof(ar)/sizeof(Man);
//========== Создаем с.тек объектов класса Man
stack<Man> s;
for (uint i=0; i<size; i++) s.push(ar[i]);
cout « "Stack of Man:\n\n";
while (s.size ())
{
cout « s.top() « "; ";
s.pop ();
}
//========== Создаем очередь объектов класса Man
queue<Man> q;
for (i=0; Ksize; i++) q.push(ar[i]);
cout « "\n\nQueue of Man:\n\n";
while (q.size ())
{
cout « q.front() « "; ";
q.pop(); }
cout«"\n\n";
}

Контейнеры типа priority_queue


Очередь с приоритетами тоже является адаптером, который позволяет вставку элементов, инспекцию и удаление верхнего (top) элемента. Она не допускает итераций прохода по своим элементам. Ее характерным отличием является то, что верхний элемент является самым большим в том смысле, в котором в шаблоне используется функциональный объект (Compare — сравнение объектов). Для разнообразия приведем объявление шаблона:
template
< 
class Type,
class Container=vector<Type>,
class Compare=less<typename Container : : value__type>

Отсюда видно, что по умолчанию очередь с приоритетами основана на контейнере типа vector и для сравнения приоритетов она использует предикат lesso. Для объектов класса Man — это внешняя friend-функция operator< (), которая упорядочивает последовательность по возрасту. Но очередь с приоритетами должна расставить элементы по убыванию приоритетов. Проверим это утверждение с помощью следующего фрагмента:
void main () {
//===== Priority queue (by age)
priority_queue<Man> men;
men.push (zoran);
//== Для проверки поведения вставляем объект повторно
men.push (zoran);
men.push (joy);
men.push (mela); men.push (win);
cout«"priority_queue size: "«men. size () «endl;
int i=0;
while ('men.empty())
{
cout « "\n"« ++i«". "«men.top();
men.pop();
}
}
Выходом этой программы будет такой текст:
priority_queue size: 5
1. Winton Kelly, Age: 50
2. Zoran Todorovitch, Age: 27
3. Zoran Todorovitch, Age: 27
4. Joy Amore, Age: 18
5. Melissa Robinson, Age: 9
Как видно, объекты выстроены по убыванию возраста. Очереди и стеки допускают повторение элементов.
  
Работа с потоками
Шаблон класса if stream позволяет работать с файловыми потоками и производить ввод объектов произвольного типа. Удобно вводить объекты прямо в контейнер. Специальный итератор (istream_iterator) помогает в этом случае воспользоваться алгоритмами (например, сору). При достижении конца потока (end of stream) итератор принимает специальное значение, которое служит барьером выхода за пределы потока (past-the-end iterator). В примере, приведенном ниже, используется еще один тип итератора (back_insert_iterator). Он является адаптером, позволяющим вставлять элементы в конец последовательности. Если использовать прямой inserter, то при чтении из файла последовательность будет реверсирована (перевернута). Позиционирование в потоке осуществляется с помощью метода seekg, техника использования которого также демонстрируется в примере:
void main ()
{
//========== Вектор строк
vector<string> v;
v.push_back("Something in the way ");
v.push_back("it works distracts me ");
v.push_back("like no other matter");
pr(v,"Before writing to file");
//========== Запрашиваем имя файла
cout « "\nEnter File Name: ";
string fn, text; cin » fn;
//========== Приписываем расширение
int pos = fn.rfind(".");
if (pos > 0)
fn.erase(pos);
fn += ".txt";
//========== Создаем и открываем поток
ofstream os(fn.c_str());
//========== Определяем входной и выходной потоки
typedef istream_iterator<string, char,
char_traits<char> > Strln;
typedef ostream_iterator<string, char,
char_traits<char> > StrOut;
//========== Копируем контейнер в выходной поток
copy (v.begin(), v.end(), StrOut(os,"\n"));
os.close();
//========== Открываем файл для чтения
if stream is(fn.c_str());
//========= Пропуск 17 символов
is.seekg(17) ;
is » text;
cout « "\n\nStream Positioning:\n\n" « "17 bytes:\t\t" « text « endl;
//========== Устанавливаем в начало потока
is.seekg(0, ios_base::beg);
is » text;
cout « "0 bytes:\t\t" « text « endl;
//========== Сдвигаем на 8 символов от конца
is.seekg(-8, ios_base::end);
is » text;
cout « "-8 bytes from end:\t" « text « "\n\n";
//========== Устанавливаем в начало потока
is.seekg(0, ios_base::beg);
v.clear () ;
//========== Копируем в контейнер
copy(Strln(is),Strln(),back_inserter(v));
pr(v,"After reading from file");
cout«"\n\n"; }
Программа производит следующий выход:
Before writing to file # Sequence:
1. Something in the way
2. it works distracts me
3. like no other matter
Enter File Name: test
Stream Positioning:
17 bytes: way
0 bytes: Something
-8 bytes from end: matter
After reading from file # Sequence:
1. Something
2. in
3. the
4. way
5. it
6. works
7. distracts
8. me
9. like
10. no
11. other
12. matter

Примеры использования string


Тип string является специализацией шаблона basic_string для элементов типа char и определен как:
typedef basic_string<char> string;
Шаблон basic_string предоставляет типы и методы, схожие с теми, что предоставляют стандартные контейнеры, но он имеет много специфических методов, которые позволяют достаточно гибко манипулировать как строками, так и их частями (подстроками). Минимизация операций копирования строк, которой гордится MFC-класс cstring, на самом деле приводит к труднообнаруживаемым и невоспроизводимым (irreproducible) ошибкам, которые очень сильно портят жизнь программистам. Я с интересом узнал, что члены комиссии по утверждению стандарта C++ анализируют ошибки, возникающие из-за совместного использования двумя переменными строкового типа одной и той же области памяти, и пытаются выработать спецификации относительно времени жизни ссылок на символы строки. Если вы запутались в этой фразе, то следующий фрагмент программы, который комиссия использует в качестве теста, должен прояснить ситуацию. При выполнении он выведет строку «Wrong» или «Right», что означает, что ваша реализация string ненадежна или, скорее всего, надежна. Если она выведет строку «Right», то это еще не означает, что ваша реализация надежна. Ошибки могут всплыть в многопоточных приложениях, когда разные потоки работают с одной строкой символов:
//====== Две тестовые текстовые строки
string source("Test"), target;
//====== Ссылка на второй символ в строке
char& с = source[1];
//=====- Если данные не копируются при присвоении
target = source;
//====== то это присвоение изменит обе строки
с = ' z ' ;
//====== Этот тест позволяет выяснить ситуацию
cout « (target[l] == 'z1 ? "\nWrong" : "\nRight");
Здесь мы использовали ссылку, но аналогичное поведение обнаруживает и итератор. Вы можете объявить и использовать его так:
string::iterator it = source.begin()+1; *it = z1 ;
В рассматриваемой версии Studio.Net я с удовлетворением отметил, что тест выводит строку «Right». Следующий фрагмент демонстрирует технику обрезания «пустого» текста в начале и конце строки. Она не очень эффективна, но вполне пригодна для строк небольшого размера:
//====== Множество пустых символов
char White " \n\t\r";
//====== Ищем реальное начало строки
//====== и усекаем лишние символы слева
s = s.substr(s.find_first_not_of(White));
//====== Переворачиваем строку и повторяем процедуру
reverse (s .begin () , s.endO);
s = s.substr(s.find_first_not_of(White));
//====== Вновь ставим строку на ноги
reverse (s .begin (), s.end());
Интересный пример, иллюстрирующий работу со строками, я увидел в MSDN. Нечто вроде секретного детского языка под названием Pig Latin (свинячья латынь). Алгоритм засекречивания слов состоит в том, что от каждого слова отрывают первую букву, переставляют ее в конец слова, а затем добавляют туда окончание «ау». Игра, очевидно, имеет свою историю. Приведем коды функции, которая реализует этот алгоритм и возвращает засекреченную строку:
//====== Преобразование строки по принципу Pig Latin
string PigLatin (const strings s)
{
string res;
//======= Перечень разделителей слов
string sep(" .,;:?");
//======= Длина всей строки
uint size = s.lengthO;
for (uint start=0, end=0, cur=0; cur < size; cur=end+l)
{
//==== Ищем позицию начала слова, начиная с cur
start = s.find_first_not_of(sep, cur) ;
//==== Копируем разделители между словами
res += s.substr(cur, start - cur) ;
//==== Ищем позицию конца слова, начиная со start
end = s.find_first_of(sep, start) ;
//==== Корректируем позицию конца слова
end = (end >= size) ? size : end - 1 ;
//==== Преобразуем по алгоритму
res += s. substr (start-t-1, end-start) + s [start] +"ay"; )
return res;
}
Проверьте работу алгоритма с помощью следующего теста, который надо вставить внутрь функции main:
string s("she,sells;
sea shells by the sea shore");
cout « "Source string: " « s « endl;
cout « "\nPig Latin(s): " « PigLatin(s);
В результате вы увидите такой текст:
Source string: she,sells;
sea shells by the sea shore
Pig Latin(s): hesay,ellssay;
easay hellssay ybay hetay easay horesay
  
Полезные константы
STL имеет много полезных констант. Проверьте свои знания основ информатики. Знаете ли вы смысл констант, приведенных ниже? Для их использования вам потребуется подключить такие файлы заголовков:
#include <limits>
#include <climits>
#finclude <cfloat>
#finclude <numeric>
Вот фрагмент, который выводит некоторые из констант и по-английски описывает их смысл. Русский язык отказывается работать на моем компьютере в окне консольного приложения. Думаю, что существуют программисты, которые зарабатывают свой хлеб, имея смутное представление о существовании этих констант:
//===== Сначала простые, которые знают все
cout « "\n Is a char signed? "
« numeric_limits<char>::is_signed;
cout « "\n The minimum value for char is: "
« (int)numeric_limits<char>::min();
cout « "\n The maximum value for char is: "
« (int)numeric_limits<char>::max();
cout « "\n The minimum value for int is: "
« numeric_limits<int>::min();
cout « "\n The maximum value for int is: "
« numeric_limits<int>::max();
cout « "\n Is a integer an integer? "
« numeric_limits<int>::is_integer;
cout « "\n Is a float an integer? "
« numeric_limits<float>::is_integer;
cout « "\n Is a integer exact? "
« numeric_limits<int>::is_exact;
cout « "\n Is a float exact? "
« numeric_limits<float>::is_exact;
//===== Теперь более сложные
cout « "\n Number of bits in mantissa (double) : "
« DBL_MANT_DIG; cout « "\n Number of bits in mantissa (float): "
« FLT_MANT_DIG;
cout <<"\n The number of digits representble " "in base 10 for float is "
« numeric_limits<float>::digitslO;
cout « "\n The radix for float is: "
« numeric_limits<float>::radix;
cout « "\n The epsilon for float is: "
« numeric_limits<float>::epsilon() ;
cout « "\n The round error for float is: "
« numeric_limits<float>::round_error();
cout « "\n The minimum exponent for float is: "
« numeric_limits<float>::min_exponent;
cout « "\n The minimum exponent in base 10: "

« numeric_limits<float>::min_exponentlO;
cout « "\n The maximum exponent is: "
« numeric_limits<float>::max_exponent;
cout « "\n The maximum exponent in base 10: "
« numeric_limits<float>::max_exponentlO;
cout « "\n Can float represent positive infinity? "
« numeric_limits<float>::has_infinity;
cout « "\n Can double represent positive infinity? "
« numeric_limits<double>::has_infinity;
cout « "\n Can int represent positive infinity? "
« numeric_limits<int>::has_infinity;
cout « "\n Can float represent a NaN? "
« numeric_limits<float>::has_quiet_NaN;
cout « "\n Can float represent a signaling NaN? "
« numeric_limits<float>::has_signaling_NaN;
//===== Теперь еще более сложные
cout « "\n Does float allow denormalized values? "
« numeric_limits<float>::has_denorm;
cout « "\n Does float detect denormalization loss? "
« numeric_limits<float>::has_denorm_loss;
cout « "\n Representation of positive infinity for"
" float: "« numeric_limits<float>::infinity();
cout « "\n Representation of quiet NaN for float: "
« numeric_limits<float>::quiet_NaN();
cout « "\n Minimum denormalized number for float: "
« numeric_limits<float>::denorm_min();
cout « "\n Minimum positive denormalized value for"
" float " « numeric_limits<float>::denorm_min();
cout « "\n Does float adhere to IEC 559 standard? "
« numeric_limits<float>::is_iec559; cout « "\n Is float bounded? "
« numeric_limits<float>::is_bounded;
cout « "\n Is float modulo? "
« numeric_limits<float>::is_modulo;
cout « "\n is int modulo? "
« numeric_limits<float>::is_modulo;
cout « "\n Is trapping implemented for float? "
« numeric_limits<float>::traps;
cout « "\n Is tinyness detected before rounding? "
« numeric_limits<float>::tinyness_before;
cout « "\n What is the rounding style for float? "
« (int)numeric_limits<float>::round_style;
cout « "\n What is the rounding style for int? "
« (int)numeric_limits<int>::round_style;
//===== Теперь из другой оперы
cout « "\n Floating digits " « FLT_DIG;
cout « "\n Smallest such that 1.0+DBL_EPSILON !=1.0: "
« DBL_EPSILON;
cout « "\n LDBL_MIN_EXP: " « LDBL_MIN_EXP;
cout « "\n LDBL_EPSILON: " « LDBL_EPSILON;
cout « "\n Exponent radix: " « _DBL_RADIX;
Незнание констант типа DBL_EPSILON или DBL_MANT_DIG довольно сильно ограничивает квалификацию программиста, поэтому советую внимательно исследовать вывод, производимый данным фрагментом, и, возможно, обратиться к специальным изданиям по архитектуре компьютера или учебникам с целью ликвидировать пробелы в знаниях в этой области.
  
Шаблон классов valarray
Этот шаблон разработан для оптимизации вычислений, производимых над массивами чисел фиксиррванного размера. Valarray похож на контейнер, но он им не является. Вы не можете динамически и эффективно наращивать его размер. Он, как и контейнер, может изменять свои размеры, используя метод resize, но при этом имеющиеся данные разрушаются. Главным преимуществом использования valarray является эффективность проведения операций сразу над всеми элементами последовательности. Предположим, вы хотите построить график функции у = sin(x) и имеете процедуру, которая сделает это с учетом масштабирования, оцифровки осей и всяких других удобств. Вашей задачей является лишь сформировать данные для графика и подать их на вход этой процедуры. Использование valarray даст преимущество в легкости манипулирования данными и эффективности выполнения. Для простоты выберем шаг изменения координаты х, равный л/3,
Примечание
C целью экономии места я обычно не привожу директивы препроцессора, которые, конечно же, должны предшествовать каждому из рассматриваемых фрагментов. Большинство читателей, я уверен, успешно решают эту проблему сами, так как сообщения об ошибках обычно довольно ясно указывают на недостающее описание. Но при работе с библиотекой STL окно сообщений ведет себя не совсем так, как при работе с MFC. Незначительный пропуск или неточность со стороны программиста порой приводят к лавине предупреждений и ошибок, анализ которых превращается в испытание для нервной системы. Здесь у компании Microsoft еще довольно много работы. Учитывая сказанное, следующий фрагмент приведен со списком директив, необходимых для его работы.
#include <iostream>
#include <algorithm>
#include <valarray>
#include <limits>
using namespace std;
void main() { //======== Вспомогательные переменные
double PI = atan(l.)*4.,
dx = PI/3., // Шаг изменения
xf = 2*PI - dx/2.;
// Барьер
int i = 0,
size = int(ceil(xf/dx)); // Количество точек
//======== Создаем два объекта типа valarray
valarray<double> vx(size), vy(size);
//======== Абсциссы точек вычисляются в цикле
for (double х=0.;
х < xf; х += dx) vx[i++] = х;
//======== Ординаты вычисляются без помощи цикла
vy = sin(vx);
cout«"Valarrays of x and sin(x)\n";
for (i=0; i < size; i++)
cout«"\nx = " « vx[i] «" у = "« vy[i];
}
Теперь усложним задачу. Представим, что надо численно продифференцировать функцию, заданную в дискретном множестве точек. Вы знаете, что конечные разности позволяют аппроксимировать производные, то есть производить численное дифференцирование. В STL есть алгоритм adjacent_dif ference, который вычисляет первые конечные разности в указанном диапазоне последовательности. Здесь важно вспомнить, что valarray не является контейнером и поэтому не поддерживает итераторов. Но алгоритмы STL принимают в качестве аргументов как итераторы, так обычные указатели. Мы воспользуемся этим фактом, а также тем, что элементы valarray расположены в памяти подряд.
Результат дифференцирования надо поместить в другую последовательность типа valarray, которую после этого можно эффективно нормировать, поделив сразу все ее элементы на шаг дискретизации вдоль оси х. Добавьте директиву # include <numeric>, вставьте следующий текст в конец предыдущего фрагмента и, пожалуй, увеличьте количество точек, заменив присвоение dx = Pi/З. на dx = Pi/10:
//======= Конструктор создает valarray нужного размера
valarray<double> vd(size);
//======= Алгоритм вычисляет конечные разности
adjacent_difference(&vy[0], &vy[size], &vd[0]);
//======= Все элементы valarray делятся на dx
vd /= dx;
//======= Мы проверяем результат
cout«"\n\nValarray of differences\n";
for (i=l; i < size; i++)
cout«"\nx = " « vx[i] «" у = "« vd[i];
Отметьте, что в первой точке (с нулевым индексом) будет ошибка, поэтому мы ее не выводим. Остальные элементы результирующей последовательности чисел (valarray vd) должны вести себя как у = cos(x). В качестве третьего параметра функции adjacent_dif ference нельзя задать просто vd, так как в отличие от обычного массива имя vd не является адресом его первого элемента. Шаблон классов valarray имеет некоторое, весьма ограниченное количество методов, которые позволяют производить манипуляции с данными, среди которых стоит отметить: min, max, sum, shift, cshift, apply. Приведем фрагмент, иллюстрирующий их использование:
//======= Функциональный объект, применяемый к каждому
//======= элементу valarray
double Sharp (double x)
{
return x != 0. ? l/(x*x) : DBL_MAX;
}
//======= Функция для вывода valarray
void out(char* head, valarray<double>& v)
{
cout « '\n' « head << '\n';
for (unsigned i=0; i < v.size(); i++)
cout«"\nv[" « i « "] = " « v[i];
cout «'\n';
}
void main()
{
int size = 11;
valarray<double> vx(size), vy(size);
//======== Заполняем диапазон от -1 до 1
for (int i=0; i < size; i++)
{
vx[i] = i/5. - 1.;
}
out("Initial valarray", vx);
//======== Вычисляем сумму всех элементов
cout « "\nsum = " « vx.sum() « endl;
//======== Применяем свое преобразование
vy = vx.apply (Sharp);
//======== Получили "острую" функцию
out("After apply", vy);
//======== Вычисляем min и max
cout « "\n\nmin = " « vy.min() « " max = " « vy.max();
}
При положительных значениях аргумента метод shift используется для сдвига всей последовательности влево или при отрицательных значениях — вправо. Метод cshif t представляет собой циклическую модификацию метода shift. Заметьте, что все рассмотренные методы возвращают новую последовательность типа valarray и не имеют модификаций, работающих в режиме in-place, что, на мой взгляд, является ощутимым недостатком этого типа данных. Вы можете проверить работу сдвигов, добавив такие строки:
//======== Циклический сдвиг на 2 позиции влево
valarray<double> r =vy.cshift(2);
out("After cyclic 2 digits left shift", r) ;
//======== Сдвиг на 2 позиции вправо
r =r.shift(-2);
out("After 2 digits right shift", r);
  
Сечения массива
Проблемы оптимизации работы с матрицами давно волнуют создателей компиляторов. В то далекое время, когда решения задач электродинамики и вообще краевых задач матфизики еще интересовали влиятельных людей нашей страны (скорее, научные авторитеты убеждали их, что такие задачи следует решать), мы, используя язык PL/I или FORTRAN, конечно же, хранили и обрабатывали матрицы в одномерных массивах. Дело в том, что выбор одного элемента из более естественного для матриц двухмерного массива обходился дорого. Выработалась особая техника работы с одномерными массивами, хранящими матрицы (обычно разреженные). В языке C++ операция выбора элемента из двухмерного динамического массива не намного дороже, чем из одномерного (да и скорости изменились), поэтому острота проблемы спала. Тем не менее проблема экономии времени при решения сложных краевых задач не ушла в прошлое.
STL имеет пару вспомогательных классов: slice и gslice, которые созданы для того, чтобы было удобно работать со срезами (сечениями) одномерных массивов. Если вы храните двухмерную матрицу в последовательности типа valarray, то элементы одной строки матрицы или одного ее столбца можно представить в виде сечения, то есть определенной части всей последовательности. Конструктор класса slice определяет закономерность, в соответствии с которой будут выбираться элементы последовательности, чтобы образовать срез. Например, объект slice s(0, n , 2); представляет собой сечение из п элементов последовательности. Элементы выбираются начиная с нулевого, через один, то есть с шагом 2. Если вы храните матрицу пхп в последовательности типа valarray и при этом она упорядочена по строкам (сначала первая строка, затем вторая, и т. д.), то третью строку матрицы можно выбрать с помощью сечения:
slice s (2*n, n , 1);
Действительно, параметры указывают, что надо пропустить 2*n элементов, затем выбрать n элементов с шагом по одному. Если матрица хранится a la FORTRAN, то есть по столбцам, то для выбора той же строки надо определить сечение:
slice s (2, n , n);
Пропускаются два элемента, затем выбирается n элементов с шагом п. Вы, конечно, поняли, как создать сечение, но не поняли, какое отношение оно имеет к последовательности valarray, так как она не фигурирует в приведенных выражениях. Да, синтаксис, связывающий срез с valarray, несколько необычен, хотя вполне логичен:
int n = 5, // Размерность матрицы n (размером пхп) пп = п*п;
// Размерность valarray
//=== Создаем матрицу (одномерную последовательность)
valarray<double> a (nn);
//=== Генерируем ее элементы по закону f (Пока его нет)
generate (&a[0], &a[nn], f) ;
//====== Создаем сечение
slice s (0, n , 1);
//====== Выделяем сечение (первую строку,
//====== если матрица хранится по строкам)
valarray<double> v = a[s];
Вы видите, что объект s класса slice помещается в то место, куда мы обычно помещаем целочисленный индекс массива или последовательности. Такая интерпретация операции [ ] непривычна. Вы, вероятно, догадались, что роль объекта s в приведенном фрагменте является чисто эпизодической. Можно обойтись и без него, заменив его временным безымянным объектом, который создаст компилятор. При этом конструкция выражения будет более эффективной, но и более головоломной. Последние две строки фрагмента можно заменить одной строкой:
valarray<double> v = afslice (0, n , 1);
Подведем итоги. В этом уроке мы оценили возможности библиотеки STL и сделали вывод, что она, очевидно, имеет гораздо больше достоинств, чем недостатков. Необходимо регулярно тренировать технику ее использования. В этой задаче может помочь сеть Интернет, в которой появляется все больше сайтов, уделяющих внимание STL. Кроме того, мы:

  • вспомнили, как создавать шаблоны функций и шаблоны классов;
  • узнали, что стандартные контейнеры делятся на последовательности и ассоциативные контейнеры;
  • узнали, как пользоваться предикатами и функциональными объектами;
  • познакомились с возможностями связывателей, адаптеров и отрицателей;
  • узнали, как шаблоны пар помогают работать с ассоциативными контейнерами типа тар;
  • получили представление об использовании очередей и стека;
  • оценили возможности текстовых строк типа string;
  • научились пользоваться итераторами различного типа, в том числе и для управления потоками ввода-вывода;
  • узнали о наличии большого количества полезных констант;
  • поработали с последовательностями типа valarray и их сечениями;
  • опробовали некоторые алгоритмы управления последовательностями.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г. Яндекс.Метрика