Как получить итератор на элемент vector
Перейти к содержимому

Как получить итератор на элемент vector

  • автор:

Получить указатель на новый элемент вектора c++

Насколько я понял, при добавлении элемента в вектор через emplace_back, он станет на свободное место (т.е. был элемент, его удалили, вот и свободное место) или же в конец вектора. Вопрос: как получить указатель на только что добавленный элемент вектора?

Отслеживать
задан 26 сен 2015 в 17:21
1,281 2 2 золотых знака 15 15 серебряных знаков 30 30 бронзовых знаков

А что будет, если стереть элемент через erase? Место «освободится» же. Вот в таком то случае .back() и не поможет.

26 сен 2015 в 17:33

3 ответа 3

Сортировка: Сброс на вариант по умолчанию

Если вас интересует именно указатель на добавленный элемент, а не итератор, то вы можете его получить, допустим, следующим образом:

#include #include #include int main() < std::vector> v; v.emplace_back( int( 'A' ), 'A' ); auto p = &v.back(); std::cout first << ": " second

Вывод программы на консоль:

65: A 

Ежели вы используете метод insert вместо одного из методов всавки в конец вектора, то метод insert возвращает итератор на вставленный элемент. Поэтому вы можете получить указатель на элемент, используя этот итератор. Например,

#include #include #include int main() < std::vectorv = < 1, 2, 4, 5 >; auto it = v.insert( std::find( v.begin(), v.end(), 4 ), 3 ); int *p = &*it; std::cout

Вывод программы на консоль:

В программе для обращения к вставленному элементу используется как итератор, так и указатель.

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

std::vector v = < 1, 2, 3, 4, 5 >; int *p = &v[2]; 

Имейте в виду, что когда элемент вектора удаляется, то все элементы, стоящие за удаляемым элементом, сдвигаются к началу вектора, занимая место удаленного элемента, если только удаленный элемент не был последним элементом вектора. То есть элементы вектора всегда располагаются последовательно друг за другом в непрерывном участке памяти. Никаких "дыр" в векторе не бывает.

Если вам надо вставлять элементы в середину вектора, то лучше будет использовать std::deque , или если прямой доступ к элементам не нужен, то std::list .

Как получить итератор на элемент vector

Итераторы обеспечивают доступ к элементам контейнера и представляют реализацию распространенного паттерна объектно-ориентированного программирования "Iterator". С помощью итераторов очень удобно перебирать элементы. В C++ итераторы реализуют общий интерфейс для различных типов контейнеров, что позволяет использовать единой подход для обращения к элементам разных типов контейнеров.

Стоит отметить, что итераторы имеют только контейнеры, адаптеры контейнеров — типы std::stack , std::queue и std::priority_queue итераторов не имеют.

Итератор описывается типом iterator . Для каждого контейнера конкретный тип итератора будет отличаться. Так, итератор для контейнера list представляет тип list::iterator , а итератор контейнера vector представляет тип vector::iterator и так далее. Однако общий функционал, который применяется для доступа к элементам, будет аналогичен.

Для получения итераторов контейнеры в C++ обладают такими функциями, как begin() и end() . Функция begin() возвращает итератор, который указывает на первый элемент контейнера (при наличии в контейнере элементов). Функция end() возвращает итератор, который указывает на следующую позицию после последнего элемента, то есть по сути на конец контейнера. Если контейнер пуст, то итераторы, возвращаемые обоими методами begin и end совпадают. Если итератор begin не равен итератору end, то между ними есть как минимум один элемент.

Обе этих функции возвращают итератор для конкретного типа контейнера:

#include #include int main() < std::vectornumbers< 1,2,3,4 >; std::vector::iterator iter = numbers.begin(); // получаем итератор >

В данном случае создается вектор - контейнер типа vector, который содержит значения типа int. И этот контейнер инициализируется набором . И через метод begin() можно получить итератор для этого контейнера. Причем этот итератор будет указывать на первый элемент контейнера.

С итераторами можно проводить следующие операции:

  • *iter : получение элемента, на который указывает итератор
  • ++iter : перемещение итератора вперед для обращения к следующему элементу
  • --iter : перемещение итератора назад для обращения к предыдущему элементу. Итераторы контейнера forward_list не поддерживают операцию декремента.
  • iter1 == iter2 : два итератора равны, если они указывают на один и тот же элемент
  • iter1 != iter2 : два итератора не равны, если они указывают на разные элементы
  • iter + n : возвращает итератор, который смещен от итератора iter на n позиций вперед
  • iter - n : возвращает итератор, который смещен от итератора iter на n позиций назад
  • iter += n : перемещает итератор на n позиций вперед
  • iter -= n : перемещает итератор на n позиций назад
  • iter1 - iter2 : возвращает количество позиций между итераторами iter1 и iter2
  • >, >=,

Стоит отметить, что итераторы не всех контейнеров поддерживают все эти операции.

Итераторы для типов std::forward_list , std::unordered_set и std::unordered_map не поддерживают операции --, -= и -. (поскольку std::forward_list - однонаправленный список, где каждый элемент хранит указатель только на следующий элемент)

Итераторы для типа std::list поддерживают операции инкремента и декремента, но не поддерживаются операции +=, -=, + и -. Те же ограничения имеют итераторы контейнеров std::map и std::set .

Операции +=, -=, +, -, , >= и поддерживаются только итераторами произвольного доступа (итераторы контейнеров std::vector , array и deque )

Получение и изменение элемента контейнера

Поскольку итератор по сути представляет указатель на определенный элемент, то через этот указатель мы можем получить текущий элемент итератора и изменить его значение:

#include #include int main() < std::vectornumbers< 1,2,3,4 >; auto iter < numbers.begin() >; // получаем итератор // получаем элемент, на который указывает итератор std::cout 

После получения итератора он будет указывать на первый элемент контейнера. То есть при выражение *iter возвратит первый элемент вектора.

Прибавляя или отнимая определенное число, можно переместить итератор вперед или назад на определенное количество элементов:

#include #include int main() < std::vectornumbers< 10, 20, 30, 40 >; auto iter < numbers.begin() >; // получаем итератор // переходим на 1 элемент вперед ко 2-му элементу ++iter; std::cout 

Опять же повторю, что стоит учитывать, что не все операции поддерживаются итераторами всех контейнеров.

Перебор контейнера

Например, используем итераторы для перебора элементов вектора:

#include #include int main() < std::vectornumbers< 10, 20, 30, 40 >; auto iter < numbers.begin() >; // получаем итератор while(iter!=numbers.end()) // пока не дойдем до конца < std::cout // аналогичный пример с циклом for for(auto start; start !=numbers.end(); start++ ) < std::cout >

При работе с контейнерами следует учитывать, что добавление или удаление элементов в контейнере может привести к тому, что все текущие итераторы для данного контейнера, а также ссылки и указатели на его элементы станут недопустимыми. Поэтому при добавлении или удалении элементов в контейнере в общем случае следует перестать использовать текущие итераторы для этого контейнера.

Константные итераторы

Если контейнер представляет константу, то для обращения к элементам этого контейнера можно использовать только константный итератор (тип const_iterator ). Такой итератор позволяет считывать элементы, но не изменять их:

const vector numbers; for(auto iter ; iter != numbers.end(); ++iter) < std::cout 

В данном случае итератор iter будет представлять тип std::vector::const_iterator .

Для получения константного итератора также можно использовать функции cbegin() и cend . При этом даже если контейнер не представляет константу, но для его перебора используется константный итератор, то опять же нельзя изменять значения элементов этого контейнера:

#include #include int main() < std::vectornumbers < 1, 2, 3, 4, 5 >; for (auto iter ; iter != numbers.cend(); ++iter) < std::cout >

Стоит отметить, что для типов std::set (множество) и std::map (словарь) доступны только константные итераторы.

Реверсивные итераторы

Реверсивные итераторы позволяют перебирать элементы контейнера в обратном направлении. Для получения реверсивного итератора применяются функции rbegin() и rend() , а сам итератор представляет тип reverse_iterator :

#include #include int main() < std::vectornumbers < 1, 2, 3, 4, 5 >; for (auto iter ; iter != numbers.rend(); ++iter) < std::cout std::cout

В данном случае итератор будет представлять тип std::vector::reverse_iterator . Консольный вывод программы:

5 4 3 2 1

Если надо обеспечить защиту от изменения значений контейнера, то можно использовать константный реверсивный итератор, который представлен типом const_reverse_iterator и который можно получить с помощью функций crbegin() и crend() :

#include #include int main() < std::vectornumbers < 1, 2, 3, 4, 5 >; for (auto iter ; iter != numbers.crend(); ++iter) < std::cout >

Итераторы для массивов

Для массивов в C++ также имеется поддержка итераторов. Для этого в стандартной библиотеке С++ определены функции std::begin() (возвращает итератор на начало массива) и std::end() (возвращает итератор на конец массива):

int data[]; // получаем итератор на начало массива auto iter = std::begin(data); // получаем итератор на конец массива auto end = std::end(data);

Как и контейнеры, массив можно перебрать с помощью итераторов:

#include int main() < int data[]; // перебор массива с помощью итераторов for(auto iter ; iter != std::end(data); iter++) < std::cout >

Но перебор массива вполне можно сделать и другими способами - через индексы, обычные указатели. Но итераторы на массивы могут быть полезны при манипуляции с контейнерами. Например, функция insert() , которая есть у ряда контейнеров, позволяет добавить в контейнер какую-то часть другого контейнера. Для выделения добавляемой части могут применяться итераторы. И таким образом, с помощью итераторов можно добавить в контейнер, например, в вектор какую-то часть контейнера:

#include #include int main() < int data[]; std::vector numbers < 1, 2, 3, 4>; // добавляем в конец вектора numbers из массива data элементы со 2-го по предпоследний (включительно) numbers.insert(numbers.end(), std::begin(data) + 1, std::end(data)-1); for (auto iter ; iter != numbers.end(); ++iter) < std::cout std::cout

numbers.insert(numbers.end(), std::begin(data) + 1, std::end(data)-1);

Добавляет в вектор numbers, начиная с позиции, на которую указывает итератор numbers.end() (то есть в самый конец вектора), диапазон элементов массива data. Начало этого диапазона задается выражением std::begin(data) + 1 (то есть со 2-го элемента), а конуц - выражением std::end(data)-1 (то есть по предпоследний элемент включительно). Консольный вывод:

stl: добавить в вектор элемент и получить его итератор

Подскажите, можно ли добавить в вектор элемент и получить итератор одной функцией или все таки придется делать так:

data.push_back(object); auto it = data.begin() + (data.size() - 1); 

Отслеживать
13.8k 12 12 золотых знаков 44 44 серебряных знака 77 77 бронзовых знаков
задан 16 янв 2019 в 10:28
37.5k 4 4 золотых знака 28 28 серебряных знаков 77 77 бронзовых знаков

3 ответа 3

Сортировка: Сброс на вариант по умолчанию

auto it; 

Отслеживать
ответ дан 16 янв 2019 в 11:07
user7860670 user7860670
29.9k 3 3 золотых знака 17 17 серебряных знаков 36 36 бронзовых знаков

Вопрос был о добавлении существующего объекта object , функция emplace тут подходит слабо так как требует лишнего копирования объекта.

16 янв 2019 в 12:07

@freim Что значит "не подходит"? Это рабочий код. Никакого лишнего копирования или создания ненужных промежуточных объектов тут не происходит (в отличие от функции insert ).

16 янв 2019 в 12:13
а чем emplace от Insert в конец отличается?
16 янв 2019 в 12:24

В данном случае emplace будет полностью эквивалентно insert - и там, и там будет вызываться конструктор копии. Работать, конечно, будет, но это нецелевое и вводящее в заблуждение использование функции. К тому же в случае insert можно использовать и перемещение - в отличие от emplace .

16 янв 2019 в 12:56

@freim: Что за ерунда? Функции emplace почти полностью замещают функции push и insert . В современнои коде надо стараться пользоваться именно emplace . Ничего нецелевого здесь нет - наоборот, именно так и надо. И emplace по определению полностью поддерживает перемещение. О каком "в отличие" вы ведете речь?

Итераторы в C++: введение

обложка статьи

Всем привет! Изучая контейнеры STL, мы использовали новый вид переменных - итераторы. Так давайте узнаем, зачем ими пользуются?

Что такое итератор

Итератор - это такая структура данных, которая используется для обращения к определенному элементу в контейнерах STL. Обычно из используют с контейнерами set , list , а у вектора для этого применяют индексы.

Кстати по мере того, как мы будем изучать итераторы, вам все больше будет казаться, что итераторы и есть указатели (это мы разберем ниже).

Как создать итератор

Для создания итератора мы должны с самого начала программы подключить библиотеку .

#include 

Далее для его создании нам потребуется использовать вот эту схему:

контейнер> его тип> :: iterator имя итератора>;
  • - указываем требуемый контейнер, на который и будет ссылаться итератор. Например map , vector , list .
  • - указываем тип контейнера.

Вам нужно помнить! Если вы создали итератор и случайно ввели не тот тип данных, который указали при создании контейнера, то ваша программа будет работать неправильно и вообще может сломается.

Методы начала и конца контейнеров

У каждого контейнера имеются два метода, которые, как указатели передают итератору начало или конец контейнера - begin() и end().

  • Метод begin() отправит итератор на начала контейнера.
  • А метод end() отправит на конец. А если точнее, то на одну ячейку больше последней. Если мы попытаемся вывести эту ячейку у нас появятся проблемы с компилятором 🙂 .

Их мы можем использовать даже без подключения библиотеки , что очень удобно.

Также при инициализации итератора мы можем с самого начала написать, куда он будет указывать:

vector int> i_am_vector; vector int> :: iterator it = i_am_vector.begin();

Итератор на vector

Для итератора на vector вы можете:

  • Выполнять операцию разыменования (обращаться к значению элемента на которое указывает итератор), как мы это делали с указателем.
int x = *it;
  • Использовать инкремент ( it++, ++it ) и декремент ( it--, --it ).
  • Применять арифметические операции. Так например мы можем сместить итератор на пять ячеек в право, вот так:
it += 5;
  • Сравнивать на равенства.
if (it == it2)  ...
  • Передать переменной разницу итераторов.
int x = it - it2;

Использовать выше сказанные операции можно только с идентичными итераторами, которые указывают на одинаковый контейнер и тип.

Есть исключение из правил - если вы создадите два одинаковых итератора на map то при сравнивании они не будут одинаковы.

Например, если мы создали два итератора на один и тот же контейнер, но указали для них разный тип данных и решили использовать выше сказанные операции - то компилятор начнет ругаться.

vector int> vector_first; vector double> vector_second; vector int> :: iterator it = vector_first.begin(); vector double> :: iterator it2 = vector_second.begin(); if (it == it2)  // ошибка! cout  <"it == it2"; >

Итератор на list, set, map

Для итераторов на list , set , map немного урезан функционал. Так вы не можете:

  • Использовать арифметические операции.
it += 5; // it *= 2; // it /= 3; // ошибка it -= 5; //
  • Применять операции сравнения ( > и < ):
if (it > it_second)  ... // // ошибка if (it  it_second)  ... //

Все остальное можно использовать:

  • Применять инкремент и декремент.
it--; // все it++; // нормально!
  • Использовать операцию разыменования.
cout  *it; *it += 5;
  • Сравнивать два итератора на равенство и неравенства:
if (it == it_second)  ... // // правильно if (it != it_second)  ... //

Кстати использовать арифметические операции, чтобы увеличить итератор на один, как это делает инкремент - нельзя.

Но вы можете сказать: “Так что мы можем двигать итератор только на один элемент? Это же неудобно!“. Да было бы совсем не гибко со стороны C++ делать вот такое, но они позаботились и создали функцию - advanсe() , она заменяет операции увеличения и уменьшения над итераторами.

Вот как она работает:

advance(итератор>, значение>);
  • - сюда мы должны указать итератор, который и нужно изменить.
  • - тут мы должны вписать число на которое должны увеличить или уменьшить итератор.

Если мы увеличиваем итератор, то используем оператор + к числу. Но можно и просто записать число без оператора + .

Если же нужно уменьшить итератор, то мы добавляем оператор - .

advanсe(it, 5); // сместили на 5 ячеек

Как работают итераторы

Чтобы понять, как работают итераторы, давайте разберем их использование на практике. В примере ниже с помощью итератора мы выводим содержимое вектора на экран:

#include #include #include using namespace std; int main()  setlocale(0, ""); vector name_vector; name_vector.push_back(3); name_vector.push_back(4); name_vector.push_back(6); vector int> :: iterator it; for (it = name_vector.end() - 1; it >= name_vector.begin(); it--)  cout  <*it  <" "; > system("pause"); return 0; >
  • В строке 10: создали вектор name_vector .
  • Дальше в последующих трех строках занимаемся его заполнением.
  • В строке 16: создали итератор под именем it .
  • В цикле for мы написали, что итератор указывает на последнюю ячейку вектора. С помощью вот такой не замысловатой конструкции :
it = name_vector.end() - 1;

Выше мы говорили, что метод end() указывает на одну ячейку больше последней. Поэтому, чтобы обратится к последнему элементу в векторе нам понадобилось отнять 1.

  • Используя операцию разыменования, в теле цикла, мы вывели все элементы.
cout  *it;

Вы наверняка заметили, что мы выводим элеме

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *