Почему Стандарт определяется end()как конец за концом, а не как фактический конец?
Почему Стандарт определяется end()как конец за концом, а не как фактический конец?
Ответы:
Лучшим аргументом легко является тот, который выдвинул сам Дейкстра :
Вы хотите, чтобы размер диапазона , чтобы быть простой разницей конца - начать ;
включение нижней границы является более «естественным», когда последовательности вырождаются в пустые, а также потому, что альтернатива ( исключая нижнюю границу) потребует существования значения «один перед началом» дозорного.
Вам все еще нужно объяснить, почему вы начинаете считать с нуля, а не с одного, но это не было частью вашего вопроса.
Мудрость, лежащая в основе соглашения [начало, конец), окупается снова и снова, когда у вас есть какой-либо алгоритм, который имеет дело с несколькими вложенными или повторяющимися вызовами конструкций на основе диапазона, которые естественным образом объединяются. Напротив, использование дважды закрытого диапазона повлечет за собой посторонний и крайне неприятный и шумный код. Например, рассмотрим раздел [ n 0 , n 1 ) [ n 1 , n 2 ) [ n 2 , n 3 ). Другим примером является стандартный цикл итерации for (it = begin; it != end; ++it), который запускается end - beginраз. Соответствующий код был бы намного менее читабельным, если бы оба конца были включительно - и представьте, как вы будете обрабатывать пустые диапазоны.
Наконец, мы также можем привести хороший аргумент, почему подсчет должен начинаться с нуля: с полуоткрытым соглашением для только что установленных диапазонов, если вам дан диапазон из N элементов (скажем, для перечисления членов массива), тогда 0 является естественным «началом», так что вы можете записать диапазон как [0, N ), без каких-либо неловких смещений или исправлений.
В двух словах: тот факт, что мы не видим число 1повсюду в алгоритмах на основе диапазонов, является прямым следствием и [мотивацией] соглашения [начало, конец).
beginи endкак ints со значениями 0и N, соответственно, он идеально подходит. Возможно, это !=условие более естественное, чем традиционное <, но мы никогда не открывали его, пока не начали думать о более общих коллекциях.
++-крименабельный шаблон итератора step_by<3>, который будет иметь первоначально объявленную семантику.
!=когда он должен использовать <, то это ошибка. Кстати, этот король ошибки легко найти с помощью модульного тестирования или утверждений.
На самом деле, многие вещи, связанные с итераторами, внезапно приобретают гораздо больший смысл, если учесть, что итераторы указывают не на элементы последовательности, а между ними , с разыменованием доступа к следующему элементу прямо к нему. Тогда итератор «один конец» внезапно приобретает смысл:
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^
| |
begin end
Очевидно, beginуказывает на начало последовательности и endуказывает на конец той же последовательности. Разыменование beginобращается к элементу A, и разыменование endне имеет смысла, потому что нет никакого элемента к нему. Кроме того, добавление итератора iв середине дает
+---+---+---+---+
| A | B | C | D |
+---+---+---+---+
^ ^ ^
| | |
begin i end
и вы сразу видите, что диапазон элементов от beginдо iсодержит элементы, Aа Bдиапазон элементов от iдо endсодержит элементы Cи D. Разыменование iдает право на элемент, то есть первый элемент второй последовательности.
Даже «отключенный» для обратных итераторов внезапно становится очевидным, если поменять эту последовательность:
+---+---+---+---+
| D | C | B | A |
+---+---+---+---+
^ ^ ^
| | |
rbegin ri rend
(end) (i) (begin)
Я написал соответствующие не обратные (базовые) итераторы в скобках ниже. Видите ли, обратный итератор, принадлежащий i(который я назвал ri), все еще указывает между элементами Bи C. Однако из-за изменения последовательности теперь элемент Bнаходится справа от него.
foo[i]) является сокращением для элемента сразу после позиции i). Размышляя об этом, я задаюсь вопросом: может быть полезно для языка иметь отдельные операторы для «элемента сразу после позиции i» и «элемента непосредственно перед позицией i», поскольку многие алгоритмы работают с парами смежных элементов и говорят « Предметы по обе стороны от позиции i "могут быть чище, чем" Предметы в позициях i и i + 1 ".
begin[0](при условии, что итератор произвольного доступа) будет обращаться к элементу 1, поскольку 0в моем примере последовательности нет элемента .
start()в вашем классе для запуска определенного процесса или чего-то еще, это будет раздражать, если оно конфликтует с уже существующим).
Почему Стандарт определяется end()как конец за концом, а не как фактический конец?
Так как:
begin()равно
end()& end()пока они не достигнуты.Потому что тогда
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().!=вместо <(меньше, чем) в условиях цикла, поэтому end()удобно указывать на позицию в начале.