Как заполнить динамический массив с клавиатуры
Обычно, объем памяти, необходимый для той или иной переменной, задается еще до процесса компиляции посредством объявления этой переменной. Если же возникает необходимость в создание переменной, размер которой неизвестен заранее, то используют динамическую память. Резервирование и освобождение памяти в программах на C++ может происходить в любой момент времени. Осуществляются операции распределения памяти двумя способами:
Функция malloc резервирует непрерывный блок ячеек памяти для хранения указанного объекта и возвращает указатель на первую ячейку этого блока. Обращение к функции имеет вид:
void *malloc(size);
Здесь size — целое беззнаковое значение, определяющее размер выделяемого участка памяти в байтах. Если резервирование памяти прошло успешно, то функция возвращает переменную типа void *, которую можно привести к любому необходимому типу указателя.
Функция — calloc также предназначена для выделения памяти. Запись ниже означает, что будет выделено num элементов по size байт.
void *calloc (nime, size);
Эта функция возвращает указатель на выделенный участок или NULL при невозможности выделить память. Особенностью функции является обнуление всех выделенных элементов.
Функция realloc изменяет размер выделенной ранее памяти. Обращаются к ней так:
char *realloc (void *p, size);
Здесь p — указатель на область памяти, размер которой нужно изменить на size. Если в результате работы функции меняется адрес области памяти, то новый адрес вернется в качестве результата. Если фактическое значение первого параметра NULL, то функция realloc работает также, как и функция malloc, то есть выделяет участок памяти размером size байт.
Для освобождения выделенной памяти используется функция free. Обращаются к ней так:
void free (void *p size);
Здесь p — указатель на участок памяти, ранее выделенный функциями malloc, calloc или realloc.
Операторы new и delete аналогичны функциям malloc и free. New выделяет память, а его единственный аргумент — это выражение, определяющее количество байтов, которые будут зарезервированы. Возвращает оператор указатель на начало выделенного блока памяти. Оператор delete освобождает память, его аргумент — адрес первой ячейки блока, который необходимо освободить.
Динамический массив — массив переменной длины, память под который выделяется в процессе выполнения программы. Выделение памяти осуществляется функциями calloc, malloc или оператором new. Адрес первого элемента выделенного участка памяти хранится в переменной, объявленной как указатель. Например, следующий оператор означает, что описан указатель mas и ему присвоен адрес начала непрерывной области динамической памяти, выделенной с помощью оператора new:
int *mas=new int[10];
Выделено столько памяти, сколько необходимо для хранения 10 величин типа int.
Фактически, в переменной mas хранится адрес нулевого элемента динамического массива. Следовательно, адрес следующего, первого элемента, в выделенном участке памяти — mas+1, а mas+i является адресом i-го элемента. Обращение к i-му элементу динамического массива можно выполнить, как обычно mas[i], или другим способом *(mas +i). Важно следить за тем, чтобы не выйти за границы выделенного участка памяти.
Когда динамический массив (в любой момент работы программы) перестает быть нужным, то память можно освободить с помощью функции free или оператора delete.
Как показала практика, у начинающих кодеров возникает множество вопросов при решении задач по теме «Массивы». В данной статье затронуты вопросы, относящиеся только к массивам в классическом понимании. Работа с контейнерами STL — это отдельная тема.
Как правило, задачи сводятся к следующему: заполнить массив, произвести некие операции с элементами массива, распечатать результат. Уже в постановке задачи угадываются логические блоки её решения. Далее я постараюсь показать типовые «кирпичики», из которых можно сложить решение задачи — т. е. программу.
Организация массива
Память под массив может выделяться автоматически или динамически.
Автоматическое выделение памяти используют, когда размер массива известен на этапе компиляции (т. е. при написании кода).
Динамическое выделение памяти используют, когда размер массива неизвестен на этапе компиляции (допустим, запрашивается у пользователя).
Оба типа массивов могут быть как глобальными (определёнными вне функций), так и локальными (определёнными внутри функции или блока). Здесь для автоматических массивов существует одна тонкость. Память для локального автоматического массива выделяется в стеке. Поэтому размер такого массива должен быть небольшим. В противном случае можно получить переполнение стека и, как следствие, аварийное завершение программы. Переполнение стека также можно получить и при небольших размерах локального автоматического массива, при многократных рекурсивных вызовах функции. Поэтому, когда вы определяете в функции автоматический массив, вы должны точно знать, что делаете.
Глобальные автоматические массивы в плане переполнения стека безопасны. Но они будут видны во всём коде, лексикографически расположенному после объявления массивов, что может спровоцировать их использование напрямую, минуя их передачу в функции через параметры. Это приведёт к возникновению побочных эффектов работы функций, что затрудняет отладку и делает программы менее надёжными. Такого использования глобальных массивов следует избегать.
Для массивов, использующих динамическое выделение памяти, память распределяется из «кучи» (heap). Куча — это память, выделяемая программе операционной системой, для использования этой программой. Размер кучи, как правило, значительно больше размера стека, а для ОС, поддерживающих парадигму виртуальной памяти, размер кучи теоретически может ограничиваться только разрядностью приложения.
Использование автоматических массивов
Автоматические массивы используют, когда размер массива известен на этапе компиляции.
Размер массива в коде настоятельно рекомендуется указывать с помощью именованной константы. Это полезно по нескольким соображениям:
- имя константы должно указывать на область её применения — самодокументирование кода;
- при необходимости изменить в коде размер массива потребуется внести правку только в одном месте;
- размер массива, как правило, используется в циклах прохода по массиву, проверки границы и пр., поэтому использование символического имени избавит от необходимости тщательной проверки и правки всего кода при изменении размера массива.
Тип константного выражения для определения размера (количество элементов) автоматического массива должен быть целочисленный: char , int , unsigned int , long , etc.
Память, отведённая под автоматические массивы, освобождается при выходе из области видимости переменной-массива. Для локальных массивов это функция или блок. Глобальные массивы уничтожаются при выходе из программы.
Пример определения глобального автоматического массива длиной 10 элементов типа int :
Пример определения локального автоматического массива длиной 10 элементов типа int :
Использование массивов с динамическим выделением памяти
Массивы с динамическим выделением памяти используют, когда размер массива не известен на этапе компиляции. Реальный размер массива может вычисляться в программе или вводиться пользователем — неважно.
Память для массива выделяется оператором new в форме new тип[количество_элементов] .
Тип выражения, определяющего размер (количество элементов) массива должен быть целочисленным. Также это выражение может быть и константным.
Когда работа с массивом закончена, память, выделенную под массив необходимо освободить. Это делается с помощью оператора delete в форме delete [] имя_переменной . После того, как память освобождена, работать с массивом нельзя.
Пример использования массива с динамическим выделением памяти:
Заполнение массива значениями
При решении учебных задач, обычно предлагается заполнить массив значениями либо введёнными с клавиатуры, либо случайными значениями из определённого диапазона. Начнём со второго случая, как более простого (Парадокс? Нет, правда жизни).
Заполнение массива случайными числами
Для начала необходим генератор случайных чисел. Ниже приведён код одной из простейших реализаций:
Однако без дополнительных телодвижений стандартная функция rand() будет при каждом запуске программы генерировать одинаковую последовательность случайных чисел (кстати, это очень удобно при отладке!). Для того, что бы при каждом запуске программы получать уникальную последовательность случайных чисел, функцию rand() надо «разогнать» начальным случайным значением. Это делается с помощью функций srand() и time() .
Заполнение массива значениями, естественно, делаем в цикле. Помним, что элементы массива в C/C++ нумеруются с 0. Следовательно последний элемент массива имеет индекс на единицу меньший, чем размер массива.
В примере показано заполнение глобального автоматического массива из 10 элементов типа int случайными значения из диапазона от −100 до 100 включительно:
Обратите внимание на включение заголовочных файлов!
Заполнение массива числами, введёнными пользователем
Как ни странно, это более сложный случай. Дело в том, что во-первых, наличие человека всегда может приводить к некорректному вводу данных (ошибкам), во-вторых, для человека необходимо обеспечить какой-никакой интерфейс, а в-третьих, система потокового ввода-вывода STL имеет свои неприятные особенности.
Оно как бы работает, но если вы попытаетесь в качестве числа (конечно случайно!) ввести 1111111111111111111111111111111111 или 11q, то, в зависимости от компилятора, сможете наблюдать некоторые интересные эффекты работы вашей программы.
Поэтому приходится писать более сложный код:
Подробный разбор данного фрагмента выходит за рамки данной статьи. Но интересующиеся могут его разобрать, вооружившись, например, известной книгой Г. Шилдта.
Вывод на консоль значений из массива
Вывод значений массива на консоль реализуется элементарно. В свете уже вышесказанного даже нечего добавить:
Данный код может быть оптимизирован, но я не стал этого делать, дабы были лучше видны те самые «кирпичики», из которых он собран.
Как видно из комментариев, за поиск минимального значения и его индекса отвечает последний фрагмент программы.
Определяются две переменные, одна из которых будет содержать минимальное значение, а вторая — индекс элемента с минимальным значением. Эти переменные инициализируются первым (нулевым) элементом массива и нулём соответственно. Далее, в цикле каждое следующее значение элемента массива сравнивается с уже найденным наименьшим значением и, если текущее значение меньше запомненного, то запоминается текущее значение и его индекс.
Понятно, что поиск максимального значения производится полностью аналогично, с точностью до знаков «больше»/«меньше», вывода строки пользователю и наименования переменных.
Поиск определённого значения в массиве
Поиск определённого значения в неупорядоченном массиве осуществляется с помощью алгоритма линейного поиска. Этот простейший алгоритм заключается в последовательном переборе элементов массива и сравнением их с искомым значением.
Задачи на поиск в массиве могут быть в двух формах:
- найти первое (последнее) вхождение искомого значения
- найти все вхождения
Поиск первого вхождения:
Поиск последнего вхождения:
Обратите внимание на следующие моменты.
Переменная цикла i описана перед циклом. Таким образом, эта переменная продолжает существовать после окончания цикла, и её значение может быть использовано.
Если искомый элемент найден, то цикл завершается досрочно оператором break : просматривать остальную часть массива не имеет смысла — задача уже выполнена.
Во втором случае переменная i имеет знаковый тип int . Отрицательное значение используется в качестве флага, что весь массив просмотрен, и значение не найдено.
Поиск всех вхождений:
Здесь цикл не прерывается. Массив просматривается полностью.
Сумма/произведение отрицательных элементов массива
Сумма элементов массива с чётными/нечётными индексами
Работа с массивами с применением функций
Практически все фрагменты кода, приведённые выше, можно оформить как функции, а массив передавать через параметры. В качестве примера приведу программу нахождения суммы элементов массива с чётными индексами, в которой используется (ради разнообразия) динамический массив.
Обратите внимание, что выделение памяти под массив и её освобождение происходит в одной функции (в данном случае, в main() ). Выделять память в одной функции, а освобождать в другой — плохая идея, чреватая ошибками.
Заключение
В этой статье рассмотрены только самые элементарные приёмы работы с массивами, которые помогут (надеюсь!) начинающему кодеру понять принципы работы с массивами.
Обычно в языке Паскаль используются статические массивы вида:
var mas: array [1..10] of integer;
Рассмотрим работу с динамическим массивом.
Объявляется динамический массив в теле программы:
begin . var a: array of integer;
Или объявление с инициализацией:
А выделение памяти и размер такого массива задается уже по ходу программы:
var a: array of integer; var n:=readInteger; a:=new integer[n];
var a: array of integer; a:=new integer[readInteger];
var a: array of integer; var n:=readInteger; SetLength(a,n); // устанавливаем размер массива а
Ссылочная объектная модель: память выделяется служебным словом NEW
var a := new integer[5];
Организация памяти для массива a
Инициализация, присваивание и вывод
Возможна инициализация динамического массива при описании:
var a: array of integer := (1,2,3);
Новые способы заполнения массива (заполнители):
var a:=Arr(1,2,3);// по правой части - integer
var a:=ArrFill(5,2); // 2 2 2 2 2
Ввод с клавиатуры:
var a:=ReadArrInteger(5); var a:=ReadArrReal(5);
Заполнение случайными числами:
var a:=new integer[10]; a:=arrRandomInteger(10);
Или с разделителем между элементами:
Переприсваивание:
var a: array of integer := (1,2,3); var b:=a; // [1,2,3]
Но!
Если теперь переприсвоить значение элементов массива b , то изменится и массив a :
var a: array of integer := (1,2,3); var b:=a; b[2]:=1; print(a); //[1,2,1]
Для того, чтобы избежать данной ситуации, необходимо создать массив b , как копию массива a :
var a: array of integer := (1,2,3); var b:=Copy(a); b[2]:=1; print(a); //[1,2,3]
Очистка динамического массива
Если в программе случайно создается повторно один и тот же массив:
var a: array of integer; a:=new integer[4]; . a:=new integer[5];
Но можно очистить память и принудительно:
В процессе очистки выполнение программы и всех процессов приостанавливается. По этой причине сборщик мусора в системах реального времени не используется.
Работа с элементами массива
for var i:=0 to High(a) do print(a[i]);;
foreach var x in a do print(x);
Примеры работы с динамическими массивами
Пример: поиск элемента в массиве (выдавать индекс искомого)Выполнение:
Выполним сначала БЕЗ использования обобщенной функции:
function IndexOf(a:array of integer;x:integer):integer; begin result:=-1; for var i:=0 to High(a) do if a[i]=x then begin result:=i; break end end; begin var a:=Arr(1,2,3,4,5); print(IndexOf(a,5)) end.
А теперь, выполним с использованием обобщенной функции:
function IndexOf<T>(a:array of T;x:T):integer; begin . end; begin var a:=Arr(1,2,3,4,5); print(IndexOf(a,5)) end.
При вызове обобщенной функции компиляция будет в два этапа:
- Автовыведение типа Т, сравнение с реальными цифрами, и т.к. числа целые, то Т определится как integer.
- Берется тело функции и заменяется Т на integer (инстанцирование функции с конкретным типом)
Методы для работы с массивами
var a:=Arr(1,2,3,4,5); reverse(a); // [5,4,3,2,1]
var a:=Arr(2,3,1,4,5); //[1,2,3,4,5] sort(a);
Сдвиги
Стандартное выполнение:
var a:=Arr(1,2,3,4,5,6); var x:=a[0]; for var i:=0 to 4 do a[i]:=a[i+1]; a[5]:=x; print(a)
Если сдвиг вправо:
Срезы
- Срезы доступны только на чтение, присваивать им значения нельзя.
- Тип среза такой же, как у массива.
- Срезы работают с массивами, строками и со списками.
var a:=Arr(1,2,3,4,5,6); print(a[1:5]) // [2,3,4,5]
println(a[:5]); // [1,2,3,4,5] - с начала до 5-го не включая println(a[1:]); // [2,3,4,5,6] - до конца println(a[::2]); // [1,3,5] - третий параметр - это шаг
Т.о. выполнение при помощи срезов выглядит так:
Перестановка:
var a:=Arr(1,2,3,4,5,6); var k:=a.Length-1; // 6 - 1 a:=a[k:]+a[:k]; print(a) // [6,1,2,3,4,5]
Т.е. создан еще массив уже со сдвигом.
Удаление и вставка элементов массива. Списки
Решение задач
Простые задачи
Пример: Напишите функцию SumArray , возвращающую сумму элементов заданного динамического массива. Выведите длину и сумму элементов массиваВыполнение:
function SumArray(var a: array of integer): integer:=a.Sum; begin var a:=new integer[10]; a:=arrRandomInteger(10); foreach var x in a do print(x); println('длина массива = ',a.Length,' сумма = ',ArraySum(a)) end.
Выполнение:
Дополните код:
procedure PrintArr( a: array of integer; delim:string:='; '); begin foreach var . in . do print(x,delim); end; begin var a:=new integer[10]; a:=arrRandomInteger(10); . end.
Задание 2: Заполнить массив (b) первыми N ( N ≥ 0 ) положительными нечётными числами массива a. Необходимо реализовать два различных решения задачи:
function MakeOddArrFunc (a: array of integer; n:integer):array of integer; begin var b:= new integer[n]; var j:=0; for var i:=0 to . do if . then begin . ;. ;end; SetLength(. ); result:=b; end; procedure MakeOddArrProc (a: array of integer;var b: array of integer; n:integer); begin b:= new integer[n]; var j:=0; for var i:=0 to . do if . then begin . ;. ;end; SetLength(. ); end; begin var a:=arrRandomInteger(20); var b: array of integer; var n:=readInteger('введите N'); println('исходный массив ',a); println('результат работы с функцией ',MakeOddArrFunc(a,n)); MakeOddArrProc(a,b,n); print('результат работы с процедурой ',b) end.
Задание 3: Заполнить целочисленный массив первыми N ( N ≥ 0 ) числами Фибоначчи. Достаточно одной из реализаций: функции или процедурыfunction MakeFibArr (n : integer):array of integer; begin var c:= new integer[n]; c[0]:=. ;c[1]:=. ; for var i:=2 to n-1 do . ; result:=c; end; begin var n:= readinteger; . ; end.
Пример: Описать функцию MakeRandomRealArr с тремя параметрами: целым числом N ( N ≥ 0 ), вещественными параметрами a и b ( a ).Функция должна возвращать массив (тип которого либо RealArr либо array of real ) из N случайных вещественных элементов в диапазоне a..b .
Продемонстрируйте заполнение и вывод на экран массива
Выполнение:
function MakeRandomRealArr (n : integer;a,b:real):array of real; begin var c:= ArrRandomReal(n,a,b); result:=c; end; begin var n:= readinteger; var a:=readReal; var b:=readReal; println('результат работы с функцией ',MakeRandomRealArr(n,a,b)); end.
Задание 5: Дан непустой массив вещественных чисел. Найти его наименьший элемент (функция FindMin ) Пример: Дан целочисленный массив. Обнулить все его элементыс нечётными индексами (процедура SetToZeroOddIndexes ).
Условный оператор не использовать
Выполнение:
procedure SetToZeroOddIndexes(a: array of integer); begin var j:=0; while j<=a.Length-1 do begin a[j]:=0;j+=2; end; println('результирующий массив: ',a); end; begin var a:=arrRandomInteger(10); println('исходный массив: ',a); SetToZeroOddIndexes(a); end.
Задание: Дан целочисленный массив, содержащий не менее трёх элементов. Найти значение первого локального минимума (функция FirstLocMin ).Пояснение: локальным минимумом считается тот элемент, который меньше каждого из своих соседей. Считать, что локальный минимум в массиве есть. Первый и последний элемент в качестве локальных минимумов не рассматривать. Задание: Дан массив целых чисел, содержащий не менее трёх элементов. Найти значение и номер последнего локального максимума (процедура с двумя выходными параметрами)
Задачи на срезы
Задание srez1: Дан массив размера N . Вывести его элементы в обратном порядке Пример: Дан массив A размера N и целое число K ( 1 ). Вывести его элементы с порядковыми номерами, кратными K :Условный оператор не использовать.
Выполнение:
var n:=ReadInteger; var a:=ReadArrReal(n); var k:=ReadInteger; a[k-1 : : k].Print;
Задание srez2: Дан массив A размера N ( N - четное число). Вывести его элементы с четными номерами в порядке возрастания номеров:Условный оператор не использовать.
Задание srez3: Дан массив A размера N ( N - нечетное число). Вывести его элементы с нечетными номерами в порядке убывания номеров:Условный оператор не использовать.
Пример: Дан массив A размера N . Вывести сначала его элементы с четными номерами (в порядке возрастания номеров), а затем - элементы с нечетными номерами (также в порядке возрастания номеров):Условный оператор не использовать
Выполнение:
var n:=ReadInteger; var a:=ReadArrReal(n); var srez:=a[1::2]+a[::2]; Print(srez);
Задание srez4: Дан массив A размера N . Вывести сначала его элементы с нечетными номерами (в порядке возрастания номеров), а затем - элементы с четными номерами (в порядке убывания номеров):Условный оператор не использовать
Пример: Дан массив размера N и целые числа K и L ( 1 ). Найти среднее арифметическое элементов массива с номерами от K до L включительноВыполнение:
var n:=ReadInteger; var a:=ReadArrReal(n); var k:=ReadInteger; var l:=ReadInteger; var srez:=a[k-1:l].Average; print(srez);
В этой статье вы научитесь работать с массивами: объявлять, инициализировать и получать доступ к элементам
Содержание
Объявление массива в C/C++
В программировании часто встречается задача обработки множества экземпляров однотипных данных. Представьте себе ситуацию: мы провели опрос 100 человек и узнали их возраст. Чтобы сохранить собранные данные, вы можете создать целочисленный массив, содержащий 100 элементов:
В C++ массивы статичны: вы не сможете изменить размер или тип элементов после объявления.
Доступ к элементам массива
Вы можете получать доступ к элементам массива, используя индексы и оператор [] . Допустим, вы объявили массив marks , как показано ниже. К первому элементу можно обратиться выражением marks[0] , ко второму - выражением marks[1] , и так далее. Доступ всегда начинается с единицы, а индекс последнего элемента на единицу меньше размера массива.
Инициализация массива при объявлении
Можно инициализировать массив при объявлении. Для этого надо указать в списке столько значений, сколько вмещает массив, либо одно значение 0, чтобы заполнить массив нулями:
Обход элементов массива в цикле
Узнать число элементов в массиве можно функцией std::size. Обойти можно, используя цикл по индексам либо range-based for:
Неопределённое поведение: выход за границы (out of bounds)
Выход за пределы массива является неопределённым поведением (англ. undefined behavior). Нет гарантий, как поведёт себя программа в этом случае. Высока вероятность, что вы испортите память других переменных, но эффект может различаться в разных режимах компиляции:
Передача массива как параметра функции
Массив в стиле языка C хранит только указатель на начало и не хранит свой размер, что и создаёт сложность в передаче в функцию. Размер массива известен во время компиляции, но не известен во время выполнения. Поэтому передать размер можно несколькими не очень очевидными путями:
Динамически изменяемый массив
Обычные массивы имеют неизменный размер. Вы можете ввести вспомогательную переменную, которая бы хранила число реально используемых ячеек массива. Но и в этом случае вы не сможете использовать элементов больше, чем задано при компиляции в виде размера массива.
Так мог бы выглядеть имитация динамического массива:
Класс std::vector<T>
Стандартная библиотека C++ содержит шаблонный класс vector, который работает как динамический массив произвольного размера. Размер может расти до тех пор, пока у операционной системы есть область памяти подходящего размера (вплоть до нескольких гигабайт).
Класс является шаблонным, то есть при объявлении переменной потребуется параметризовать шаблон класса vector типом элемента:
Использование вектора похоже на использование массива:
- работает запрос элемента ages[index] , причём индексация так же начинается с нуля
- при выходе за границы динамического массива так же возникает неопределённое поведение (англ. undefined behavior)
- работает перебор элементов с помощью индексов, range-based for или итераторов
- есть метод size для получения размера: ages.size()
Добавление элементов в конец массива
Для добавления существует два метода: push_back и emplace_back
- push_back получает значение элемента и добавляет в конец
- emplace_back работает сложнее: он получает параметры, необходимые конструктору элемента, и конструирует его прямо в конце массива
Вы можете практически всегда использовать push_back. Метод pop_back можно использовать для удаления элемента:
В документации std::vector можно прочитать о других методах.
Перемещение элементов в памяти при изменении массива
Динамический массив использует для хранения элементов динамическую память (так же известную как “куча”, англ. heap). При добавлении большого числа элементов динамический массив несколько раз перераспределяет память, поскольку выделенной ранее линейной области памяти уже не хватает для хранения всех элементов. Обычно при нехватке памяти под очередной элемент vector запрашивает новую область памяти в 1,5-2 раза больше предыдущей, перемещает в неё уже существующие элементы и добавляет в конец новый, а затем освобождает старую область памяти.
Если не сообразили, как это происходит, взгляните на картинку:
Новая область находится уже другом месте, потому что менеджер динамической памяти не мог просто взять и расширить старую область (ведь сразу за ней находилась чужая память). Поэтому все итераторы, ссылки и указатели на элементы могут стать некорректными после любого изменения массива!
Метод erase для удаления элементов из середины
Метод erase класса vector получает итератор и уничтожает элемент, на который итератор указывает:
Последствия перемещения элементов: ошибка в простом цикле с erase
Использование итератора, ссылки или указателя на элемент после перераспределения памяти в массиве является неопределённым поведением: скорее всего произойдёт падение программы либо будет пропущено несколько элементов коллекции. Это показано в примере ниже:
Если вы запустите этот код, вы можете увидеть что угодно. Скорее всего программа выведет 10 38 99 , хотя должна вывести 10 23 7 38 99 по замыслу автора.
Программа корректно напечатает 10 23 7 38 99 .
Идиома remove_if + erase
В C++ есть замечательная библиотека алгоритмов <algorithm> . В данном случае алгоритмом называют шаблон функции, способный заменить цикл в какой-то одной узкой задаче. Например, remove_if перемещает элементы, соответствующие условию, в конец массива (в “удалённую” зону), и возвращает итератор на начала “удалённой” зоны. Затем вызовом erase можно уничтожить элементы из этой зоны.
Читайте также: