Escolhendo o tipo de variáveis ​​de índice

11

Usamos o tipo Inteiro para representar variáveis ​​de índice na maioria das vezes. Mas, em algumas situações, somos forçados a escolher

std::vector<int> vec;
....

for(int i = 0; i < vec.size(); ++i)
....

Isso fará com que o compilador aumente o aviso de que o uso misto de variáveis ​​assinadas / não assinadas. se eu fizer a variável index como for( size_t i = 0; i < vec.size(); i++ ), (ou uma unsigned int), ela resolverá os problemas.

Quando se torna mais específico o uso de tipos de janelas, a maioria das APIs do Windows trata do DWORD (que é digitado como não assinado por muito tempo).

Portanto, quando eu usar iterações semelhantes, causarei novamente o mesmo aviso. Agora, se eu reescrevê-lo como

DWORD dwCount;
....

for(DWORD i = 0; i < dwCount; ++i)
....

Acho isso um pouco estranho. Pode ser o problema com as percepções.

Concordo que devemos usar o mesmo tipo de variável de índice para evitar que problemas de intervalo possam ocorrer com as variáveis ​​de índice. Por exemplo, se estamos usando

_int64 i64Count; // 
....

for(_int64 i = 0; i < i64Count; ++i)
....

Mas no caso de DWORD, ou números inteiros não assinados, há algum problema em reescrevê-lo como

for(int i = 0; (size_t)i < vec.size(); ++i)

Como a maioria das pessoas está trabalhando com problemas semelhantes?

sarat
fonte
4
Por que você usaria um número inteiro assinado para representar o índice? É como usar o vetor de números inteiros para armazenar uma string.
Let_Me_Be 30/08
3
@Let_Me_Be: Porque facilita as condições de contorno. Por exemplo, em uma contagem regressiva a zero, um teste menor que antes de executar o corpo do loop não pode funcionar com um valor não assinado. Da mesma forma, uma contagem até o máximo não funciona. Obviamente, nesse caso, um número inteiro assinado também não funcionará (porque não pode representar um valor tão grande).
Yttrill
"Isso fará com que o compilador aumente o aviso de que o uso misto de variáveis ​​assinadas / não assinadas." Esse é apenas um dos dois problemas com os quais você terá que lidar. Em muitos casos, std::size_té uma classificação mais alta que a int (ou até longa). Se o tamanho do vetor exceder std::numeric_limits<int>::max(), você se arrependerá de ter usado int.
Adrian McCarthy

Respostas:

11

O vetor possui um typedef que informa o tipo correto a ser usado: -

for(std::vector<int>::size_type i = 0; i < thing.size(); ++i)
{
}

É quase sempre definido como size_t, mas você não pode confiar nisso

JohnB
fonte
8
Não é realmente uma melhoria na legibilidade, IMHO.
Doc Brown
Não, mas porque é a única maneira de saber qual é o tipo correto a ser usado para um índice para o vetor, que realmente não importa ...
JohnB
4
Estes dias em c ++ 11 apenas uso auto
JohnB
6
@JohnB Você quer dizer auto i = 0? Isso não ajuda em nada, itornar-se um int.
zenith
1
A legibilidade pode ser melhorada, com using index_t = std::vector<int>::size_type;.
Toby Speight
4
std::vector<int> vec;

for(int i = 0; i < vec.size(); ++i)

Use um iterador para isso, não um forloop.

Para os outros, desde que o tipo de variável seja do mesmo tamanho, static_castdeve funcionar bem (isto é, DWORDpara int16_t)

Demian Brecht
fonte
2
for (std::vector<int>::iterator i = vec.begin(); i != vec.end(); ++i)é uma dor de escrever. Ter for (auto i = vec.begin();...é muito mais legível. Claro, foreachtambém está em C ++ 11.
David Thornley
3

O caso que você descreveu também é uma das coisas que eu não gosto em C ++. Mas eu aprendi a conviver com isso, usando

for( size_t i = 0; i < vec.size(); i++ )

ou

for( int i = 0; i < (int)vec.size(); i++ )

(é claro, o último apenas quando não há risco de obter algum transbordamento int).

Doc Brown
fonte
3

O motivo pelo qual o alerta sobre a comparação entre assinado e não assinado é porque o valor assinado provavelmente será convertido em não assinado, o que pode não ser o que você espera.

No seu exemplo (comparando inta size_t), intserá implicitamente convertido em size_t(a menos que de intalguma forma tenha um intervalo maior que size_t). Portanto, se o valor intfor negativo, provavelmente será maior que o valor com o qual você o está comparando devido à envolvente. Isso não será um problema se seu índice nunca for negativo, mas você ainda receberá esse aviso.

Em vez disso, use um tipo não assinado (como unsigned int, size_tou, como John B recomenda , std::vector<int>::size_type) para a sua variável de índice:

for(unsigned int i = 0; i < vec.size(); i++)

Tenha cuidado ao fazer uma contagem regressiva, no entanto:

for(unsigned int i = vec.size()-1; i >= 0; i--) // don't do this!

O acima não funcionará porque i >= 0sempre é verdadeiro quando inão está assinado. Em vez disso, use o " operador de seta " para loops que fazem contagem regressiva:

for (unsigned int i = vec.size(); i-- > 0; )
    vec[i] = ...;

Como outras respostas apontam, você normalmente deseja usar um iterador para percorrer a vector. Aqui está a sintaxe do C ++ 11:

for (auto i = vec.begin(); i != vec.end(); ++i)
Joey Adams
fonte
1
Isso ainda corre o risco de que um unsigned intnão seja grande o suficiente para manter o tamanho.
Adrian McCarthy
2

Uma nova opção para o C ++ 11, você pode fazer coisas como as seguintes

for(decltype(vec.size()) i = 0; i < vec.size(); ++i) {...}

e

for(decltype(dWord) i = 0; i < dWord; ++i) {...}

Embora ele repita um pouco mais do que o loop for básico, não é tão longo quanto as formas pré-11 de especificar valores, e usar esse padrão consistentemente funcionará para a maioria dos termos possíveis, se não todos. deseja comparar, o que o torna ótimo para refatoração de código. Até funciona para casos simples como este:

int x = 3; int final = 32; for(decltype(final) i = x; i < final; ++i)

Além disso, embora você deva usar autosempre que estiver definindo ialgum valor inteligente (como vec.begin()), decltypefunciona quando estiver definindo uma constante como zero, onde auto resolveria isso apenas intporque 0 é um literal inteiro simples.

Para ser sincero, eu gostaria de ver um mecanismo de compilador para estender a autodeterminação de tipo para incrementadores de loop para observar o valor que está sendo comparado.

matias
fonte
1

Eu uso um elenco para int, como em for (int i = 0; i < (int)v.size(); ++i). Sim, é feio. Eu culpo o design estúpido da biblioteca padrão, onde eles decidiram usar números inteiros não assinados para representar tamanhos. (Para .. o quê? Estender o intervalo em um bit?)

zvrba
fonte
1
Em que situação seria significativo um tamanho de coleção negativo? Usar números inteiros não assinados para vários tamanhos de coleção parece ser a escolha sensata para mim. Última vez que verifiquei, tendo o comprimento de uma corda raramente retornou um resultado negativo ou ...
um CVn
1
Em que situação seria significativo para um teste simples, como if(v.size()-1 > 0) { ... }retornar true para um contêiner vazio? O problema é que os tamanhos também são frequentemente usados ​​em aritmética, esp. com contêineres baseados em índices, que estão solicitando problemas, pois não estão assinados. Basicamente, o uso de tipos não assinados para qualquer coisa além de 1) manipulações bit a bit ou 2) aritmética modular está causando problemas.
Zvrba
2
bom ponto. Embora eu realmente não veja o ponto do seu exemplo em particular (provavelmente escreveria, if(v.size() > 1) { ... }já que isso torna a intenção mais clara e, como um bônus adicional, a questão de assinado / não assinado se torna nula), vejo como, em alguns casos específicos a assinatura pode ser útil. Eu estou corrigido.
um CVn
1
@ Michael: Eu concordo que foi um exemplo artificial. Ainda assim, costumo escrever algoritmos com loops aninhados: for (i = 0; i <v.size () - 1; ++ i) for (j = i + 1; j <v.size (); ++ j) .. se v estiver vazio, o loop externo será executado (size_t) -1 vezes. Então, eu tenho que verificar v.empty () antes do loop ou converter v.size () para um tipo assinado, os quais eu pessoalmente acho que são soluções feias. Eu escolho um elenco porque é menos LOC, não se () s => menos possibilidades de erro. (Além disso, no segundo complemento, conversão oveflow retorna um número negativo, então o laço não é executado em todos os.)
zvrba
Estender o intervalo em 1 bit foi (e continua sendo) muito útil em sistemas de 16 bits.
Adrian McCarthy