Com as novas funcionalidades introduzidas pelos padrões recentes da linguagem, uma tarefa simples como percorrer um array em C++ pode se tornar confusa para alguns, pois agora há diversas maneiras de se fazê-lo. Logo, pensei que seria útil resumir neste artigo as três principais formas de se iterar por um array em C++.
Todavia, antres de passarmos aos detalhes de cada uma das 3 maneiras das quais falarei, é útil saber quais são cada uma delas: 1) for loop usando índices; 2) for loop usando iteradores; 3) range-based for loop (laço for baseado em uma faixa de valores).
Método #1 para percorrer um array em C++ – For loop usando índices
Este método de se percorrer arrays em C++ é o método clássico: todos os programadores de longa data do C++ já o utilisaram em algum momento, e até mesmo os que acabaram de começar no C++ que não estão habituados com as novas funcionalidades da linguagem já devem tê-lo utilizado.
Como funciona?
O modo de se percorrer containers (usarei esse termo mais genérico para me referir aos arrays de quando em quando) usando índices funciona da seguinte maneira: usa-se um for com uma variável de tipo inteiro (preferencialmente se usa o tipo size_t
, o mais adaptado a esta finalidade) no primeiro campo (o de declaração); testa-se essa variável no segundo campo (o da condição de execução) para verificar que ela é menor do que o tamanho do vetor; no terceiro campo (o da expressão executada ao fim de cada iteração do for), então, incrementa-se o valor da variável com o operador de incremento.
Exemplo de utilização
No exemplo a seguir, crio e percorro um container de tipo vector – o famoso vetor – com o método do for loop usando índices. Para percorrer o meu vetor intVector
, crio a variável index
cujo tipo é size_t
, e na condição de execução (ou de parada, como você preferir vê-la) verifico se ela é menor do que o tamanho do vetor (index < intVector.size()
). Ao fim de cada iteração do for, a variável index
é incrementada (++index
).
Além disso, no cout do corpo do for (linha 10) eu imprimo o valor da variável index
para formar a base da mensagem que é impressa na tela (intVector[1]: , por exemplo), e depois, para acessar o elemento do vetor correspondente ao índice armazenado na variável index
, eu preciso usá-la novamente, dessa vez na expressão intVector[index]
. Assim, o resultado impresso na tela a cada iteração tem o formato “intVector[índice]: [valor do vetor na posição do índice]” – veja a figura dos resultados após o bloco de código para compreender melhor o que quero dizer.
// Como percorrer um array em C++ - exemplo #1 // For loop usando índices #include <iostream> #include <vector> using namespace std; int main() { vector<int> intVector = {1, 2, 3, 4, 5}; // For loop usando índices para acessar os elementos do vetor for (size_t index = 0; index < intVector.size(); ++index) { cout << "intVector[" << index << "]: " << intVector[index] << endl; } return 0; }
Dica – cuidado com a condição de execução do for
Como os índices dos containers começam em 0, e não em 1, sempre tome cuidado ao percorrer um array em C++ usando for para não escrever o teste da condição de execução usando o operador <= ao invés do operador <. Se usarmos o operador <=, acessaremos uma posição além do último elemento do array e nosso programa terá então um comportamento indefinido.
Método #2 para percorrer um array em C++ – For loop usando iteradores
O segundo método para se percorrer arrays em C++ utiliza os iteradores. Os iteradores são tipos especiais de ponteiros cuja finalidade é a de percorrer contêineres, e para se declarar um iterador basta utilizar o tipo do contêiner seguido do operador de escopo e a palavra-chave iterator, seguidos do nome da variável (exemplo: vector<double>::iterator nomeDaVariavel
).
Como funciona?
Essa maneira de se percorrer o contêiner pode parecer um pouco estranho para aqueles que não estão acostumados a usar os iteradores, mas não se preocupe: o segredo é entender o que as funções begin()
e end()
fazem, e o que o operador de incremento ++ faz quando aplicado a ponteiros.
As funções begin( )
e end( )
retornam, respectivamente, um iterador (ou seja, um ponteiro dedicado a percorrer arrays que oferece funções que facilitam essa tarefa) para o primeiro elemento do array, e um iterador para um elemento que fica uma posição após o último elemento do array.
Assim, no for
inicializa-se a variável do loop com o tipo iterador para que ela receba o valor de retorno de begin()
(ou seja, para que ela seja um iterador de aponta para o primeiro elemento do array), e essa variável é incrementada ao fim de cada iteração do laço for
através do operador de incremento ++, que desloca o ponteiro uma posição para a frente – fazendo com que o iterador avance para o próximo elemento no contêiner. Por fim, para acessar o elemento ao qual aponta o iterador, basta usar o operador *, assim como fazemos com ponteiros.
Exemplo de utilização
Tomemos o exemplo a seguir para entender melhor como funciona o for usando iteradores para se percorrer um array em C++. Nesse exemplo, usamos o mesmo contêiner (um vector de inteiros contendo valores de 1 a 5) do exemplo anterior, mas logo vemos a primeira diferença quando olhamos para a variável declarada dentro do for
(linha 12): usamos um iterador ao invés de size_t
, e como não queremos modificar os valores do nosso vetor, usamos um iterador constante (vector<int>::const_iterator
), que não permite usar o operador * para modificar os dados do elemento para o qual ele aponta, mas apenas para lê-los. Como se trata de um iterador constante, usamos o método cbegin()
ao invés de begin()
para obtê-lo.
Em seguida, na condição de execução do laço, verificamos se iterador
é diferente do iterador constante que aponta para a posição além do último elemento do vetor intVector
(esse iterador é retornado pela função cend()
– aqui também usamos um iterador constante para manter a consistência do bloco); enquanto eles forem diferentes, sabemos que iterador
aponta para elementos que pertencem ao vetor.
Por fim, incrementamos o valor do iterador ao fim de cada iteração com a expressão ++iterador
, e acessamos o elemento apontado por iterador
usando o operador * (linha 14).
// Como percorrer um array em C++ - exemplo #2 // For loop usando iteradores #include <iostream> #include <vector> using namespace std; int main() { vector<int> intVector = {1, 2, 3, 4, 5}; // For loop usando iteradores para acessar os elementos do vetor for (vector<int>::const_iterator iterador = intVector.cbegin(); iterador != intVector.cend(); ++iterador) { cout << "Valor do elemento para o qual o iterador aponta: " << *iterador << endl; } return 0; }
Bom hábito – use iteradores constantes sempre que possível
Sempre que se for usar um iterador apenas para ler valores de um contêiner, e não para modificá-los, use as versões constantes dos iteradores (obtidas com cbegin() e cend()). Desta forma, previnem-se modificações indesejadas dos elementos do contêiner, mas também a intenção do código (de não modificar nada) fica clara pelo uso do iterador constante.
Método #3 para percorrer um array em C++ – Range-based for loop
O terceiro método para se percorrer um array em C++ é o range-based for loop (ou for baseado em uma faixa de valores). Esta nova versão do clássico laço for é semelhante ao modo como ele é usado em outras linguagens (como Python e Javascript), e foi introduzida na linguagem com o padrão C++11. Ela é equivalente (em seus efeitos) às outras duas, mas é muito mais fácil de se ler e compreender. Vejamos, então, como ela funciona.
Como funciona?
A sintaxe para se usar o range-based for é a seguinte: for([declaração da variável do loop] : [expressão geradora da faixa a se percorrer])
. A variável declarada à esquerda dos dois-pontos é utilizada dentro do for, e só existe no escopo do for, e pode ser uma variável normal ou uma referência. À direita dos dois-pontos, por sua vez, está o contêiner ou a expressão que gerará a faixa de valores a ser percorrida (é preciso dizê-lo assim, e não apenas falar do contêiner ou variável, porque pode-se usar diretamente o retorno de uma função, por exemplo, nessa posição). Assim, a cada iteração, a variável à esquerda dos dois-pontos será inicializada usando um novo valor da expressão à direita, na ordem em que eles estiverem posicionados.
Como o funcionamento desse método para se percorrer um array em C++ pode ser um pouco confuso à primeira vista, vejamos um exemplo para tornar as coisas mais claras.
Exemplo de utilização
No exemplo 3, retomei o mesmo contêiner dos exemplos anteriores, intVector
, e o percorri utilizando um range-based for. Na linha 12, vemos que à esquerda dos dois-pontos criei uma variável de tipo referência para const auto, valorInt
; mas que moléstia é isso? Calma, isso nada mais é do que uma referência para inteiro constante, ou seja, isso: const auto&
, é igual a isso: const int&
(neste contexto específico, claro). Como pode? É tudo culpa do auto.
O auto é uma palavra-chave muito útil para ser usada com os laços for
desse tipo por lhe facilitarem a leitura: ao se usar auto ao invés do tipo da variável, o compilador detecta qual é o tipo correto e “reescreve” a declaração para você usando esse tipo.
Portanto, essa capacidade do auto permite reescrever declarações complicadas de se ler de um modo mais inteligível. Por exemplo: const std::vector<std::pair<int, double>> vetorDeParesIntDouble = funcaoQueRetornaEsseTipoEstranho()
torna-se simplesmente const auto vetorDeParesIntDouble = funcaoQueRetornaEsseTipoEstranho()
. Legal, não? Voltemos ao nosso caso.
Sabemos agora que à esquerda dos dois-pontos há uma referência para inteiros constante (usei esse tipo constante para deixar claro que não quero modificar os valores que forem lidos do vetor), e à direita está o próprio vetor a ser percorrido. Tá, mas e o resto? Não tem resto, é só isso mesmo ?. Para usar o valor do elemento do vetor em cada operação, basta usar o nome da variável que foi declarada (valorInt
) onde se quiser usar o elemento (como é feito na linha 16).
// Como percorrer um array em C++ - exemplo #3 // Range-based for loop #include <iostream> #include <vector> using namespace std; int main() { vector<int> intVector = {1, 2, 3, 4, 5}; // Range-based for loop usado para acessar os elementos do vetor. // Criei um tipo referência e não uma variável normal para evitar // copiar desnecessariamente o elemento do vetor. for (const auto& valorInt : intVector) { cout << "Valor do elemento da vez: " << valorInt << endl; } return 0; }
Qual método usar para percorrer um array em C++?
Agora que vimos os principais modos de se percorrer um array em C++ (omiti propositalmente aquele que utilizam while), a pergunta que se segue é: qual deles devo usar? Falarei das situações de uso comuns para cada um deles.
For loop usando índices
Esta maneira de se percorrer um array em C++ tem alguns problemas que lhe tornam, normalmente, a última escolha para essa tarefa. O primeiro deles é que ela não expressa bem o intuito do laço, com exceção de às vezes utilizar diretamente o método size()
sobre a variável do array na condição de execução (exemplo #1, linha 11)- o que indica que se está percorrendo aquele contêiner.
Além disso, este primeiro modo não é muito fácil de se ler, podendo ser mais ou menos complicada de entender de acordo com as expressões usadas no ‘cabeçalho’ do for
.
Quando usá-lo?
O for loop com índices deve ser usado apenas quando não se puder utilizar os outros dois modos, como em alguns casos onde é absolutamente necessário se ter o índice que corresponde a um elemento do array (ainda assim, é possível criar uma “gambiarra” para obtê-lo usando os outros métodos).
For loop usando iteradores
O segundo modo de se percorrer um array em C++, usando iteradores com for loops, expressa sua intençao melhor do que o primeiro (por normalmente se usarem diretamente as funções begin()
e end()
sobre o contêiner percorrido na “assinatura” do for
– exemplo #2, linha 12), mas também tem o problema de não ser muito légivel: aqui, sobretudo, a notação pode ficar muito carregada.
Quando usá-lo?
Esse método tem a vantagem de avaliar a condição de execução e verificar onde está o iterador retornado pelo método end()
a cada iteração (ao contrário do modo #3, que verifica onde acaba o array com end()
apenas na “inicialização” do for
), o que o permite se adaptar às possíveis mudanças feitas à estrutura do contêiner (por exemplo, a uma adição de elemento no fim de um vetor) que não invalidem os iteradores para aquele contêiner. Assim, se a estrtura interna do contêiner for ser alterada durante o loop, a melhor alternativa são os for com iteradores.
Ao percorrer um vetor, por exemplo, se é previsto que serão adicionados elementos ao fim do vetor (uma operação que não invalida iteradores para o vetor na maioria dos casos – a menos que haja uma expansão do vetor), é melhor usar o for com iteradores ao invés do range-based for
Range-based for loop
Esse tipo de for é o mais fácil de se ler entre os três, e também o mais simples de se escrever, então na maioria das situações ele é a escolha mais fácil a se fazer para percorrer um array em C++. De fato, na maioria das situações a estrutura interna do array ou contêiner percorrido não é alterada – apenas seus elementos são lidos ou modificados -, e ele torna-se a escolha mais vantajosa. Todavia, como dito na seção anterior, quando a estrutura do contêiner muda este tipo de for não é aconselhado, pois ele avalia onde está o fim do contêiner apenas antes da primeira iteração, e portanto não “acompanha” as mudanças na estrutura do contêiner.
Quando usá-lo?
Pelas vantagens que mencionei no parágrafo anterior, o range-based for pode ser usado – e é a escolha com melhor custo-benefício – na maioria das situações quando se deseja percorrer um array em C++, com exceção de quando a estrutura interna do contêiner é alterada durante o loop (na verdade, esse tipo de modificação deve ser evitada sempre que possível).
Conclusão
Neste artigo vimos 3 maneiras de percorrer um array em C++. Tratamos dos seguintes modos de fazê-lo: 1) for usando índices; 2) for usando iteradores; 3) range-based for. Abordamos como funciona cada um deles (com exemplos), e também discutimos quando usar cada uma das maneiras apresentadas.
Sou apaixonado por tecnologia, literatura e também filosofia. O cultivo dessas paixões ao longo da minha trajetória me inspiraram a compartilhar aquilo que aprendo com os outros da maneira mais clara que eu possa. Sou formado em Engenharia Elétrica no Brasil, e também sou engenheiro formado na França. Trabalho atualmente como programador C++ em uma multinacional francesa, uma das maiores empresas de TI do mundo.