Программирование >>  Включение нужных заголовков 

1 ... 37 38 39 [ 40 ] 41 42 43 ... 71


объясняется тот удивительный факт, что вызов remove не изменяет количества элементов в контейнере:

vector<int> v: v.reserve(lO); forCint i=l;i<=10:++i){ v.push back(i);

cout V.sizeO: v[3]=v[5]=v[9]=99: remove( V.beginO, V.endO, 99): cout V. SizeO:

Создать vector<int> и заполнить его числами 1-10 (вызов reserve описан в совете 14)

Выводится число 10 Присвоить 3 элементам значение 99 Удалить все элементы со значением 99 Все равно выводится 10!

Чтобы понять смысл происходящего, необходимо запомнить следующее: Алгоритм remove по настоящему ничего не удаляет, потому что не может. На всякий случай повторю: ...потому что не может\

Алгоритм remove не знает, из какого контейнера он должен удалять элементы, а без этого он не может вызвать функцию настоящего удаления.

Итак, теперь вы знаете, чего алгоритм remove сделать не может и по каким причинам. Остается понять, что же он все-таки делает.

В общих чертах remove перемещает элементы в заданном интервале до тех пор, пока все оставшиеся элементы не окажутся в начале интервала (с сохранением их относительного порядка). Алгоритм возвращает итератор, указывающий на позицию за последним оставшимся элементом. Таким образом, возвращаемое значение можно интерпретировать как новый логический конец интервала.

В рассмотренном выше примере вектор v перед вызовом remove выглядел следующим образом:

V.beginO

99 7

v.endQ

9 99

Предположим, возвращаемое значение remove сохраняется в новом итераторе с именем newEnd:

vector<int>::iterator newEnd(remove(v.begin().v.end().99)):

После вызова вектор v принимает следующий вид:

V.beginO

V.endO

8 9

newEnd

Вопросительными знаками отмечены значения элементов, концептуально удаленных из v, но продолжающих благополучно существовать.

Раз оставшиеся элементы v находятся между v.beginO и newEnd, было бы логично предположить, что удаленные элементы будут находиться между newEnd и V.endO. Но это не так\ Присутствие удаленных элементов в v вообще не



2 3 5

newEnd

Как видите, два значения 99 , ранее существовавших в v, исчезли, а одно осталось. В общем случае после вызова remove элементы, удаленные из интервала, могут остаться в нем, а могут исчезнуть. Многие программисты находят это странным, но почему? Вы просите remove убрать некоторые элементы, алгоритм выполняет вашу просьбу. Вы же не просили разместить удаленные значения в особом месте для последующего использования... Так в чем проблема? (Чтобы предотвратить потерю значений, вместо remove лучше воспользоваться алгоритмом parti ti on, описанным в совете 31.)

На первый взгляд поведение remove выглядит довольно странно, но оно является прямым следствием принципа работы алгоритма. Во внутренней реализации remove перебирает содержимое интервала и перезаписывает удаляемые значения сохраняемыми . Перезапись реализуется посредством присваивания.

Алгоритм remove можно рассматривать как разновидность уплотнения, при этом удаляемые значения играют роль пустот , заполняемых в процессе уплотнения. Опишем, что происходит с вектором v из нашего примера.

1. Алгоритм remove анализирует v[0], видит, что данный элемент не должен удаляться, и перемещается к v[l]. То же самое происходит с элементами v[l] HV[2].

2. Алгоритм определяет, что элемент v[3] подлежит удалению, запоминает этот факт и переходит к v[4]. Фактически v[3] рассматривается как дыра , подлежащая заполнению.

3. Значение v[4] необходимо сохранить, поэтому алгоритм присваивает v[4] элементу v[3], запоминает, что v[4] подлежит перезаписи, и переходит к v[5]. Если продолжить аналогию с уплотнением, элемент v[3] заполняется значением v[4], а на месте v[4] образуется новая дыра .

4. Элемент v[5] исключается, поэтому алгоритм игнорирует его и переходит к v[6]. При этом он продолжает помнить, что на месте v[4] остается дыра , которую нужно заполнить.

5. Элемент v[6] сохраняется, поэтому алгоритм присваивает v[6] элементу v[4], вспоминает, что следующая дыра находится на месте v[5], и переходит к v[7].

гарантировано. Алгоритм remove не изменяет порядок элементов в интервале так, чтобы удаленные элементы сгруппировались в конце - он перемещает остающиеся элементы в начало. Хотя в Стандарте такое требование отсутствует, элементы за новым логическим концом интервала обычно сохраняют свои старые значения. Во всех известных мне реализациях после вызова remove вектор v выглядит так:

vbeginO vend() I t



ХУКУ

Как объясняется в совете 33, факт перезаписи некоторых удаляемых значений имеет важные последствия в том случае, если эти значения являются указателями. Но в контексте данного совета достаточно понимать, что remove не удаляет элементы из контейнера, поскольку в принципе не может этого сделать. Элементы могут удаляться лишь функциями контейнера, отсюда следует и главное правило настоящего совета: чтобы удалить элементы из контейнера, вызовите erase после remove.

Элементы, подлежащие фактическому удалению, определить нетрудно - это все элементы исходного интервала, начиная с нового логического конца интервала и завершая его физическим концом. Чтобы уничтожить все эти элементы, достаточно вызвать интервальную форму erase (см. совет 5) и передать ей эти два итератора. Поскольку сам алгоритм remove возвращает итератор для нового логического конца массива, задача решается прямолинейно:

vector<int> v: См. ранее

v.erase(renrave(v.begin().V.endO,99).V.endO); Фактическое удаление

элементов со значением 99 cout V.sizeO; Теперь выводится 7

Передача в первом аргументе интервальной формы erase возвращаемого значения remove используется так часто, что рассматривается как стандартная конструкция. Remove и erase настолько тесно связаны, что они были объединены в функцию remove контейнера 1 i st. Это единственная функция STL с именем remove, которая производит фактическое удаление элементов из контейнера:

list<int> li; Создать список

Заполнить данными li.remove(99); Удалить все элементы со значением 99.

Команда производит фактическое удаление

элементов из контейнера, поэтому размер li

может измениться

Честно говоря, выбор имени remove в данном случае выглядит непоследовательно. В ассоциативных контейнерах аналогичная функция называется erase, поэтому в контейнере list функцию remove тоже следовало назвать erase. Впрочем, этого не произошло, поэтому остается лишь смириться. Мир, в котором

6. Аналогичным образом анализируются элементы v[7], v[8] и v[9]. Значение v[7] присваивается элементу v[5], а значение v[8] присваивается элементу v[6]. Элемент v[9] игнорируется, поскольку находящееся в нем значение подлежит удалению.

7. Алгоритм возвращает итератор для элемента, следующего за последним оставшимся . В данном примере это элемент v[7].

Перемещения элементов в векторе v выглядят следующим образом:

8 I 9 99



1 ... 37 38 39 [ 40 ] 41 42 43 ... 71

© 2006 - 2024 pmbk.ru. Генерация страницы: 0
При копировании материалов приветствуются ссылки.
Яндекс.Метрика