Вы находитесь на странице: 1из 5

Векторы и итераторы.

Наряду с обычными массивами, доставшимися языку C++ в наследство от C, в C++ имеются и более удобные
средства для решения задачи хранения последовательности элементов одного типа, доступных по номеру.
Наиболее близкое по устройству и возможностям к массивам средство такого рода называется vector. Чтобы его
использовать, нужно в начале программы написать #include <vector>
Завести в программе вектор (в программировании вектор — это просто последовательность элементов одного типа)
из элементов определенного типа можно, написав

vector<тип_элемента> имя_вектора;

Такое определение переменной заводит пустой вектор (без элементов; поскольку элементы можно добавлять позже,
это осмысленно). Инициализировать элементы вектора можно, как и обычного массива, указав список значений эле-
ментов, через запятую и в фигурных скобках. Доступ к элементам вектора осуществляется так же, как и к элементам
массива.
Однако, возможности у вектора гораздо шире, чем у массива. Например, можно изменять количество его элементов,
написав
имя_вектора.resize(новый_размер);
Добавить новый элемент в конец вектора можно, написав
имя_вектора.push_back(новый_элемент);
Также, если можно сравнивать элементы вектора, то можно сравнивать и сделанные из них векторы (лексикогра-
фически). Имея вектор, можно узнать число его элементов, написав
имя_вектора.size();
Векторы можно присваивать один другому; при этом, старое значение вектора из левой части операции присваивания
полностью удаляется и заменяется копией вектора из правой части.
Также, в отличие от массивов, векторы можно передавать в функции и возвращать из них, как по значению, так
и по ссылке. При передаче по значению, векторы копируются.
Рассмотренный до сих пор способ хранения последовательности элементов годился только для одномерных после-
довательностей. Однако, конструкцию вектора можно использовать и для построения матриц (аналогов двумерных
массивов). Для того, чтобы завести матрицу, можно написать так:
vector<vector<тип_элемента>> имя_матрицы;
При этом доступ к элементам матрицы будет осуществляться обычным способом.
В этом случае, как и для двумерных динамических массивов, мы можем иметь в нашей матрице строки разной
длины.
Можно аналогично завести и трехмерный массив, но такое встречается довольно редко.
Роль указателей на элементы для векторов играют так называемые итераторы. Итератор — нечто, указывающее
на (выбирающее) элемент вектора.
Завести в программе итератор можно следующим способом:
vector<тип_элемента>::iterator имя_итератора;
Итераторы тоже можно присваисвать один другому, передавать в функции и т. д. В качестве начальных значений
итератора для вектора можно использовать следующие:
имя_вектора.begin()
Такое значение итератора указывает на первый элемент соответствующего вектора. Также, имеется некоторое спе-
циальное значение (как nullptr для указателей), «указывающее» на фиктивный элемент вектора, расположенный за
последним его элементом:
имя_вектора.end()
Если вектор пустой, последние два выражения возвращают одно и то же.
Пользуясь итератором для указания позиции в векторе, можно вставлять в вектор элементы и даже диапазоны
при помощи следующей записи:
имя_вектора.insert(куда, что)

1
Здесь куда — итератор, указывающий на элемент, непосредственно перед которым вставляются элементы, а что —
элемент (или диапазон), который нужно вставить. Если нужно вставить элементы в конец вектора, нужно в качестве
позиции указать итератор, возвращенный имя_вектора.end(). Возвращает insert итератор, указывающий на первый
из только что вставленных элементов. Это важно, поскольку вставка в вектор новых элементов может привести к
перемещению содержимого вектора в памяти на новое место, что, в свою очередь, сделает все имеющиеся на данный
момент итераторы данного вектора недействительными.
Также можно удалять из вектора элементы в заданной (итератором) позиции и диапазоны (как всегда, диапазоны
задаются парой итераторов):
имя_вектора.erase(что)
Эта конструкция имеет и результат — она возвращает итератор, указывающий на новое положение первого элемента,
следующего за удаленным(и). Можно удалять из вектора элементы не только по их позиции, но и по значению:

имя_вектора.erase(
remove(имя_вектора.begin(),имя_вектора.end(), значение),
имя_вектора.end());
Можно даже удалять элементы по их свойствам:

имя_вектора.erase(
remove_if(имя_вектора.begin(),имя_вектора.end(), предикат),
имя_вектора.end());
Здесь предикат — функция с одним параметром типа элементов вектора, возвращающая логическое значение. Удалены
из векотра будут те элементы, на которых эта функция принимает значение true.
Удалить все элементы вектора можно, написав короче:
имя_вектора.clear()
Как и в случае указателей, для итераторов вектора имеются аналогичные арифметические операции. Более того,
для получения того элемента, на который указывает итератор, его можно разыменовать с использованием такого же
синтаксиса, как и для указателей (одноместная операция *). А вот пользоваться операцией взятия адреса для полу-
чения итератора, указывающего на конкретный элемент вектора, увы, нельзя — для этого нужно сдвигать итераторы
(алгоритм advance), полученные при помощи вышеописанных выражений с использованием begin и end.
В качестве примера рассмотрим функцию, выводящую на экран элементы вектора из целых чисел, через пробел.
void print(vector<int> V)
{
vector<int>::iterator i;
for(i = V.begin(); i != V.end(); ++i)
cout<<*i<<" ";
}
Оказывается, поскольку вектор имеет такое понятие итератора, его можно использовать и в более короткой форме
цикла for для перебора элементов вектора. Так что текст этой функции может быть резко упрощен следующим
образом:
void print(vector<int> V)
{
for(int x: V)
cout<<x<<" ";
}
Так же, как и указатели, итераторы могут быть висячими. Если у нас есть итератор i, указывающий на какой-то
элемент вектора, то после добавления в вектор новых элементов этот вектор может быть перемещен в памяти, что
приведет к тому, что i будет указывать на освобожденную память.
Векторы и итераторы позволяют воспользоваться большим количеством библиотечных функций, которые назы-
ваются алгоритмами (#include <algorithm>).
Например, имеется алгоритм copy, переписывающий элементы из одной последовательности в другую. Чтобы им
воспользоваться, надо написать
copy(диапазон, куда);

2
Здесь диапазон — два итератора через запятую; первый из них указывает на начало диапазона, второй — на элемент,
следующий непосредственно за последним элементом в диапазоне. Куда — итератор, указывающий на место для
первого из копируемых элементов; в процессе копирования он сдвигается на одну позицию вперед после копирования
каждого элемента.
Этот алгоритм позволяет решать широкий набор задач. Например, он позволяет переписывать элементы из на-
стоящего массива в вектор или наоборот (в первом случае в качестве диапазона указываются обычные указатели
на первый и следующий за последним элементы). Также он позволяет вводить и выводить данные, содержащиеся в
массиве или векторе. Для этого нужно подключить #include <iterator> и для ввода элементов писать
istream_iterator<тип_элементов> b(поток), e;
copy(b, e, куда);
а для вывода —
ostream_iterator<тип_элементов> i(поток, разделитель);
copy(диапазон, i);
Этот алгоритм позволяет даже дописывать элементы из диапазона в последовательность, для чего нужно написать
copy(диапазон, back_inserter(имя_вектора));
При использовании алгоритма copy для ввода элементов массива или вектора с клавиатуры (из потока cin) ввод
элементов будет происходить до тех пор, пока не кончится содержимое потока cin. Чтобы указать компьютеру на
конец ввода из этого потока, при вводе с клавиатуры нужно нажать комбинацию клавиш Ctrl-Z (иногда требуется,
чтобы это происходило на пустой строчке, т. е. после последнего элемента данных была нажата клавиша «ввод».)
Однако, после этого весь ввод с клавиатуры блокируется до тех пор, пока в программе не будет выполнен оператор
cin.clear(); .
Также имеется алгоритм find, позволяющий искать первое вхождение конкретного значения в последовательность:
имя_итератора = find(диапазон, значение);
Как и в случае copy, диапазон задается двумя итераторами, и может находиться в обычном массиве или векторе.
Если значение не найдено, возвращается второй итератор из диапазона, т. е. итератор элемента, непосредственно
следующего за последним, входящим в диапазон.
Кроме задания пустого вектора или с явно указанными элементами (как для массивов), вектор можно создать по
диапазону, который будет скопирован. Также, если нужно перезаписать вектор полностью элементами из указанного
диапазона, можно написать так:
имя_вектора.assign(диапазон);
Тип вектора имеет параметр — тип его элемента. Такие типы называются шаблонными. Для работы с ними удоб-
но подходят шаблонные функции, позволяющие задавать способ обработки объектов шаблонных типов для любых
значений параметров. Например, функция, выводящая на экран содержимое вектора из элементов любого типа будет
выглядеть так:
template<class T>
void print(vector<T> V)
{
typename vector<T>::iterator i;
for(i = V.begin(); i != V.end(); ++i)
cout<<*i<<" ";
}
Здесь использовано ключевое слово typename для указания компилятору на то, что выражение vector<T>::iterator
на самом деле является именем типа (как мы увидим позже, пока точно не известно значение шаблонного параметра
T, такого рода выражения могут означать все, что угодно, например, константу в перечислимом типе, и т. д.).
То же самое можно написать и проще, так:
template<class T>
void print(vector<T> V)
{
for(T x: V)
cout<<x<<" ";
}

3
Нередко такого рода функции накладывают определенные ограничения на значения своих шаблонных парамет-
ров. Например, только что рассмотренная функция вывода предполагает, что объеты типа T можно выводить в поток
вывода обычным способом. К сожалению, такого рода ограничения проверяются только на момент построения такой
функции для конкретного типа, являющегося шаблонным параметром. Средство решения этой проблемы, позволяю-
щее описывать такого рода требования прямо в шаблонном заголовке шаблонной функции, появилось и называется
«понятия» (concepts), правда, оно до сих пор не входит в стандарт C++.
Задачи.
0. Написать шаблонную функцию input, вводящую вектор с клавиатуры.
1. Написать шаблонную функцию part, принимающую вектор и возвращающую а) первую половину, б) среднюю
треть его элементов (тоже как вектор).
2. Написать шаблонную функцию concat, принимающую два вектора и возвращающую их сцепление (сначала идут
элементы первого, а затем второго).
2. Написать шаблонную функцию repeat, принимающую вектор v и неотрицательное целое число n, и возвращаю-
щую новый вектор, полученный повторением вектора v n раз.
3. Написать шаблонную функцию subseq, принимающую два вектора и проверяющую, что второй из них явля-
ется подпоследовательностью первого (например, для векторов (1,2,3,4,5,6,7,8,9) и (1,5,7,8) ответ будет true, а для
(1,2,3,4,5,6,7,8,9) и (2,1,5,7,8) ответ будет false).
4. Написать шаблонную функцию enlarge, принимающую вектор по ссылке и вставляющую между каждыми со-
седними элементами их полусумму.
5. Написать функцию shorten, принимающую вектор из целых чисел по ссылке и удаляющую элементы, стоящие
между соседними (т. е. такими, между которыми нет единиц и двоек) 1 и 2 (именно в таком порядке).
6. Написать шаблонную функцию co, принимающую два вектора и возвращающую число вхождений второго век-
тора, как подпоследовательность из элементов, идущих подряд (а не как в задаче 3), в первый (с использованием
алгоритма search).
7. Написать набор шаблонных функций для реализации понятия множества, которое хранится как упорядоченный
вектор. Должны быть реализованы следующие операции: ввод, вывод, добавить элемент, удалить элемент, проверить
принадлежность элемента множеству, объединение, пересечение и разность.
8. Написать набор шаблонных функций для реализации понятия многочлена одной переменной, который хра-
нится как вектор коэффициентов. Должны быть реализованы следующие операции: ввод, вывод, задать постоянный
многочлен, задать многочлен x, вычислить значение многочлена, сумму, произведение, неполное частное и остаток
от деления многочленов (последние две функции предполагают, что коэффициенты принадлежат полю, т. е. можно
делить на любой ненулевой коэффициент, и притом точно).
Что нужно знать в результате (проверяется на зачете):

• Какой заголовочный файл нужно подключить, чтобы использовать vector?


• Как определить и инициализировать переменную типа vector?
• Как изменить число элементов в векторе?
• Как добавить элемент в конец вектора?
• Как узнать число элементов вектора?
• Как определить переменную, содержащую матрицу?
• Что такое итератор?
• Как определить переменную типа итератор?
• Что можно делать с итераторами вектора?
• Какие итераторы и как можно получить по переменной типа вектор?
• Как вставить элемент в заданную позицию вектора?
• Как удалить из вектора элемент в заданной позиции или диапазоне?
• Как удалить из вектора элементы с заданным значением, или по определенному условию?
• Как перебрать подряд все элементы вектора?
• Что можно делать с помощью алгоритма copy?
• Что можно делать с помощью алгоритма find?

4
• Как перезаписать содержимое вектора элементами из указанного диапазона?
• Как написать шаблонную функцию, перебирающую все элементы любого вектора, т. е. вектора из элементов
любого типа?

Что нужно уметь в результате (задача на контрольной работе):

• Использовать вектор как замену для массива.


• Использовать возможности вектора для решения задач.