Eu estou supondo "porque é isso que o padrão diz" não vai funcionar, certo? :)
Luchian Grigore
39
@LuchianGrigore: Claro que não. Isso acabaria com o nosso respeito pelo (povo por trás) do padrão. Devemos esperar que haja uma razão para as escolhas feitas pelo padrão.
Kerrek SB
4
Em resumo, os computadores não contam como pessoas. Mas se você está curioso para saber por que as pessoas não contam como computadores, recomendo O Nada Que É: Uma História Natural de Zero para uma análise aprofundada do problema que os seres humanos descobriram que existe um número menos um do que um.
John McFarlane
8
Como existe apenas uma maneira de gerar a "última", geralmente não é barata, porque precisa ser real. Gerar "você caiu do fim do penhasco" é sempre barato, muitas representações possíveis servirão. (vazio *) "ahhhhhhh" vai dar certo.
21711
6
Olhei a data da pergunta e, por um segundo, pensei que você estivesse brincando.
Você quer que o tamanho do intervalo para ser um simples diferença final - começar ;
incluir o limite inferior é mais "natural" quando as seqüências degeneram para vazias e também porque a alternativa ( excluindo o limite inferior) exigiria a existência de um valor sentinela "um antes do início".
Você ainda precisa justificar por que começa a contar com zero, em vez de um, mas isso não fazia parte da sua pergunta.
A sabedoria por trás da convenção [começo, fim] compensa várias vezes quando você tem algum tipo de algoritmo que lida com várias chamadas aninhadas ou iteradas para construções baseadas em intervalo, que são encadeadas naturalmente. Por outro lado, o uso de um intervalo duplamente fechado resultaria em códigos isolados e extremamente desagradáveis e barulhentos. Por exemplo, considere uma partição [ n 0 , n 1 ) [ n 1 , n 2 ) [ n 2 , n 3 ). Outro exemplo é o loop de iteração padrão for (it = begin; it != end; ++it), que executa os end - begintempos. O código correspondente seria muito menos legível se as duas extremidades fossem inclusivas - e imagine como você lidaria com intervalos vazios.
Finalmente, também podemos argumentar bem por que a contagem deve começar em zero: com a convenção semi-aberta para os intervalos que acabamos de estabelecer, se você receber um intervalo de N elementos (digamos, para enumerar os membros de uma matriz), então 0 é o "começo" natural, para que você possa escrever o intervalo como [0, N ), sem deslocamentos ou correções difíceis.
Em poucas palavras: o fato de não vermos o número 1em todos os lugares nos algoritmos baseados em intervalo é uma conseqüência direta e a motivação da convenção [início, fim].
O loop C típico para iteração sobre uma matriz de tamanho N é "para (i = 0; i <N; i ++) a [i] = 0;". Agora, você não pode expressar isso diretamente com os iteradores - muitas pessoas perderam tempo tentando fazer <significativo. Mas é quase igualmente óbvio dizer "para (i = 0; i! = N; i ++) ..." Mapear 0 para começar e N para terminar é, portanto, conveniente.
Krazy Glew
3
@KrazyGlew: Eu não coloquei tipos no meu exemplo de loop deliberadamente. Se você pensa em begine endcomo ints com valores 0e N, respectivamente, ele se encaixa perfeitamente. Indiscutivelmente, é a !=condição mais natural que a tradicional <, mas nunca descobrimos isso até começarmos a pensar em coleções mais gerais.
Kerrek SB
4
@KerrekSB: Concordo que "nunca descobrimos que [! = É melhor] até que começamos a pensar em coleções mais gerais". O IMHO é uma das coisas pelas quais Stepanov merece crédito - falando como alguém que tentou escrever essas bibliotecas de modelos antes do STL. No entanto, discutirei sobre "! =" Ser mais natural - ou melhor, argumentarei que! = Provavelmente introduziu bugs, que <seriam detectados. Pense for (i = 0; i = 100;! I + = 3) ...
Krazy Glew
@KrazyGlew: Seu último ponto é algo fora de tópico, pois a sequência {0, 3, 6, ..., 99} não é da forma que o OP perguntou. Se você deseja que seja assim, escreva um ++modelo de iterador incrementável step_by<3>, que terá a semântica anunciada originalmente.
Kerrek SB
@KrazyGlew Mesmo que <em algum momento oculte um bug, é um bug de qualquer maneira . Se alguém usa !=quando deve usar <, então é um bug. A propósito, esse rei do erro é fácil de encontrar com testes ou afirmações de unidade.
Phil1970
80
Na verdade, muitas coisas relacionadas ao iterador de repente fazem muito mais sentido se você considerar que os iteradores não apontam para os elementos da sequência, mas no meio , com a desreferenciação acessando o próximo elemento à sua direita. Então, o iterador "one end end" faz repentinamente sentido imediato:
+---+---+---+---+| A | B | C | D |+---+---+---+---+^^||beginend
Obviamente beginaponta para o início da sequência e endaponta para o final da mesma sequência. A desreferenciação beginacessa o elemento A, e a desreferenciação endnão faz sentido porque não há nenhum elemento certo para ele. Além disso, adicionar um iterador ino meio fornece
+---+---+---+---+| A | B | C | D |+---+---+---+---+^^^|||begin i end
e você vê imediatamente que o intervalo de elementos de begina icontém os elementos Ae Benquanto o intervalo de elementos de ia endcontém os elementos Ce D. A desreferenciação ifornece o elemento certo, que é o primeiro elemento da segunda sequência.
Mesmo o "off-by-one" para iteradores reversos repentinamente se torna óbvio assim: a reversão dessa sequência fornece:
+---+---+---+---+| D | C | B | A |+---+---+---+---+^^^|||
rbegin ri rend
(end)(i)(begin)
Escrevi os iteradores não reversos (base) correspondentes entre parênteses abaixo. Você vê, o iterador reverso pertencente a i( ao qual eu nomeei ri) ainda aponta entre os elementos Be C. No entanto, devido à reversão da sequência, agora o elemento Bestá à direita.
Esta é IMHO a melhor resposta, embora eu ache que seria melhor ilustrar se os iteradores apontassem para números e os elementos estivessem entre os números (a sintaxe foo[i]) é uma abreviação para o item imediatamente após a posição i). Pensando nisso, gostaria de saber se seria útil para uma linguagem ter operadores separados para "item imediatamente após a posição i" e "item imediatamente antes da posição i", pois muitos algoritmos trabalham com pares de itens adjacentes e dizem " Os itens em ambos os lados da posição i "podem ser mais limpos que" Os itens nas posições iei + 1 ".
Supercat
@ supercat: Os números não deveriam indicar posições / índices do iterador, mas sim os próprios elementos. Vou substituir os números por letras para deixar isso mais claro. De fato, com os números fornecidos, begin[0](assumindo um iterador de acesso aleatório) acessaria o elemento 1, pois não há elemento 0na minha sequência de exemplo.
celtschk
Por que a palavra "begin" é usada em vez de "start"? Afinal, "begin" é um verbo.
user1741137
@ user1741137 Acho que "begin" deve ser a abreviação de "begin" (que agora faz sentido). "começo" é muito longo, "começo" soa como um bom ajuste. "start" estaria em conflito com o verbo "start" (por exemplo, quando você tiver que definir uma função start()em sua classe para iniciar um processo específico ou qualquer outra coisa, seria irritante se conflitar com um já existente).
Fareanor 24/06
74
Por que o Padrão define end()como um passado após o final, em vez de no final real?
Porque:
Evita manuseio especial para intervalos vazios. Para intervalos vazios, begin()é igual a
end()&
Isso simplifica o critério final para loops que iteram sobre os elementos: os loops simplesmente continuam enquanto end()não forem atingidos.
size()==end()-begin()// For iterators for whom subtraction is valid
e você não terá que fazer coisas estranhas como
// Never mind that this is INVALID for input iterators...bool empty(){returnbegin()==end()+1;}
e você acidentalmente não escreverá códigos errados como
bool empty(){returnbegin()==end()-1;}// a typo from the first version// of this post// (see, it really is confusing)bool empty(){returnend()-begin()==-1;}// Signed/unsigned mismatch// Plus the fact that subtracting is also invalid for many iterators
Além disso: o que find()retornaria se end()apontado para um elemento válido?
Você realmente quer outro membro do chamado invalid()que retorna um iterador inválido ?!
Dois iteradores já são dolorosos o suficiente ...
Esta é uma resposta altamente subestimada. Os exemplos são concisos e diretos ao ponto, e os "Também" não foram ditos por mais ninguém e são o tipo de coisas que parecem muito óbvias em retrospecto, mas me parecem revelações.
underscore_d
@underscore_d: Obrigado !! :)
user541686
Aliás, caso eu pareça um hipócrita por não ter votado, é porque eu já estava lá em julho de 2016!
underscore_d
@underscore_d: hahaha eu nem percebi, mas obrigado! :)
user541686
22
O idioma do iterador de intervalos semi-fechados [begin(), end())é originalmente baseado na aritmética do ponteiro para matrizes simples. Nesse modo de operação, você teria funções que receberam uma matriz e um tamanho.
void func(int* array,size_t size)
A conversão para intervalos semi-fechados [begin, end)é muito simples quando você tem essa informação:
int*begin;int*end= array + size;for(int* it =begin; it <end;++it){...}
Para trabalhar com faixas totalmente fechadas, é mais difícil:
int*begin;int*end= array + size -1;for(int* it =begin; it <=end;++it){...}
Como os ponteiros para matrizes são iteradores em C ++ (e a sintaxe foi projetada para permitir isso), é muito mais fácil chamar std::find(array, array + size, some_value)do que chamar std::find(array, array + size - 1, some_value).
Além disso, se você trabalha com faixas semi-fechadas, pode usar o !=operador para verificar a condição final, porque (se seus operadores estão definidos corretamente) <implica !=.
for(int* it =begin; it !=end;++ it){...}
No entanto, não há uma maneira fácil de fazer isso com faixas totalmente fechadas. Você está preso <=.
O único tipo de iterador que suporta <e >opera em C ++ são iteradores de acesso aleatório. Se você tivesse que escrever um <=operador para cada classe de iterador em C ++, seria necessário tornar todos os seus iteradores totalmente comparáveis e haveria menos opções para criar iteradores menos capazes (como os iteradores bidirecionais emstd::list ou os iteradores de entrada operam iostreams) se o C ++ usasse faixas totalmente fechadas.
Respostas:
O melhor argumento é facilmente o do próprio Dijkstra :
Você quer que o tamanho do intervalo para ser um simples diferença final - começar ;
incluir o limite inferior é mais "natural" quando as seqüências degeneram para vazias e também porque a alternativa ( excluindo o limite inferior) exigiria a existência de um valor sentinela "um antes do início".
Você ainda precisa justificar por que começa a contar com zero, em vez de um, mas isso não fazia parte da sua pergunta.
A sabedoria por trás da convenção [começo, fim] compensa várias vezes quando você tem algum tipo de algoritmo que lida com várias chamadas aninhadas ou iteradas para construções baseadas em intervalo, que são encadeadas naturalmente. Por outro lado, o uso de um intervalo duplamente fechado resultaria em códigos isolados e extremamente desagradáveis e barulhentos. Por exemplo, considere uma partição [ n 0 , n 1 ) [ n 1 , n 2 ) [ n 2 , n 3 ). Outro exemplo é o loop de iteração padrão
for (it = begin; it != end; ++it)
, que executa osend - begin
tempos. O código correspondente seria muito menos legível se as duas extremidades fossem inclusivas - e imagine como você lidaria com intervalos vazios.Finalmente, também podemos argumentar bem por que a contagem deve começar em zero: com a convenção semi-aberta para os intervalos que acabamos de estabelecer, se você receber um intervalo de N elementos (digamos, para enumerar os membros de uma matriz), então 0 é o "começo" natural, para que você possa escrever o intervalo como [0, N ), sem deslocamentos ou correções difíceis.
Em poucas palavras: o fato de não vermos o número
1
em todos os lugares nos algoritmos baseados em intervalo é uma conseqüência direta e a motivação da convenção [início, fim].fonte
begin
eend
comoint
s com valores0
eN
, respectivamente, ele se encaixa perfeitamente. Indiscutivelmente, é a!=
condição mais natural que a tradicional<
, mas nunca descobrimos isso até começarmos a pensar em coleções mais gerais.++
modelo de iterador incrementávelstep_by<3>
, que terá a semântica anunciada originalmente.!=
quando deve usar<
, então é um bug. A propósito, esse rei do erro é fácil de encontrar com testes ou afirmações de unidade.Na verdade, muitas coisas relacionadas ao iterador de repente fazem muito mais sentido se você considerar que os iteradores não apontam para os elementos da sequência, mas no meio , com a desreferenciação acessando o próximo elemento à sua direita. Então, o iterador "one end end" faz repentinamente sentido imediato:
Obviamente
begin
aponta para o início da sequência eend
aponta para o final da mesma sequência. A desreferenciaçãobegin
acessa o elementoA
, e a desreferenciaçãoend
não faz sentido porque não há nenhum elemento certo para ele. Além disso, adicionar um iteradori
no meio fornecee você vê imediatamente que o intervalo de elementos de
begin
ai
contém os elementosA
eB
enquanto o intervalo de elementos dei
aend
contém os elementosC
eD
. A desreferenciaçãoi
fornece o elemento certo, que é o primeiro elemento da segunda sequência.Mesmo o "off-by-one" para iteradores reversos repentinamente se torna óbvio assim: a reversão dessa sequência fornece:
Escrevi os iteradores não reversos (base) correspondentes entre parênteses abaixo. Você vê, o iterador reverso pertencente a
i
( ao qual eu nomeeiri
) ainda aponta entre os elementosB
eC
. No entanto, devido à reversão da sequência, agora o elementoB
está à direita.fonte
foo[i]
) é uma abreviação para o item imediatamente após a posiçãoi
). Pensando nisso, gostaria de saber se seria útil para uma linguagem ter operadores separados para "item imediatamente após a posição i" e "item imediatamente antes da posição i", pois muitos algoritmos trabalham com pares de itens adjacentes e dizem " Os itens em ambos os lados da posição i "podem ser mais limpos que" Os itens nas posições iei + 1 ".begin[0]
(assumindo um iterador de acesso aleatório) acessaria o elemento1
, pois não há elemento0
na minha sequência de exemplo.start()
em sua classe para iniciar um processo específico ou qualquer outra coisa, seria irritante se conflitar com um já existente).Por que o Padrão define
end()
como um passado após o final, em vez de no final real?Porque:
begin()
é igual aend()
&end()
não forem atingidos.fonte
Porque então
e você não terá que fazer coisas estranhas como
e você acidentalmente não escreverá códigos errados como
Além disso: o que
find()
retornaria seend()
apontado para um elemento válido?Você realmente quer outro membro do chamado
invalid()
que retorna um iterador inválido ?!Dois iteradores já são dolorosos o suficiente ...
Ah, e veja este post relacionado .
Além disso:
Se o
end
foi antes do último elemento, como você estariainsert()
no final verdadeiro ?!fonte
O idioma do iterador de intervalos semi-fechados
[begin(), end())
é originalmente baseado na aritmética do ponteiro para matrizes simples. Nesse modo de operação, você teria funções que receberam uma matriz e um tamanho.A conversão para intervalos semi-fechados
[begin, end)
é muito simples quando você tem essa informação:Para trabalhar com faixas totalmente fechadas, é mais difícil:
Como os ponteiros para matrizes são iteradores em C ++ (e a sintaxe foi projetada para permitir isso), é muito mais fácil chamar
std::find(array, array + size, some_value)
do que chamarstd::find(array, array + size - 1, some_value)
.Além disso, se você trabalha com faixas semi-fechadas, pode usar o
!=
operador para verificar a condição final, porque (se seus operadores estão definidos corretamente)<
implica!=
.No entanto, não há uma maneira fácil de fazer isso com faixas totalmente fechadas. Você está preso
<=
.O único tipo de iterador que suporta
<
e>
opera em C ++ são iteradores de acesso aleatório. Se você tivesse que escrever um<=
operador para cada classe de iterador em C ++, seria necessário tornar todos os seus iteradores totalmente comparáveis e haveria menos opções para criar iteradores menos capazes (como os iteradores bidirecionais emstd::list
ou os iteradores de entrada operamiostreams
) se o C ++ usasse faixas totalmente fechadas.fonte
Com o
end()
apontador no final, é fácil iterar uma coleção com um loop for:Ao
end()
apontar para o último elemento, um loop seria mais complexo:fonte
begin() == end()
,.!=
vez de<
(menos que) em condições de loop, portanto,end()
é conveniente apontar para uma posição inicial.fonte