#include <iostream>
#include <list>
using namespace std;
int main() {
list<int> A;
A.push_back(1);
A.push_back(2);
A.push_back(3);
auto it = A.begin();
cout << *it << ' ';
advance(it, 1);
cout << *it << ' ';
advance(it, 1);
cout << *it << ' ';
advance(it, 1);
cout << *it << ' ';
advance(it, 1);
cout << *it << ' ';
}
Я считаю, что list — это двусвязный список.
Удивительно, но результат равен 1 2 3 3 1.
Может кто-нибудь объяснить, что здесь происходит?
Вот вопросы:
3.
Здесь дополнительный advance перемещает итератор на A.end().
Я считаю, что A.end() — это воображаемый узел, который не указывает на исходный элемент. Однако он распечатывает 3. Почему?1? list — это не круговая очередь.Вы, кажется, делаете поспешный вывод, что 1, который вы видите, — это 1 из начала списка, но это всего лишь 1. Даже если бы следующие числа были 2 и 3, вы никак не могли бы сказать, что это числа в списке. Во всяком случае, его undefined и вывод может быть любым
Ясно, это означает, что я должен проверить диапазон перед использованием std::advance. Спасибо за ваш вклад!
Да, никогда не забывайте о проверке диапазона! STL (который был основой этих контейнеров и алгоритмов) был разработан как острый инструмент для людей, которые обращают внимание. Нет сетей, которые вас поймают, но и штрафа за производительность вы не платите. Большинство реализаций в настоящее время поставляются с диагностическим режимом, который может уберечь вас от этого в целях разработки. Однако вам нужно активировать его явно. Как это сделать, должно быть задокументировано.
или вы знаете, что элементов достаточно без проверки. Я имею в виду, что в этом текущем примере вы также знаете, что выходите за пределы списка, проверка действительно не нужна.
@UlrichEckhardt MSVC хорош в этом, многие проверки включаются автоматически в отладочных сборках.
Помимо проверки итератора перед его продвижением, необходимо проверить его перед разыменованием. Если it является конечным итератором, то оценка *it и/или вызов advance(it, ...) дают неопределенное поведение.





Разыменование конечного итератора не определено. Конечный итератор указывает на один после последнего элемента в списке. Он не относится к элементу в списке, потому что после последнего элемента нет.
Дальнейшее продвижение конечного итератора также не определено.
Ваша программа имеет неопределенное поведение.
Неопределенное поведение означает, что все может случиться. Поведение кода во время выполнения без неопределенного поведения указано в стандарте языка (некоторые из них определены или не определены реализацией, но давайте не будем усложнять). С другой стороны, когда ваш код имеет неопределенное поведение, вы не можете гарантировать, каким будет результирующее поведение программы во время выполнения. Ваш результат может быть "Hello World".
Конечно, результат не "Hello World", и есть причина, по которой вы видите результат, который видите. Однако эти причины не связаны с тем, как работает C++. Чтобы понять, почему вы получаете такой вывод, вам нужно изучить сборку, сгенерированную компилятором. В конце концов, чтобы понять, почему компилятор сгенерировал эту сборку, вам нужно будет изучить детали реализации вашего компилятора. Однако, если вы хотите узнать о C++, то все это бесполезно, и все, что вам нужно знать, это то, что ваш код имеет неопределенное поведение.
..... и (за исключением особых случаев использования, таких как преднамеренное использование специфичных для компилятора расширений), что лучше всего писать код (выполнять все необходимые проверки), чтобы избежать неопределенного поведения.
Я тестировал программу, и она вылетела
Добро пожаловать в страну неопределенного поведения, где все, что происходит, правильно.