Есть два вектора, 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
vl.clear():
for (vector
ci != v2.end();
++ci)
v1.push_back(*c
В совете 43 подробно объясняется, почему использовать явные циклы не рекомендуется, но и без этого ясно, что написание этого фрагмента потребует больше усилий, чем простой вызов assign
. Цикл также отрицательно влияет на быстродействие, но к этой теме мы вернемся позже.
Одно из возможных решений заключается в том, чтобы последовать совету 43 и воспользоваться алгоритмом:
vl.clear();
copy(v2.begin()+v2.size()/2.v2.end().back_inserter(v1));
Но и этот вариант требует больших усилий, чем простой вызов assign
. Более того, хотя цикл не встречается в программе, он наверняка присутствует внутри вызова сору
(см. совет 43). В результате потенциальное снижение быстродействия не исчезает (вскоре мы поговорим об этом). А сейчас я хочу ненадолго отвлечься от темы и заметить, что практически все случаи использования сору, когда приемный интервал задается итератором вставки (inserter, back_inserter
или front_inserter
), могут — insert
:
vl.insert(vl.end(),v2.begin()+v2.size()/2.v2.end());
Команда получается ненамного короче, но она к тому же ясно указывает на суть происходящего: данные вставляются в v1. Вызов сору
означает примерно то же, но не столь очевидно. В данном случае важно не то, что элементы копируются, а то, что в v1
добавляются новые данные. Функция insert
прямо говорит об этом, а сору лишь сбивает с толку. Нет ничего особенно интересного в том факте, что данные где-то копируются, — собственно, вся библиотека STL
Многие программисты STL злоупотребляют функцией сору
, поэтому только что данный совет стоит повторить: вызовы сору, в которых результирующий интервал задается итератором вставки, практически всегда следует заменять вызовами интервальных функций.
Вернемся к примеру с assign
. Мы уже выяснили две причины, по которым интервальным функциям отдается предпочтение перед их одноэлементными аналогами.
•Написание кода с интервальными функциями обычно требует меньших усилий.
•Решения с интервальными функциями обычно выглядят более наглядно и логично.
Короче говоря, программы с интервальными функциями удобнее как писать, так и читать. О чем тут еще говорить?