Язык программирования C++. Пятое издание - Стенли Липпман
Шрифт:
Интервал:
Закладка:
// читать слова со стандартного устройства ввода и помещать
// их в конец контейнера
string word;
while (cin >> word)
container.push_back(word);
Вызов функции push_back() создает новый элемент в конце контейнера container, увеличивая его размер на 1. Значением этого элемента будет копия значения переменной word. Контейнер может иметь любой тип: list, vector или deque.
Поскольку класс string — это только контейнер символов, функцию push_back() можно использовать для добавления символов в ее конец:
void pluralize(size_t cnt, string &word) {
if (cnt > 1)
word.push_back('s'); // то же, что и word += 's'
}
Ключевая концепция. Элементы контейнера содержат копии значенийКогда объект используется для инициализации контейнера или вставки в него, в контейнер помещается копия значения объекта, а не сам объект. Подобно передаче объекта в не ссылочном параметре (см. раздел 6.2.1), между элементом в контейнере и объектом, значение которого было использовано для его инициализации, нет никакой связи. Последующие изменения элемента в контейнере никак не влияют на исходный объект, и наоборот.
Применение функции push_front()Кроме функции push_back(), контейнеры list, forward_list и deque предоставляют аналогичную функцию push_front(). Она вставляет новый элемент в начало контейнера:
list<int> ilist;
// добавить элементы в начало ilist
for (size_t ix = 0; ix != 4; ++ix)
ilist.push_front(ix);
Этот цикл добавляет элементы 0, 1, 2, 3 в начало списка ilist. Каждый элемент вставляется в новое начало списка, т.е. когда добавляется 1, она оказывается перед 0, 2 — перед 1 и так далее. Таким образом, добавленные в цикле элементы расположены в обратном порядке. После этого цикла список ilist содержит такую последовательность: 3, 2, 1, 0.
Обратите внимание: контейнер deque, предоставляющий подобно контейнеру vector быстрый произвольный доступ к своим элементам, обладает функцией-членом push_front(), а контейнер vector — нет. Контейнер deque гарантирует постоянное время вставки и удаления элементов в начало и в конец контейнера. Как и у контейнера vector, вставка элементов иначе как в начало или в конец контейнера deque — потенциально продолжительная операция.
Добавление элементов в контейнеры vector, string или deque способно сделать недействительными все существующие итераторы, ссылки и указатели на их элементы.
Добавление элементов в указанную точку контейнераФункции push_back() и push_front() предоставляют весьма удобный способ добавления одиночных элементов в конец или в начало последовательного контейнера. Функция insert() обладает более общим характером и позволяет вставлять любое количество элементов в любую указанную позицию контейнера. Ее поддерживают контейнеры vector, deque, list и string, а контейнер forward_list предоставляет собственные специализированные версии этих функций-членов, которые рассматриваются в разделе 9.3.4.
Каждая из функций вставки получает в качестве первого аргумента итератор, указывающий позицию помещения элемента (элементов) в контейнере. Он может указывать на любую позицию, включая следующий элемент после конца контейнера. Поскольку итератор может указывать на несуществующий элемент после конца контейнера, а также потому, что полезно иметь способ вставки элементов в начало контейнера, элемент (элементы) вставляются перед позицией, обозначенной итератором. Рассмотрим следующий оператор:
slist.insert(iter, "Hello!"); // вставить "Hello!" прямо перед iter
Он вставляет строку со значением "Hello" непосредственно перед элементом, обозначенным итератором iter.
Даже при том, что у некоторых контейнеров нет функции push_front(), к функции insert() это не относится. Функция insert() позволяет вставлять элементы в начало контейнера, не заботясь о наличии у контейнера функции push_front():
vector<string> svec;
list<string> slist;
// эквивалент вызова slist.push_front("Hello!");
slist.insert(slist.begin(), "Hello!");
// вектор не имеет функции push_front(),
// но вставка перед begin() возможна
// внимание: вставка возможна везде, но вставка в конец вектора может
// потребовать больше времени
svec.insert(svec.begin(), "Hello!");
Контейнеры vector, deque и string допускают вставку в любую позицию, но это может потребовать больше времени.
Вставка диапазона элементовСледующие аргументы функции insert(), расположенные после начального итератора, похожи на аргументы конструкторов контейнеров. Версия, получающая количество элементов и значение, добавляет определенное количество одинаковых элементов перед указанной позицией:
svec.insert(svec.end(), 10, "Anna");
Этот код вставляет 10 элементов в конец вектора svec и инициализирует каждый из них строкой "Anna".
Данная версия функции insert() получает пару итераторов или список инициализации для вставки элементов из данного диапазона перед указанной позицией:
vector<string> v = {"quasi", "simba", "frollo", "scar"};
// вставить два последних элемента вектора v в начало slist
slist.insert(slist.begin(), v.end() - 2, v.end());
slist.insert(slist.end(), {"these", "words", "will",
"go", "at", "the", "end"});
// ошибка времени выполнения:
// обозначающие копируемый диапазон итераторы
// не должны принадлежать тому же контейнеру, который изменяется
slist.insert(slist.begin(), slist.begin(), slist.end());
При передаче пары итераторов они не могут относиться к тому же контейнеру, к которому добавляются элементы.
По новому стандарту версии функции insert(), получающие количество или диапазон, возвращают итератор на первый вставленный элемент. (В предыдущих версиях библиотеки эти функции возвращали тип void.) Если диапазон пуст, никакие элементы не вставляются, а функция возвращает свой первый параметр.
Применение возвращаемого значения функции insert()Значение, возвращенное функцией insert(), можно использовать для многократной вставки элементов в определенной позиции контейнера:
list<string> lst;
auto iter = lst.begin();
while (cin >> word)
iter = lst.insert(iter, word); // то же, что и вызов push_front()
Важно понимать, почему именно цикл, подобный этому, эквивалентен вызову функции push_front().
Перед циклом итератор iter инициализируется возвращаемым значением функции lst.begin(). Первый вызов функции insert() получает только что прочитанную строку и помещает ее перед элементом, обозначенным итератором iter. Значение, возвращенное функцией insert(), является итератором на этот новый элемент. Присвоим этот итератор итератору iter и повторим цикл, читая другое слово. Пока есть слова для вставки, каждая итерация цикла while вставляет новый элемент перед позицией iter и снова присваивает ему позицию недавно вставленного элемента. Этот (новый) элемент является первым. Таким образом, каждая итерация вставляет элемент перед первым элементом в списке.
Применение функций emplace()Новый стандарт вводит три новых функции-члена — emplace_front(), emplace() и emplace_back(), которые создают элементы, а не копируют. Они соответствуют функциям push_front(), insert() и push_back(), позволяющим помещать элемент в начало контейнера, перед указанной позицией или в конец контейнера соответственно.
Когда происходит вызов функции-члена insert() или push(), им передается объект типа элемента для копирования в контейнер. Когда происходит вызов функции emplace(), ее аргументы передаются конструктору типа элемента, который создает элемент непосредственно в области, контролируемой контейнером. Предположим, например, что контейнер с содержит элементы типа Sales_data (см. раздел 7.1.4):
// создает объект класса Sales_data в конце контейнера с
// использует конструктор класса Sales_data с тремя аргументами
с.emplace_back("978-05903534 03", 25, 15.99);
// ошибка: нет версии функции push_back(), получающей три аргумента