Существует несколько групп алгоритмов, среди них:
1) алгоритмы, не модифицирующие последовательность
2) алгоритмы, модифицирующие последовательность
3) алгоритмы сортировки
Для примера рассмотрим два алгоритма, не модифицирующих последовательности: find() и count(), и один - модифицирующий: replace(). Базовое использование алгоритма сортировки sort() уже рассматривалось в руководстве к лабораторной работе 3.
Алгоритм find() находит в последовательности заданный элемент и возвращает итератор, указывающий на этот элемент. Если алгоритм не находит элемент, он возвращает итератор, указывающий за конец последовательности. Воспользуемся find() для поиска в векторе первого элемента со значением 4:
vector<int> v(10); // заполнить вектор
// (1 3 4 6 4 5 3 4)
typedef vector<int>::iterator VI;
VI p = find (v.begin(), v.end(), 4);
if (p!= v.end())
cout << "element v["<< p-v.begin() << "] found" << endl; // 2
else
cout << "element not found\n";
Обратите внимание, что выражение p-v.begin() дает нам способ узнать индекс элемента последовательности, на который указывает итератор p.
Если нам нужен второй элемент, мы просто можем продолжить поиск соследующего элемента после найденного:
VI p_next = find (p+1,v.end()); // итератор на элемент с индексом 4
Алгоритм count() подсчитывает число элементов с заданным значением:
int n = count (v.begin(), v.end(), 4); // n==3
Возвращаемое значение будет целочисленным.
Алгоритм replace заменяет в последовательности все элементы с одним
значением на другое значение:
replace (v.begin(), v.end(), 4, 10); // (1 3 10 6 10 5 3 10)
// заменяет все значения 4 на значения 10
Очень важным моментом является то, что итератор, которые вернул один алгоритм, можно подать на вход в другой.
Вот так можно отсортировать все элементы от найденного:
sort(p+1,v.end());
или, полностью
sort(find (v.begin(), v.end(), 4), v.end());
Тем самым мы можем находить границы, начиная от которых мы будем проводить некоторые действия.
2. Функциональные объекты
Есть ряд проблем с использованием немодифицированных стандартных
алгоритмов, таких как find() или replace().
1) они хорошо работают только для последовательностей, содержащих объекты простых типов (например, int) или таких типов, для которых определены стандартные операции (например, string). Если мы создадим некоторый произвольный класс, то непонятно, как для контейнера, содержащего объекты такого класса, будут работать наши алгоритмы.
find() для того, чтобы найти, нужно сравнивать значение с объектами такого класса по очереди, sort() чтобы сортировать, нужно знать, как сравнивать объекты класса между собой и т.д.
2) они не позволяют задавать более сложные критерии работы. Мы не сможем модифицировать обычный find() так, чтобы он искал элемент, который больше некоторого значения.
3) хотелось бы иметь возможность производить собственные действия для
всех или некоторых элементов контейнера.
Для того, чтобы расширить сферу применимости алгоритмов и приспособить их к нуждам пользователя, используются функциональные объекты. Функциональный объект - это объект класса, для которого переопределена операция вызова функции (см. п.8.7 методических указаний для самостоятельной работы).
Большинство алгоритмов реализовано таким образом, что критерии, на основании которых они работают, вычисляются с помощью применения операции вызова функции к объекту, переданному в этот алгоритм как параметр. Операцию вызова функции можно применять к объектам двух видов: указателям на функцию и к функциональным объектам. Поэтому в простейшем случае в такие алгоритмы можно просто передавать адрес функции, производящей нужные вычисления, а если нам нужны дополнительные данные, то можно передавать должным образом инициализированный функциональный объект.
2.1. Использование функциональных объектов на примере алгоритма
for_each().
Примером алгоритма, требующего в качестве параметра функциональный объект, является алгоритм for_each(). Это - простейший из алгоритмов такого рода, он просто применяет свой аргумент (указатель на функцию или функциональный объект) последовательно ко всем элементам последовательности, заданной двумя итераторами start и end.
Общий вид for_each() следующий:
for_each(start, end, fun);
start задает начало последовательности, end - ее конец, fun вызывается для каждого элемента. Единственный параметр, который принимает fun - это ссылка на элемент последовательности, для которого она вызывается.
Вот как вывести на экран все элементы вектора:
void print_v(int &elem) { // функция для печати одного элемента
cout << elem << " ";
}
vector<int> v(10);
// заполнить v
for_each (v.begin(), v.end(), print_v);
fun() принимает только один параметр и, используя указатель на функцию, никакой дополнительной информации в нее передать нельзя. Вот как можно реализовать прибавление некоторого заранее заданного числа ко всем элементам вектора:
void add_2(int &elem) { // функция для добавления 2 к элементу
elem += 2;
}
for_each (v.begin(), v.end(), add_2);
Но если нам хочется прибавлять произвольное число, то функция нам не подойдет. Нам требуется нечто, чему мы можем передать произвольное значение и что может использовать это значение всякий раз, когда вызывается. Для этой цели нам нужно создать функциональный объект.
class Adder {
int val;
public:
Adder (int i) { val = i; }
void operator() (int &elem) { elem += val; }
};
У такого класса есть конструктор с одним параметром типа int. При инициализации объекта такого класса целым значением это значение запоминается в элементе val, а при вызове такого объекта как функции (например, в for_each()) это значение прибавляется к элементу, для которого произошел вызов:
for_each (v.begin(), v.end(), Adder(10));
for_each (v.begin(), v.end(), Adder(20));
Здесь в for_each передается временный объект типа Adder, а внутри алгоритма к нему применяется операция вызова функции, которая добавляет заданное число к элементу вектора.
Это - очень распространенный подход. Если нам нужно больше данных, все эти данные аналогичным образом можно передавать в конструктор вспомогательного функционального объекта.
Все это работает потому, что for_each является шаблонной функцией (как и все алгоритмы), которая просто вызывает как функцию свой третий аргумент для каждого элемента последовательности, заданной первыми двумя. Код для нее выглядит примерно так:
template <class In, class Op>
Op for_each (In first, In second, Op fun) {
while (first!= second) fun(*first++);
return f;
}
Если fun можно вызвать как функцию с параметром соответствующего типа, все будет работать.
3. Предикаты и алгоритмы с предикатами.
Предикат - это объект, который можно вызвать как функцию и который вовращает значение типа bool.
Пользовательские предикаты могут быть реализованы как функция, возвращающая bool или как функциональный объект, для которого переопределена операция вызова функции, возвращающая bool.
Существует множество алгоритмов, проверяющих предикат для каждого элемента последовательности. Некоторые из них (например, алгоритмы условного выполнения) выполняются, пока предикат возвращает true или только для тех элементов, для которых предикат вернул true, другие (например, алгоритмы поиска) прекращают свое выполнение, когда предикат вернет true.
Рассмотрим такие алгоритмы на примере уже знакомых нам find() и count(). find() и count(), как мы уже видели, принимают значение, с которым сравниваются элементы последовательности. Хотелось бы, чтобы при поиске или при подсчете можно было не сравнивать элементы со значением, а проверять для каждого элемента предикат. Такие версии этих алгоритмов существуют - это алгоритмы find_if() и count_if().
Алгоритм find_if() находит элемент, для которого выполняется предикат. Первые два параметра задают последовательность, третий - предикат. Возвращает алгоритм итератор, указывающий на найденный элемент, или указывающий за последовательность, если элемент не найден.
Предположим, мы хотим найти первый элемент, для которого значение меньше 4. В качестве предиката можно задать функцию, возвращающую bool:
bool less_than_4 (int elem) {
return elem < 4;
}
typedef vector<int>::iterator VI;
VI p = find_if (v.begin(), v.end(), less_than_4);
if (p!= v.end())
cout << "element with index "<< p-v.begin() << " found" << endl;
Более удобным представляется создать функциональный объект:
class less_than {
int val;
public:
less_than (int i) { val = i; }
bool operator() (int elem) { return elem < val; }
};
VI p = find_if (v.begin(), v.end(), less_than(4));
Возможно задание такого объекта как шаблонного класса (предполагается, что для типа шаблона допустима операция <)
template <class T> class less_than {
T val;
public:
less_than (T i) { val = i; }
bool operator() (T elem) { return elem < val; }
};
VI p = find_if (v.begin(), v.end(), less_than<int>(4));
Алгоритм count_if() возвращает число элементов, удовлетворяющих предикату. Он принимает те же параметры, что и find_if(), но возвращает целочисленное значение (разность итераторов).
Предположим, нам нужно подсчитать в векторе все значения, меньшие 4.
Воспользуемся уже созданным функциональным объектом less_than:
int n = count_if (v.begin(), v.end(), less_than<int>(4));
3.1. Стандартные предикаты и адаптеры
Представляется не очень удобным каждый раз задавать такие простые предикаты, как less_than. Ряд предикатов, соответствующих операциям отношения и логическим операциям, реализован в стандартной библиотеке.
Для их использования нужно подключать заголовочный файл <functional>. К этим предикатам относятся унарный предикат logical_not (реализующий!) и бинарные предикаты equal_to (==) not_equal_to (!=), greater (>), less (<), greater_equal (>=), less_equal (<=), logical_and (&&), logical_or (||). Каждый такой предикат является шаблоном, принимающим в качестве параметра тип значения, для которого производится сравнение или логические операции.
Для некоторых алгоритмов (обычно работающих с двумя последовательностями) такие предикаты могут использоваться сами по себе. В качестве примера приведем алгоритм mismatch(), который находит первую пару элементов, для которой две последовательности не совпадают, и возвращает pair из двух итераторов, указывающих на эти элементы.
pair <In,In> mismatch (In begin1,In end1,In begin2,BinPred p);
In - тип итератора, BinPred - бинарный предикат, который сравнивает соответствующие элементы последовательностей. Для второй последовательности достаточно задать начало, считается, что в ней по крайней мере столько же элементов, как и в первой.
Если мы хотим, чтобы элементы последовательности сравнивались по ==, мы можем предикат опустить:
// найти первую пару несовпадающих элементов
pair<VI,VI> pv = mismatch (v1.begin(),v1.end(),v2.begin());
Если же мы хотим, чтобы элементы последовательности сравнивались, например, с использованием < (найти первую пару элементов, для которой элемент первой последовательности не меньше, чем элемент второй), то нам нужен предикат и мы можем воспользоваться стандартным предикатом less:
pair<VI,VI> pv = mismatch (v1.begin(),v1.end(),v2.begin(),
less<int>());
cout << "mismatch at element " << pv.first-v1.begin() << endl;
// если v1 (3,4,5,6) а v2 (5,6,3,7) индекс будет 2
Предикат является объектом и для него нужны скобки.
Аналогичен mismatch() алгоритм equal():
bool equal (first1, last1, first2 [,bin_pred]);
который сравнивает элементы двух последовательностей на соответствие предикату (по умолчанию - на равенство). Если все элементы равны или для всех пар предикат выполняется, equal() вернет true, иначе false.
Не всегда достаточно использовать стандартные объекты непосредственно. Типичным примером может служить уже рассмотренная нами функция find_if() и написанный нами ранее предикат less_than.
Предикат less_than отличается от стандартного предиката less тем, что его второй параметр зафиксирован для некоторого значения (передаваемого в функциональный объект при его инициализации). Хотелось бы, чтобы в нем можно было использовать less, т.е. необходима какая-то композиция функциональных объектов. Стандартная библиотека предусматривает такие случаи - в ней есть специальные средства, называемые адаптерами.
Адаптер принимает на вход функциональный объект и выдает другой функциональный объект, некоторым образом измененный. Для нашего случая можно воспользоваться специальными адаптерами, называемыми связывателями (binders), которые позволяют использовать функциональный объект с двумя аргументами как объект с одним аргументом путем связывания одного аргумента со значением.
Есть два таких связывателя:
bind1st(fun,x) - связывает первый аргумент функционального объекта fun
со значением x
bind2nd(fun,x) - то же для второго аргумента
Вот как в нашем случае можно использовать bind2nd():
VI p = find_if (v.begin(), v.end(), bind2nd (less<int>(), 4));
При каждом вызове операции сравнения less внутри find_if() в качестве первого аргумента будет взят элемент вектора, а в качестве второго - значение 4.
Так же можно подсчитать число элементов, которые больше или равны 20:
int n = count_if (v.begin(), v.end(),
bind2nd(greater_equal<int>(),20));
Второй род адаптеров называется отрицателями (negators). Они модифицируют объект так, что он при использовании превращается в свое логическое отрицание. Есть два вида отрицателей: not1() для унарных операций, not2() для бинарных. Так можно записать предыдущий пример:
int n = count_if (v.begin(), v.end(), bind2nd (not2 (less<int>()), 20));
Мы сначала отрицаем less, а потом привязываем получившийся функциональный объект к 20.
4. Классы как элементы контейнеров
Рассмотрим, как работать с поиском в контейнере, состоящем из
произвольных классов. Для этого можно поступить так:
class X {
friend bool pred_X (X&);
int key;
public:
X(int i) { key = i; }
};
bool X_less_than_100 (X& elem) { return elem.key < 100; }
vector<X> v;
int n = count_if(v.begin(), v.end(), X_less_than_100);
Аналогично можно работать и с функциональными объектами, в которые
передавать объекты класса. Представляется удобным использовать для организации такой работы функции-элементы класса (чтобы можно было вызвать функцию-элемент для каждого объекта последовательности). Для этого используются специальные адаптеры mem_fun() и mem_fun_ref(). Оба они принимают указатели на функцию-элемент класса и возвращают объект, который можно передавать в алгоритмы, но mem_fun() используется для вызова этого элемента через указатель на объект (т.е. для контейнеров указателей на объект), а mem_fun_ref() - для вызова непосредственно через объект (т.е. для контейнеров объектов). Используются они так:
class Y {
int key;
public:
Y(int i) { key = i; }
bool less_than_100 () { return key < 100; }
bool less_than (int n) { return key < n; }
int print () { cout << "Y(" << key << ") "; return 0; }
};
vector<Y*> vp;
vector<Y> vy;
// посчитать число объектов класса, для которых функция-элемент
// возвращает true
int nx = count_if(vp.begin(), vp.end(), mem_fun(&Y::less_than_100));
// напечатать все
for_each (vy.begin(), vy.end(), mem_fun_ref(&Y::print));
Для каждого элемента вектора будет вызываться функция-элемент less_than_100().
Можно заставить вызываться функцию-элемент с одним аргументом – для этого есть адаптеры mem_fun1() и mem_fun1_ref():
int nn = count_if(vp.begin(), vp.end(), bind2nd(mem_fun1(&Y::less_than),100));
Для каждого элемента вектора будет вызываться функция-элемент less_than(), при этом в нее будет передаваться значение 100. Вот пример без связывателей - проверка, меньше ли каждый ключ для класса соответствующего элемента вектора целых.
vector<int> vi;
bool b = equal (vp.begin(), vp.end(), vi.begin(),
mem_fun1(&Y::less_than));
Для каждого элемента вектора vp будет вызываться функция-элемент less_than() и при этом в нее будет передаваться соответствующее значение из вектора vi.
5. Алгоритмы, не модифицирующие последовательность
Мы уже знаем, как пользоваться алгоритмами find(), find_if(), count(), count_if(), mismatch() и equal(). Вкратце рассмотрим другие алгоритмы из этой группы (Iter - тип итератора, все first и last – типа Iter).
1) Iter find_first_of (first1, last1, first2, last2 [,bin_pred]);
находит в первой последовательности первый элемент, который есть и во второй последовательности, и возвращает итератор на него. bin_pred задает, как сравнивать элементы (по умолчанию - по ==)
(1 3 5 3 5 6) (7 4 5 3) -> итератор на первое "3"
2) Iter adjacent_find (first, last [,bin_pred]);
находит в последовательности пару соседних одинаковых значений и возвращает итератор на первый элемент пары (1 2 3 3 5 6) -> итератор на первое "3"
3) Iter search (first1, last1, first2, last2 [,bin_pred]);
выясняет, входит ли вторая последовательность в первую как подпоследовательность и возвращает итератор на тот элемент первой последовательности, с которого эта подпоследовательность начинается (1 3 5 3 5 6) (5 3 5) -> итератор на первое "5"
4) Iter find_end (first1, last1, first2, last2 [,bin_pred]);
то же, но возвращается итератор на тот элемент, которым последовательность заканчивается (1 3 5 3 5 6) (5 3 5) -> итератор на второе "5"
5) Iter min_element(first, last[, bin_pred]);
находит минимум последовательности. Аналогично есть и max_element().
6. Функциональные объекты как критерии сравнения. Алгоритмы сортировки.
Для того, чтобы задать критерий сортировки для алгоритма sort(), используются бинарные предикаты. По умолчанию sort() использует стандартный предикат less, так что пользовательский предикат должен возвращать true, если первый аргумент должен стоять в отсортированной последовательности раньше, чем второй.
Вот так можно отсортировать вектор объектов некоторого класса:
class Y {
friend class YCmp;
//...
};
class YCmp {
public:
bool operator() (const Y& y1, const Y& y2) {
return y1.key < y2.key;
}
};
vector<Y> vy;
sort (vy.begin(), vy.end(), YCmp());
При использовании адаптеров функций-элементов можно вовсе отказаться от вспомогательного объекта:
class Y {
//...
public:
bool less_than (Y& y) { return key < y.key; }
};
sort (vy.begin(), vy.end(), mem_fun1_ref(&Y::less_than));
При каждом сравнении для первого объекта вызывается less_than() и на вход ей подается второй объект. Еще более удобным представляется перегрузить для класса операцию "меньше". В этом случае можно вызывать sort() и прочие алгоритмы, использующие сравнение, вообще без дополнительного аргумента:
class Y {
//...
public:
bool operator<(Y& y) { return key < y.key; }
};
sort (vy.begin(), vy.end());
Кратко рассмотрим другие алгоритмы, близкие к сортировке.
1) partial_sort(first, middle, last[, bin_pred]);
Частичная сортировка - упорядочивает только элементы от first до middle
для последовательности, заданной first и last.
2) partial_sort_copy(first1, last1, first2, last2[, bin_pred]);
заносит во вторую последовательность N элементов, где N - меньшая из
длин двух последовательностей.
3) binary_search(first, last, val [, bin_pred]);
двоичный поиск значения val в отсортированной последовательности
Если элементов может быть несколько, то можно пользоваться алгоритмами equal_range(), lower_bound() и upper_bound(), которые аналогичны элементам multimap(), но принимают на вход начало и конец последовательности.
4) unique(first, last, [, bin_pred]);
сдвигает уникальные элементы вперед так, чтобы они были все перед неуникальными и возвращает итератор на конец уникальной последовательности (т.е. элементы не удаляются):
(1 2 2 1 4 5 3 2 1) -> (1 2 4 5 3 2 1 2 1) и итератор на "3"
7. Алгоритмы, изменяющие последовательность и итераторы-вставки
Простейший алгоритм, изменяющий последовательность - это алгоритм copy(). Он просто берет последовательность и копирует ее начиная с некоторого результирующего итератора:
copy (first, last, res);
Но если мы начнем использовать copy(), то тут же столкнемся с проблемой. Вот такой простой код скорее всего приведет к краху программы:
vector<int> v1(10); // и зададим значения
vector<int> v2;
copy (v1.begin(), v1.end(), v2.begin()); // скорее всего крах
Дело в том, что ни один алгоритм, который пишет в выходную последовательность подобно copy(), не расширяет эту последовательность.
Если в ней есть место для элементов - все будет в порядке, иначе - нет. В нашем случае вектор v2 пуст и запись пойдет за его границу.
Аналогично, такая попытка добавить элементы в конец вектора тоже ни к чему не приведет:
vector<int> v2(10);
copy (v1.begin(), v1.end(), v2.end()); // скорее всего тоже крах
Для решения этой проблемы стандартная библиотека предоставляет специальные итераторы - итераторы-вставки (inserters). Они принимают на вход контейнер и при каждой попытке записи через них добавляют элементы в этот контейнер. Существует три вида таких итераторов:
back_inserter(c) - добавляет в конец контейнера c
front_inserter(c) - добавляет в начало
inserter(c,i) - добавляет в контейнер c начиная с итератора i
Вот как безопасно скопировать один контейнер в конец другого:
vector<int> v1(10);
vector<int> v2;
copy (v1.begin(), v1.end(), back_inserter(v2)); // OK
Вектор v2 автоматически будет расширяться.
Вкратце рассмотрим другие алгоритмы такого рода.
1) copy_backward(first, last, res);
копирование от конца к началу
2) transform(first, last, res, un_op);
transform(first1, last1, first2, res, bin_op);
Это - мощный алгоритм, который преобразует вход (одну или две последовательности) на основании операций un_op или bin_op и генерирует выход (тоже последовательность, каждый элемент которой - это результат выполнения операции над соответствующим элементом или двумя элементами входа). Вот как можно занести в вектор все ключи из вектора объектов
класса Y:
class Y {
friend int get_key(Y& y);
};
int get_key(Y &y) { return y.key; }
vector<Y> vy(10); // и заполнить его
vector<int> vi;
transform (vy.begin(), vy.end(), back_inserter(vi), get_key);
или с использованием методов класса:
class Y {
int get_key() { return key; }
};
vector<Y> vy(10); // и заполнить его
vector<int> vi;
transform (vy.begin(), vy.end(), back_inserter(vi),
mem_fun_ref(&Y::get_key));
Для двух последовательностей bin_op принимает на вход два параметра.
3) unique_copy(first, last, res [,bin_pred]);
аналогична unique(), но пишет в выходную последовательность.
4) алгоритмы replace. Первый из них не пишет в выходную последовательность:
replace_if(first, last, pred, new_val);
заменяет все элементы, для которых pred вернул true, на значение val:
replace_if(v.begin(),v.end(),bind2nd(less<int>(),4),6);
// заменяет все, что меньше 4, на 6
Остальные генерируют последовательности:
replace_copy (first, last, res, val, new_val);
replace_copy_if (first, last, res, pred, new_val);
5) fill(first, last, val);
заполняет последовательность значением val
fill_n(res, n, val);
выдает в res n значений val
fill_n(back_inserter(v), 10, 0);
// добавляет в n 10 элементов со значением 0
6) generate (first, last, gen_op);
заносит в последовательность последовательные результаты вызова функции или функционального объекта gen_op(). gen_op не должна принимать параметров, это может быть, например, генератор случайных чисел. Если нам нужны параметры, можно воспользоваться, как и раньше, функциональным объектом.
generate_n(res, n, gen_op);
выдает в res n результатов вызова gen_op()
generate_n(back_inserter(v), 10, rand);
добавляет в v 10 случайных чисел.
8. Арифметические функциональные объекты и арифметические алгоритмы
Для числовых объектов существует ряд стандартных арифметических функциональных объектов: plus (+), minus(-), multiplies(*), divides(/), modulus(%) и negate(унарный -). Их можно использовать с теми
алгоритмами, которые мы рассмотрели, например, с transform():
// попарное перемножение элементов векторов
transform(v1.begin(),v1.end(),v2.begin(),back_inserter(res),
multiplies<int>());
Эти функциональные объекты объявлены в <functional>.
Существуют и специальные числовые алгоритмы, которые объявлены в заголовочном файле <numeric>. К ним относятся (T - скалярный тип, остальные типы параметров - итераторы):
1) T accumulate<first, last, T init [, bin_op]);
накапливает элементы. В качестве начального значения берется init, операция по умолчанию - сложение:
int sum = accumulate(v.begin(), v.end(), 0);
int prod = accumulate(v.begin(), v.end(), 1, multiplies<int>());
Можно работать с произвольными функциями или функциональными объектами. Требуется, чтобы функция каким-то образом добавляла значение к своему первому параметру и это значение возвращала:
class Y {
friend int y_add(int, Y&);
};
int y_add(int val, Y& y) {
return val + y.key;
}
int ysum = accumulate(vy.begin(), vy.end(), 0, y_add);
2) T inner_product(first, last, first2, last2 [, bin_op1, bin_op2]);
накапливает элементы двух последовательностей.
По умолчанию она вычисляет скалярное произведение (на каждом шаге элемент первой последовательности умножается на элемент второй, а от шага к шагу элементы суммируются), но может быть обобщена так, чтобы на каждом шаге выполнялась bin_op2, а от шага к шагу - bin_op1.
9. Итераторы потоков
Потоки ввода и вывода тоже можно представить в виде последовательностей, т.е. стандартные алгоритмы могут непосредственно записывать на стандартный вывод (или в файл), а также считывать со стандартного ввода (или из файла).
Для этого используются специальные итераторы:
ostream_iterator для записи в потоки вывода
istream_iterator для чтения из потоков ввода
Чтобы работать с таким итератором, требуется:
1) создать такой объект-итератор, передав в его конструктор поток ввода или вывода и необязательную строку-разделитель и задав тип выводимых объектов как параметр шаблона:
ostream_iterator<int> os(cout, " ");
2) если он выходной - вывести в него данные, например, с помощью алгоритма copy():
copy (v.begin(), v.end(), os);
или с помощью transform():
// вывести попарно перемноженные элементы векторов через пробел
transform(v1.begin(), v1.end(), v2.begin(), os, multiplies<int>());
Для вывода объектов некоторого класса для него должна быть перегружена операция вывода в поток, остальное аналогично:
class Y {
friend ostream& operator<<(ostream&, const Y&);
};
ostream& operator<<(ostream& o, const Y& y) {
o << y.key; return o;
}
ofstream ofile ("test.txt");
ostream_iterator<Y> osy(ofile, " ");
vector<Y> vy; // и заполнить
copy (vy.begin(), vy.end(), osy); // вывести в файл
3) если он входной - считать из него данные. В качестве итератора конца последовательности нужно использовать istream_iterator<T>() (без