Почему стандартные диапазоны итераторов [begin, end) вместо [begin, end]?
почему стандарт определяет end()
как один после конца, а не в фактическом конце?
7 ответов
лучший аргумент легко это сделал Дейкстра:
вы хотите, чтобы размер диапазона был простой разницей конец - начать;
включение нижней границы более "естественно", когда последовательности вырождаются в пустые, а также потому, что альтернатива (кроме нижняя граница) потребует существования" одного перед началом " часового значение.
вам все равно нужно оправдать, почему вы начинаете считать с нуля, а не с единицы, но это не было частью вашего вопроса.
мудрость за соглашением [begin, end) окупается снова и снова, когда у вас есть какой-либо алгоритм, который имеет дело с несколькими вложенными или итерационными вызовами к построениям на основе диапазона, которые естественно цепляются. Напротив, использование двойного закрытого диапазона повлечет за собой отключение и чрезвычайно неприятный и шумный код. Для пример раздела [n0, n1)[n1, n2)[n2,n3). Другим примером является стандартный цикл итерации for (it = begin; it != end; ++it)
, который работает end - begin
раза. Соответствующий код был бы намного менее читаемым, если бы оба конца были включены-и представьте, как вы обрабатываете пустой диапазоны.
наконец, мы также можем сделать хороший аргумент, Почему подсчет должен начинаться с нуля: с полуоткрытым соглашением для диапазонов, которые мы только что установили, если вам задан диапазон N элементы (скажем, для перечисления членов массива), то 0 является естественным "началом", так что вы можете записать диапазон как [0,N), без каких-либо неудобных смещения или корректировки.
в двух словах: тот факт, что мы не видим число 1
везде в алгоритмах на основе диапазона является прямым следствием и мотивацией соглашения [begin, end).
почему стандарт определяет end()
как один после конца, а не в фактическом конце?
потому что:
- он избегает особенной регуляции для пустых рядов. Для пустых диапазонов,
begin()
равнаend()
& - это делает конечный критерий простым для циклов, которые повторяют элементы: циклы просто
продолжайте до тех пор, пока
end()
не достигнуто.
на самом деле, много итераторов, связанных с вещами, внезапно имеет гораздо больше смысла, если вы считаете, что итераторы не указывают at элементы последовательности, но между, С разыменованием доступа к следующему элементу справа от него. Тогда итератор" one past end " внезапно имеет непосредственный смысл:
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^
| |
begin end
очевидно begin
указывает на начало последовательности, а end
указывает на конец той же последовательности. Разыменование begin
доступ к элементу A
, и разыменование end
не имеет смысла, потому что в нем нет элемента. Кроме того, добавление итератора i
в середине дает
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^ ^
| | |
begin i end
и вы сразу видите, что диапазон элементов от begin
to i
содержит элементы A
и B
в то время как диапазон элементов из i
to end
содержит элементы C
и D
. Разыменование i
дает право элемента, то есть первый элемент второй последовательности.
даже "off-by-one" для обратных итераторов внезапно становится очевидным таким образом: обращение этой последовательности дает:
+---+---+---+---+
| D | C | B | A |
+---+---+---+---+
^ ^ ^
| | |
rbegin ri rend
(end) (i) (begin)
я написал соответствующие нереверсивные (базовые) итераторы в скобках ниже. Вы видите, обратный итератор, принадлежащий i
(который я назвал ri
) еще пункты между элементами B
и C
. Однако из-за реверса последовательности теперь элемент B
is справа от него.
потому что тогда
size() == end() - begin() // For iterators for whom subtraction is valid
и вам не придется делать неудобно такие вещи, как
// Never mind that this is INVALID for input iterators...
bool empty() { return begin() == end() + 1; }
и вы не будете случайно писать ошибочный код как
bool empty() { return begin() == end() - 1; } // a typo from the first version
// of this post
// (see, it really is confusing)
bool empty() { return end() - begin() == -1; } // Signed/unsigned mismatch
// Plus the fact that subtracting is also invalid for many iterators
также: что бы find()
вернуться если end()
указал на допустимый элемент?
Вы действительно хочу другое член invalid()
который возвращает неверный итератор?!
Два итератора уже достаточно больно...
О, а посмотреть этой похожие статьи.
также:
если end
был перед последним элементом, как бы вы insert()
в настоящий конец?!
идиома итератора полузакрытых диапазонов [begin(), end())
первоначально основан на арифметике указателя для простых массивов. В этом режиме работы у вас будут функции, которые были переданы массиву и размеру.
void func(int* array, size_t size)
преобразование в полузакрытые диапазоны [begin, end)
очень удобно, когда у вас есть эта информация:
int* begin;
int* end = array + size;
for (int* it = begin; it < end; ++it) { ... }
для работы с полностью закрытыми диапазонами сложнее:
int* begin;
int* end = array + size - 1;
for (int* it = begin; it <= end; ++it) { ... }
поскольку указатели на массивы являются итераторами в C++ (и синтаксисе был разработан, чтобы позволить это), гораздо проще позвонить std::find(array, array + size, some_value)
чем это назвать std::find(array, array + size - 1, some_value)
.
кроме того, если вы работаете с полузакрытыми диапазонами, вы можете использовать !=
оператор для проверки конечного условия, потому что (если ваши операторы определены правильно)<
подразумевает !=
.
for (int* it = begin; it != end; ++ it) { ... }
однако нет простого способа сделать это с полностью закрытыми диапазонами. Вы застряли с <=
.
единственный вид итератора, который поддерживает <
и >
операции в C++ являются итераторами произвольного доступа. Если бы вам пришлось написать <=
оператор для каждого класса итераторов в C++ вам придется сделать все ваши итераторы полностью сопоставимыми, и у вас будет меньше вариантов для создания менее способных итераторов (таких как двунаправленные итераторы на std::list
, или входные итераторы, которые работают на iostreams
) если C++ использовал полностью закрытые диапазоны.
С end()
указывая один за конец, легко повторить коллекцию с циклом for:
for (iterator it = collection.begin(); it != collection.end(); it++)
{
DoStuff(*it);
}
С end()
указывает на последний элемент, цикл будет более сложным:
iterator it = collection.begin();
while (!collection.empty())
{
DoStuff(*it);
if (it == collection.end())
break;
it++;
}
- если контейнер пуст, то
begin() == end()
. - программисты C++, как правило, используют
!=
вместо<
(меньше чем) в условиях цикла, поэтому имеяend()
указывать на положение одно-конец удобен.