Возвращая последний элемент контейнера, который не имеет метода back () в C ++? - PullRequest
0 голосов
/ 16 апреля 2019

Каков наилучший метод для возврата последнего элемента в контейнере, который не предоставляет функцию-член back(), например std::set?

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

Например:

std::set<int> set = {1,2,3,4,5};

int end = *(set.end());
int beforeEnd = *(--set.end());

std::cout << "set.end() -> " << end << std::endl;
std::cout << "--set.end() -> " << beforeEnd << std::endl;

Однако оба возвращают:

set.end() -> 5
--set.end() -> 5

Это правильный способ получения последнего элемента и почему они возвращают одно и то же значение?

Ответы [ 2 ]

2 голосов
/ 16 апреля 2019

Это

int end = *(set.end());

Как прокомментировал πάντα ῥεῖ , имеет неопределенное поведение. Это потому что std::set::end

Возвращает итератор для элемента , следующего за последним элементом контейнера. Этот элемент действует как заполнитель; попытка доступа к нему приводит к неопределенному поведению. (https://en.cppreference.com/w/cpp/container/set/end, выделенная мина)

Другая строка:

int beforeEnd = *(--set.end());

Работать не гарантировано. Смотрите, например https://en.cppreference.com/w/cpp/iterator/prev, Акцент шахты:

Хотя выражение --c.end() часто компилируется, это не гарантируется: c.end() является выражением rvalue , и не существует требования к итератору, определяющего это уменьшение значения rvalue гарантированно работает . В частности, когда итераторы реализованы в виде указателей, --c.end() не компилируется, а std::prev(c.end()) -.

Так что может произойти сбой по той же причине, по которой он не скомпилируется:

int arr[4] = {1,2,3,4};
int *p = --(arr + 4); // --> error: expression is not assignable

Вместо этого вы можете написать что-то вроде следующего.

std::set<int> set = {1,2,3,4,5};

if ( set.begin() != set.end() )
{
    auto itLast = std::prev(set.end());

    std::cout << "last -> " << *itLast << '\n';
}
1 голос
/ 16 апреля 2019

Является ли единственным способом получить последний элемент для уменьшения итератора перед разыменованием его?

Нет, есть и другие варианты.

Самый простой способиспользуя обратные итераторы (оба std::set::rbegin или std::set::crbegin), которые непосредственно дают вам элемент, который проходит один итератор std::set s end .

От cppreference.com , std::set::rbegin and std::set::crbegin

Возвращает обратный итератор для первого элемента обращенного контейнера .Это соответствует последнему элементу нереверсивного контейнера .Если контейнер пуст, возвращаемый итератор равен rend ().

std::set<int> set = { 1,2,3,4,5 };

auto iter = set.rbegin();
const int beforeEndIter = *iter;
std::cout << "--set.end() -> " << beforeEndIter  << '\n';

( update ) в случае второго последнего элемента в контейнере (т.е. двапосле итератора end , используйте std :: next по той же причине, которая указана в другом ответе для std::prev. См. Демонстрацию

std::set<int> set = {1, 2};

const bool hasElements = set.cbegin() != set.cend();
auto iter = set.rbegin();
if(hasElements && iter != set.rend())            std::cout << "--set.end() -> " << *iter << '\n';
if(hasElements && std::next(iter) != set.rend()) std::cout << "two past set.end() -> " << *std::next(iter) << '\n';

выходы :

--set.end() -> 2
two past set.end() -> 1

Второй вариант упоминался в другомответ.


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

...