Есть два вектора, v1
и v2
. Как проще всего заполнить v1
содержимым второй половины v2
? Только не надо мучительно размышлять над тем, что считать «половиной» при нечетном количестве элементов в v2
. Просто постарайтесь быстро дать разумный ответ.
Время истекло! Если вы предложили
v1.assign(v2.begin+v2.size/2, v2.end)
или нечто похожее — поздравляю, пять баллов. Если в вашем ответе присутствуют вызовы более чем одной функции, но при этом он обходится без циклов, вы получаете «четверку». Если в ответе задействован цикл, вам есть над чем поработать, а если несколько циклов — значит, вы узнаете из этой книги много нового.
Кстати говоря, если при чтении
Я привел эту задачу по двум причинам. Во-первых, она напоминает вам о существовании очень удобной функции assign
, о которой многие программисты попросту забывают. Функция assign
поддерживается всеми стандартными последовательными контейнерами (vector, string, deque
и list
). Каждый раз, когда вам требуется полностью заменить содержимое контейнера, подумайте, нельзя ли добиться желаемой цели присваиванием. Если вы просто копируете один контейнер в другой контейнер того же типа, задача решается функцией operator=
. Но, как показывает приведенный пример, существует также функция assign
, которая позволяет заполнить контейнер новыми данными в тех случаях, когда operator=
не подходит.
Во-вторых, эта задача показывает, почему интервальные функции лучше своих одноэлементных аналогов.
vector
// векторы объектов Widget
…
v1.clear:
for (vector
v1.push_back(*ci);
В совете 43 подробно объясняется, почему использовать явные циклы не рекомендуется, но и без этого ясно, что написание этого фрагмента потребует больше усилий, чем простой вызов assign
. Цикл также отрицательно влияет на быстродействие, но к этой теме мы вернемся позже.
Одно из возможных решений заключается в том, чтобы последовать совету 43 и воспользоваться алгоритмом:
v1.clear;
copy(v2.begin+v2.size/2, v2.end, back_inserter(v1));
Но и этот вариант требует больших усилий, чем простой вызов assign
. Более того, хотя цикл не встречается в программе, он наверняка присутствует внутри вызова copy
(см. совет 43). В результате потенциальное снижение быстродействия не исчезает (вскоре мы поговорим об этом). А сейчас я хочу ненадолго отвлечься от темы и заметить, что практически все случаи использования copy
, когда приемный интервал задается итератором вставки (inserter, back_inserter
или front_inserter
), могут — copy
заменяется интервальной версией insert
:
v1.insert(v1.end, v2.begin+v2.size/2. v2.end);
Команда получается ненамного короче, но она к тому же ясно указывает на суть происходящего: данные вставляются в v1
. Вызов copy
означает примерно то же, но не столь очевидно. В данном случае важно не то, что элементы копируются, а то, что в v1
добавляются новые данные. Функция insert
прямо говорит об этом, а copy
лишь сбивает с толку. Нет ничего особенно интересного в том факте, что данные где-то копируются, — собственно, вся библиотека STL
Многие программисты STL злоупотребляют функцией copy
, поэтому только что данный совет стоит повторить: вызовы copy, в которых результирующий интервал задается итератором вставки, практически всегда следует заменять вызовами интервальных функций.
Вернемся к примеру с assign
. Мы уже выяснили две причины, по которым интервальным функциям отдается предпочтение перед их одноэлементными аналогами.
• Написание кода с интервальными функциями обычно требует меньших усилий.
• Решения с интервальными функциями обычно выглядят более наглядно и логично.
Короче говоря, программы с интервальными функциями удобнее как писать, так и читать. О чем тут еще говорить?