Iteração sobre std :: vector: variável de índice não assinada vs assinada

470

Qual é a maneira correta de iterar sobre um vetor em C ++?

Considere estes dois fragmentos de código, este funciona bem:

for (unsigned i=0; i < polygon.size(); i++) {
    sum += polygon[i];
}

e este:

for (int i=0; i < polygon.size(); i++) {
    sum += polygon[i];
}

que gera warning: comparison between signed and unsigned integer expressions.

Eu sou novo no mundo do C ++, então a unsignedvariável me parece um pouco assustadora e sei que as unsignedvariáveis ​​podem ser perigosas se não forem usadas corretamente, então - isso está correto?

Yuval Adam
fonte
10
O não assinado está correto porque polygon.size () é do tipo não assinado. Não assinado significa positivo sempre ou 0. Isso é tudo o que significa. Portanto, se o uso da variável for sempre apenas para contagens, a assinatura não assinada é a escolha certa.
11133 Adam Bruss
3
@AdamBruss .size()não é do tipo unsignedaka unsigned int. É do tipo std::size_t.
underscore_d
1
@underscore_d size_t é um alias para não assinado.
Adam Bruss
2
@AdamBruss No. std::size_té um typedef definido por _implementation. Veja o padrão. std::size_tpode ser equivalente à unsignedsua implementação atual, mas isso não é relevante. Fingir que é pode resultar em código não portátil e comportamento indefinido.
underscore_d
2
@LF ... claro, o que provavelmente está std::size_tna prática. Você acha que já cobrimos tudo nesse fluxo de comentários durante seis anos?
Underscore_d

Respostas:

817

Para iterar para trás, veja esta resposta .

Iterar para frente é quase idêntico. Apenas altere o decremento de iteradores / swap por incremento. Você deve preferir iteradores. Algumas pessoas dizem para você usar std::size_tcomo o tipo de variável de índice. No entanto, isso não é portátil. Sempre use o size_typetypedef do contêiner (Embora você possa se livrar apenas de uma conversão no caso de iteração direta, ele pode realmente dar errado todo o caminho no caso de iteração reversa ao usar std::size_t, caso std::size_tseja maior do que o typedef de size_type) :


Usando std :: vector

Usando iteradores

for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    /* std::cout << *it; ... */
}

Importante é sempre usar o formulário de incremento de prefixo para iteradores cujas definições você não conhece. Isso garantirá que seu código seja executado o mais genérico possível.

Usando o intervalo C ++ 11

for(auto const& value: a) {
     /* std::cout << value; ... */

Usando índices

for(std::vector<int>::size_type i = 0; i != v.size(); i++) {
    /* std::cout << v[i]; ... */
}

Usando matrizes

Usando iteradores

for(element_type* it = a; it != (a + (sizeof a / sizeof *a)); it++) {
    /* std::cout << *it; ... */
}

Usando o intervalo C ++ 11

for(auto const& value: a) {
     /* std::cout << value; ... */

Usando índices

for(std::size_t i = 0; i != (sizeof a / sizeof *a); i++) {
    /* std::cout << a[i]; ... */
}

Leia na resposta de iteração reversa para qual problema a sizeofabordagem pode render.

Johannes Schaub - litb
fonte
tipo de tamanho dos ponteiros: o uso de difference_type pode ser mais portátil. tente iterator_traits <element_type *> :: difference_type. este é um bocado de uma declaração, mas é mais portátil ...
wilhelmtell
wilhelmtell, para o que devo usar o different_type? sizeof é definido para retornar size_t :) eu não te entendo. se eu subtraísse os ponteiros um do outro, o different_type seria a escolha certa.
Johannes Schaub - litb 4/01/09
A iteração sobre matrizes usando a técnica mencionada nesta postagem não funcionará se a iteração estiver sendo executada em uma função em uma matriz passada para essa função. Porque sizeof array retornará apenas o ponteiro sizeof.
Systemfault
1
@ Nils eu concordo que usar contadores de loop não assinados é uma má idéia. mas como a biblioteca padrão usa tipos inteiros não assinados para índice e tamanho, prefiro tipos de índice não assinados para a biblioteca padrão. outras bibliotecas, conseqüentemente, usam apenas tipos assinados, como o Qt lib.
Johannes Schaub - litb 11/03/12
32
Atualização para C ++ 11: intervalo baseado em loop. for (auto p : polygon){sum += p;}
Siyuan Ren 22/08/13
170

Quatro anos se passaram, o Google me deu essa resposta. Com o C ++ 11 padrão (também conhecido como C ++ 0x ), há realmente uma nova maneira agradável de fazer isso (ao preço de quebrar a compatibilidade com versões anteriores): a nova autopalavra-chave. Isso evita a necessidade de especificar explicitamente o tipo de iterador a ser usado (repetindo o tipo de vetor novamente), quando é óbvio (para o compilador), qual tipo usar. Com vsendo o seu vector, você pode fazer algo como isto:

for ( auto i = v.begin(); i != v.end(); i++ ) {
    std::cout << *i << std::endl;
}

O C ++ 11 vai ainda mais longe e fornece uma sintaxe especial para iterar sobre coleções como vetores. Remove a necessidade de escrever coisas que são sempre as mesmas:

for ( auto &i : v ) {
    std::cout << i << std::endl;
}

Para vê-lo em um programa de trabalho, crie um arquivo auto.cpp:

#include <vector>
#include <iostream>

int main(void) {
    std::vector<int> v = std::vector<int>();
    v.push_back(17);
    v.push_back(12);
    v.push_back(23);
    v.push_back(42);
    for ( auto &i : v ) {
        std::cout << i << std::endl;
    }
    return 0;
}

Ao escrever isso, quando você o compila com o g ++ , normalmente é necessário configurá-lo para funcionar com o novo padrão, dando um sinalizador extra:

g++ -std=c++0x -o auto auto.cpp

Agora você pode executar o exemplo:

$ ./auto
17
12
23
42

Observe que as instruções de compilação e execução são específicas para o compilador gnu c ++ no Linux , o programa deve ser independente da plataforma (e compilador).

Kratenko
fonte
7
O C ++ 11 fornece a vocêfor (auto& val: vec)
Flexo
@ Flexo Obrigado, eu não sei como eu poderia esquecer isso. Não estou fazendo C ++ suficiente, eu acho. Não podia acreditar que havia algo tão prático (na verdade, pensava que era a sintaxe do JavaScript). Mudei a resposta para incluir isso.
kratenko
Sua resposta é muito boa. É desagradável que a versão padrão do g ++ em vários devkits do SO esteja na versão 4.3, o que a faz não funcionar.
Ratata Tata
Você precisa inicializar o vetor com std::vector<int> v = std::vector<int>();, ou poderia simplesmente usá-lo std::vector<int> v;?
Bill Cheatham
@ BillCheatham Bem - eu apenas tentei sem a inicialização, e funcionou, então parece que funciona sem.
kratenko
44

No caso específico do seu exemplo, eu usaria os algoritmos STL para fazer isso.

#include <numeric> 

sum = std::accumulate( polygon.begin(), polygon.end(), 0 );

Para um caso mais geral, mas ainda bastante simples, eu diria:

#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

using namespace boost::lambda;
std::for_each( polygon.begin(), polygon.end(), sum += _1 );
paxos1977
fonte
38

Em relação à resposta de Johannes Schaub:

for(std::vector<T*>::iterator it = v.begin(); it != v.end(); ++it) { 
...
}

Isso pode funcionar com alguns compiladores, mas não com o gcc. O problema aqui é a questão se std :: vector :: iterator é um tipo, uma variável (membro) ou uma função (método). O seguinte erro ocorre com o gcc:

In member function void MyClass<T>::myMethod()’:
error: expected `;' before ‘it’
error: ‘it’ was not declared in this scope
In member function ‘void MyClass<T>::sort() [with T = MyClass]’:
instantiated from ‘void MyClass<T>::run() [with T = MyClass]’
instantiated from here
dependent-name ‘std::vector<T*,std::allocator<T*> >::iterator’ is parsed as a non-type, but instantiation yields a type
note: say ‘typename std::vector<T*,std::allocator<T*> >::iterator’ if a type is meant

A solução está usando a palavra-chave 'typename', conforme informado:

typename std::vector<T*>::iterator it = v.begin();
for( ; it != v.end(); ++it) {
...
Polat Tuzla
fonte
2
Você deve elaborar que isso se aplica apenas quando Té um argumento de modelo e, portanto, a expressão std::vector<T*>::iteratoré um nome dependente. Para que um nome dependente seja analisado como um tipo, ele precisa ser anexado pela typenamepalavra - chave, conforme indica o diagnóstico.
Reintegrar Monica
17

Uma chamada para vector<T>::size()retorna um valor do tipo std::vector<T>::size_type, não int, int não assinado ou não.

Também geralmente a iteração sobre um contêiner em C ++ é feita usando iteradores , como este.

std::vector<T>::iterator i = polygon.begin();
std::vector<T>::iterator end = polygon.end();

for(; i != end; i++){
    sum += *i;
}

Onde T é o tipo de dados que você armazena no vetor.

Ou utilizando os diferentes algoritmos de iteração ( std::transform, std::copy, std::fill, std::for_eachet cetera).

Jasper Bekkers
fonte
Os iteradores geralmente são uma boa idéia, embora eu duvide que seja necessário armazenar "end" em uma variável separada e tudo isso pode ser feito dentro de uma instrução for (;;).
Saulius Žemaitaitis
1
Eu sei que begin () e end () são amortizados em tempo constante, mas geralmente acho que isso é mais legível do que colocar tudo em uma única linha.
Jasper Bekkers
3
Você pode dividir o for em linhas separadas para melhorar a legibilidade. Declarar iteradores fora do loop significa que você precisa de um nome de iterador diferente para cada loop em contêineres de tipos diferentes.
Jay Conrod
Estou ciente de todas as diferenças, e basicamente se resume a preferência pessoal; geralmente é assim que acabo fazendo as coisas.
Jasper Bekkers
2
@pihentagy Acho que seria configurá-lo na primeira seção do loop for. por exemplo. Para (auto i = polygon.begin (), end = polygon.end (); i! = end; i ++)
Jasper Bekkers
11

Use size_t:

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

Citando a Wikipedia :

Os arquivos de cabeçalho stdlib.he stddef.h definem um tipo de dados chamado size_tque é usado para representar o tamanho de um objeto. As funções de biblioteca que aceitam tamanhos esperam que sejam do tipo size_te o operador sizeof avalia size_t.

O tipo real de size_tdepende da plataforma; um erro comum é assumir que size_té o mesmo que int não assinado, o que pode levar a erros de programação, principalmente quando as arquiteturas de 64 bits se tornam mais prevalentes.

Igor Oks
fonte
size_t OK para vetor, pois ele deve armazenar todos os objetos em uma matriz (também um objeto), mas uma lista std :: pode conter mais do que elementos size_t!
MSalters
1
size_t normalmente é suficiente para enumerar todos os bytes no espaço de endereço de um processo. Embora eu possa ver como isso pode não ser o caso em algumas arquiteturas exóticas, prefiro não me preocupar com isso.
Recomenda-se #include <cstddef>ao AFAIK, em vez de <stddef.h>, ou pior, a totalidade [c]stdlib, e usar std::size_tem vez da versão não qualificada - e o mesmo para qualquer outra situação em que você tenha uma escolha entre <cheader>e <header.h>.
underscore_d
7

Um pouco de história:

Para representar se um número é negativo ou não, use um bit de 'sinal'. inté um tipo de dados assinado, o que significa que pode conter valores positivos e negativos (cerca de -2 bilhões a 2 bilhões). Unsignedsó pode armazenar números positivos (e, como não desperdiça um pouco de metadados, pode armazenar mais: 0 a cerca de 4 bilhões).

std::vector::size()retorna um unsigned, pois como um vetor pode ter comprimento negativo?

O aviso está dizendo que o operando direito da sua declaração de desigualdade pode conter mais dados do que o esquerdo.

Essencialmente, se você tiver um vetor com mais de 2 bilhões de entradas e usar um número inteiro para indexar, terá problemas de estouro (o int retornará para 2 bilhões negativos).

ecoffey
fonte
6

Eu costumo usar BOOST_FOREACH:

#include <boost/foreach.hpp>

BOOST_FOREACH( vector_type::value_type& value, v ) {
    // do something with 'value'
}

Funciona em contêineres STL, matrizes, cadeias de estilo C, etc.

Martin Cote
fonte
2
Boa resposta para alguma outra questão (como devo iterar um vetor?), Mas não completamente em tudo o que o OP estava pedindo (qual é o significado do aviso sobre uma variável unsigned?)
abelenky
3
Bem, ele perguntou qual era a maneira correta de iterar sobre um vetor. Parece relevante o suficiente. O aviso é exatamente por que ele não está feliz com sua solução atual.
jalf
5

Para ser concluída, a sintaxe do C ++ 11 permite apenas uma outra versão para os iteradores ( ref ):

for(auto it=std::begin(polygon); it!=std::end(polygon); ++it) {
  // do something with *it
}

O que também é confortável para iteração reversa

for(auto it=std::end(polygon)-1; it!=std::begin(polygon)-1; --it) {
  // do something with *it
}
Jan Turoň
fonte
5

Em C ++ 11

Eu usaria algoritmos gerais for_eachpara evitar procurar o tipo certo de iterador e expressão lambda para evitar funções / objetos extra nomeados.

O pequeno exemplo "bonito" para o seu caso particular (assumindo que o polígono é um vetor de números inteiros):

for_each(polygon.begin(), polygon.end(), [&sum](int i){ sum += i; });

testado em: http://ideone.com/i6Ethd

Não se esqueça de incluir: algoritmo e, é claro, vetor :)

A Microsoft também tem um bom exemplo disso:
fonte: http://msdn.microsoft.com/en-us/library/dd293608.aspx

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

int main() 
{
   // Create a vector object that contains 10 elements.
   vector<int> v;
   for (int i = 1; i < 10; ++i) {
      v.push_back(i);
   }

   // Count the number of even numbers in the vector by 
   // using the for_each function and a lambda.
   int evenCount = 0;
   for_each(v.begin(), v.end(), [&evenCount] (int n) {
      cout << n;
      if (n % 2 == 0) {
         cout << " is even " << endl;
         ++evenCount;
      } else {
         cout << " is odd " << endl;
      }
   });

   // Print the count of even numbers to the console.
   cout << "There are " << evenCount 
        << " even numbers in the vector." << endl;
}
jave.web
fonte
4
for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
    sum += *it; 
Mehrdad Afshari
fonte
2
Para vetor, isso é bom, mas, genericamente, é melhor usá-lo em vez de em ++, caso o próprio iterador não seja trivial.
Steve Jessop
Pessoalmente, estou acostumado a usar ++ i, mas acho que a maioria das pessoas prefere o estilo i ++ (o snippet de código VS padrão para "for" é i ++). Apenas um pensamento
Mehrdad Afshari
@MehrdadAfshari Quem se importa com o que "a maioria das pessoas" faz? "a maioria das pessoas" está errada sobre muitas coisas. Pós-inc / decremento onde o pré-valor nunca é usado é errado e ineficiente, pelo menos em teoria - independentemente da frequência com que é usado cegamente em códigos de exemplo abaixo do par em todos os lugares. Você não deve incentivar más práticas apenas para tornar as coisas mais familiares para as pessoas que ainda não conhecem melhor.
underscore_d
2

O primeiro é o tipo correto, e correto em algum sentido estrito. (Se você pensa que é, o tamanho nunca pode ser menor que zero.) Esse aviso me parece um dos bons candidatos a ser ignorado.

Charlie Martin
fonte
2
Eu acho que é um candidato terrível a ser ignorado - é fácil de corrigir e, de vez em quando, ocorrem erros genuínos devido a erros na comparação inadequada de valores assinados / não assinados. Por exemplo, neste caso, se o tamanho for maior que INT_MAX, o loop nunca termina.
Steve Jessop
... ou talvez termine imediatamente. Um dos dois Depende se o valor assinado é convertido em não assinado para comparação ou se o não assinado é convertido em assinado. Em uma plataforma de 64 bits com um int de 32 bits, porém, como o win64, o int seria promovido para size_t, e o loop nunca termina.
Steve Jessop
@SteveJessop: Você não pode dizer com certeza que o loop nunca acaba. Na iteração quando i == INT_MAX, i++causa um comportamento indefinido. Neste ponto, tudo pode acontecer.
Ben Voigt
@BenVoigt: true, e ainda não fornece motivos para ignorar o aviso :-)
Steve Jessop
2

Considere se você precisa iterar

O <algorithm>cabeçalho padrão fornece facilidades para isso:

using std::begin;  // allows argument-dependent lookup even
using std::end;    // if the container type is unknown here
auto sum = std::accumulate(begin(polygon), end(polygon), 0);

Outras funções na biblioteca de algoritmos realizam tarefas comuns - verifique se você sabe o que está disponível se quiser economizar seu esforço.

Toby Speight
fonte
1

Detalhes obscuros mas importantes: se você disser "for (auto it)" da seguinte maneira, obterá uma cópia do objeto, não do elemento real:

struct Xs{int i} x;
x.i = 0;
vector <Xs> v;
v.push_back(x);
for(auto it : v)
    it.i = 1;         // doesn't change the element v[0]

Para modificar os elementos do vetor, você precisa definir o iterador como uma referência:

for(auto &it : v)
Pierre
fonte
1

Se o seu compilador suportar, você pode usar um intervalo baseado em para acessar os elementos do vetor:

vector<float> vertices{ 1.0, 2.0, 3.0 };

for(float vertex: vertices){
    std::cout << vertex << " ";
}

Impressões: 1 2 3. Observe que você não pode usar esta técnica para alterar os elementos do vetor.

Brett L
fonte
0

Os dois segmentos de código funcionam da mesma maneira. No entanto, a rota unsigned int "está correta. O uso de tipos int não assinados funcionará melhor com o vetor na instância em que você o usou. Chamar a função de membro size () em um vetor retorna um valor inteiro não assinado, portanto, você deseja comparar a variável "i" para um valor de seu próprio tipo.

Além disso, se você ainda estiver um pouco desconfortável com a aparência de "unsigned int" em seu código, tente "uint". Esta é basicamente uma versão abreviada de "unsigned int" e funciona exatamente da mesma maneira. Você também não precisa incluir outros cabeçalhos para usá-lo.


fonte
Inteiro não assinado para size () não é necessariamente igual a "unsigned int" em termos de C ++, geralmente 'integer não assinado' nesse caso é um número inteiro não assinado de 64 bits, enquanto 'int não assinado' geralmente é de 32 bits.
Medran
0

Adicionando isso como não consegui encontrá-lo mencionado em nenhuma resposta: para iteração baseada em índice, podemos usar o decltype(vec_name.size())que seria avaliado comostd::vector<T>::size_type

Exemplo

for(decltype(v.size()) i{ 0 }; i < v.size(); i++) {
    /* std::cout << v[i]; ... */
}
Bharat S
fonte