Como faço para colocar duas instruções de incremento em um loop 'for' do C ++?

93

Eu gostaria de incrementar duas variáveis ​​em um for condição -loop em vez de uma.

Então, algo como:

for (int i = 0; i != 5; ++i and ++j) 
    do_something(i, j);

Qual é a sintaxe para isso?

Peter Smit
fonte

Respostas:

154

Um idioma comum é usar o operador vírgula que avalia os dois operandos e retorna o segundo operando. Portanto:

for(int i = 0; i != 5; ++i,++j) 
    do_something(i,j);

Mas é realmente um operador vírgula?

Agora que escreveu isso, um comentarista sugeriu que era na verdade algum açúcar sintático especial na instrução for, e não um operador vírgula. Verifiquei isso no GCC da seguinte forma:

int i=0;
int a=5;
int x=0;

for(i; i<5; x=i++,a++){
    printf("i=%d a=%d x=%d\n",i,a,x);
}

Eu esperava que x pegasse o valor original de a, então deveria ter mostrado 5,6,7 .. para x. O que eu consegui foi isso

i=0 a=5 x=0
i=1 a=6 x=0
i=2 a=7 x=1
i=3 a=8 x=2
i=4 a=9 x=3

No entanto, se eu colocar a expressão entre colchetes para forçar o analisador a realmente ver um operador vírgula, recebo isso

int main(){
    int i=0;
    int a=5;
    int x=0;

    for(i=0; i<5; x=(i++,a++)){
        printf("i=%d a=%d x=%d\n",i,a,x);
    }
}

i=0 a=5 x=0
i=1 a=6 x=5
i=2 a=7 x=6
i=3 a=8 x=7
i=4 a=9 x=8

Inicialmente pensei que isso mostrava que não estava se comportando como um operador vírgula, mas, ao que parece, isso é simplesmente um problema de precedência - o operador vírgula tem a precedência mais baixa possível , então a expressão x = i ++, a ++ é eficaz analisado como (x = i ++), a ++

Obrigado por todos os comentários, foi uma experiência de aprendizado interessante, e uso o C há muitos anos!

Paul Dixon
fonte
1
Eu li várias vezes que a vírgula na primeira ou terceira parte de um loop for não é o operador de vírgula, mas apenas um separador de vírgula. (No entanto, não consigo encontrar uma fonte oficial para isso, já que sou particularmente ruim em analisar o padrão da linguagem C ++.)
Daniel Daranas
A princípio pensei que você estava incorreto, mas escrevi um código de teste e você está correto - ele não se comporta como um operador de vírgula. Irá corrigir minha resposta!
Paul Dixon
19
Ele é um operador vírgula nesse contexto. O motivo pelo qual você não está obtendo o que espera é que o operador de comando tem precedência mais baixa do que o operador de atribuição, portanto, sem os parênteses, ele analisa como (x = i ++), j ++.
caf
6
É um operador vírgula. A atribuição é mais forte do que o operador vírgula, então x = i ++, a ++ é analisado (x = i ++), a ++ e não x = (i ++, a ++). Essa característica é mal utilizada por algumas bibliotecas de modo que v = 1,2,3; faz as coisas intuitivas, mas apenas porque v = 1 retorna um objeto proxy para o qual o operador vírgula sobrecarregado faz um acréscimo.
AProgrammer
3
Está bem. Da seção 6.5.3 de open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2857.pdf , a última parte é uma "expressão". (Embora 1.6 # 2 defina uma "lista de expressões" como uma "lista de expressões separadas por vírgulas", esta construção não aparece em 6.5.3.). Isso significa que quando escrevemos "++ i, ++ j", ela deve ser uma expressão em si mesma e, portanto, "," deve ser o operador vírgula (5.18). (Esta não é uma "lista de inicializadores" ou "lista de argumentos para funções", que são exemplos onde "a vírgula tem um significado especial", como diz 5.18 # 2.). Acho que é um pouco confuso.
Daniel Daranas
55

Tente isto

for(int i = 0; i != 5; ++i, ++j)
    do_something(i,j);
Yeyeyerman
fonte
17
+1 Você também pode declarar j na primeira parte. for (int i = 0, j = 0; i! = 5; ++ i, ++ j) {...}
Daniel Daranas
1
+1 Como uma observação lateral, esta mesma sintaxe funciona em C # (eu consegui aqui de uma pesquisa no Google por "C # para contadores de incremento de loop 2", então pensei em mencionar isso).
CodingWithSpike de
@CodingWithSpike: Bem, em C # a vírgula é especial; na verdade, não é permitido que uma expressão do operador vírgula apareça lá. Exemplo de uso legal de operador vírgula em C ++, mas rejeitado por C #:for( ; ; ((++i), (++j)) )
Ben Voigt,
@BenVoigt não tem nada a ver com a vírgula. Isso também não é C # válido: for(int i = 0; i != 5; (++i)) {o parêntese extra engana o compilador fazendo-o pensar que não é mais uma operação de "incremento".
CodingWithSpike
@CodingWithSpike: Isso é verdade, mas os parênteses também alteram a forma como C # vê a vírgula e evita o significado especial dentro da ação for.
Ben Voigt,
6

Tente não fazer isso!

De http://www.research.att.com/~bs/JSF-AV-rules.pdf :

Regra AV 199
A expressão de incremento em um loop for não realizará nenhuma ação a não ser alterar um único parâmetro de loop para o próximo valor do loop.

Justificativa: Legibilidade.

Squelart
fonte
4
Isso é verdade, mas para ser justo, tenho quase certeza de que o padrão de regras foi escrito para software embarcado em um caça a jato, não para o programa C (++) tipo jardim. Dito isso, é provavelmente um bom hábito de legibilidade para adquirir e, quem sabe, talvez você esteja projetando o software F-35 e será um hábito a menos para quebrar.
galois
3
for (int i = 0; i != 5; ++i, ++j) 
    do_something(i, j);
malaio
fonte
2

Vim aqui para me lembrar de como codificar um segundo índice na cláusula de incremento de um loop FOR, o que eu sabia que poderia ser feito principalmente por observá-lo em um exemplo que incorporei em outro projeto, aquele escrito em C ++.

Hoje estou trabalhando em C #, mas tive certeza de que obedeceria às mesmas regras nesse aspecto, já que a instrução FOR é uma das estruturas de controle mais antigas em toda a programação. Felizmente, recentemente passei vários dias documentando precisamente o comportamento de um loop FOR em um de meus programas C mais antigos, e percebi rapidamente que esses estudos continham lições que se aplicavam ao problema C # atual, em particular ao comportamento da segunda variável de índice .

Para os incautos, a seguir está um resumo de minhas observações. Tudo o que vi acontecendo hoje, ao observar cuidadosamente as variáveis ​​na janela Locals, confirmou minha expectativa de que uma instrução C # FOR se comporta exatamente como uma instrução C ou C ++ FOR.

  1. Na primeira vez que um loop FOR é executado, a cláusula de incremento (a terceira de suas três) é ignorada. No Visual C e C ++, o incremento é gerado como três instruções de máquina no meio do bloco que implementa o loop, de forma que a passagem inicial execute o código de inicialização apenas uma vez e, em seguida, salte sobre o bloco de incremento para executar o teste de finalização. Isso implementa o recurso de que um loop FOR executa zero ou mais vezes, dependendo do estado de seu índice e variáveis ​​de limite.
  2. Se o corpo do loop for executado, sua última instrução é um salto para a primeira das três instruções de incremento que foram ignoradas pela primeira iteração. Após a execução deles, o controle cai naturalmente no código de teste de limite que implementa a cláusula do meio. O resultado desse teste determina se o corpo do loop FOR é executado ou se o controle é transferido para a próxima instrução após o salto na parte inferior de seu escopo.
  3. Como o controle é transferido da parte inferior do bloco de loop FOR para o bloco de incremento, a variável de índice é incrementada antes que o teste seja executado. Esse comportamento não apenas explica por que você deve codificar suas cláusulas de limite da maneira que aprendeu, mas também afeta qualquer incremento secundário que você adicionar, por meio do operador vírgula, porque se torna parte da terceira cláusula. Conseqüentemente, não é alterado na primeira iteração, mas sim na última iteração, que nunca executa o corpo.

Se alguma de suas variáveis ​​de índice permanecer no escopo quando o loop terminar, seu valor será um maior do que o limite que interrompe o loop, no caso da variável de índice verdadeira. Da mesma forma, se, por exemplo, a segunda variável for inicializada com zero antes de o loop ser inserido, seu valor no final será a contagem de iteração, assumindo que é um incremento (++), não um decremento, e que nada em o corpo do loop muda seu valor.

David A. Gray
fonte
1

Eu concordo com o squelart. Incrementar duas variáveis ​​é propenso a bugs, especialmente se você testar apenas uma delas.

Esta é a maneira legível de fazer isso:

int j = 0;
for(int i = 0; i < 5; ++i) {
    do_something(i, j);
    ++j;
}

Foros loops são destinados aos casos em que o seu loop é executado em uma variável crescente / decrescente. Para qualquer outra variável, altere-a no loop.

Se você precisa jestar vinculado a i, por que não deixar a variável original como está e adicionar i?

for(int i = 0; i < 5; ++i) {
    do_something(i,a+i);
}

Se sua lógica for mais complexa (por exemplo, você precisa monitorar mais de uma variável), eu usaria um whileloop.

Ran Halprin
fonte
2
No primeiro exemplo, j é incrementado mais uma vez do que i! Que tal um iterador no qual é necessário realizar alguma ação nas primeiras x etapas? (E a coleção é sempre longa o suficiente) Você pode ascender o iterador a cada iteração, mas é muito mais limpo.
Peter Smit
0
int main(){
    int i=0;
    int a=0;
    for(i;i<5;i++,a++){
        printf("%d %d\n",a,i);
    } 
}
Arkaitz Jimenez
fonte
1
Qual é o ponto de não fazer ie alocal para o loop?
sbi
2
Nenhum, apenas mostrando como fazer os dois incrementos no for, é apenas um exemplo do sytnax
Arkaitz Jimenez
0

Use matemática. Se as duas operações dependem matematicamente da iteração do loop, por que não fazer as contas?

int i, j;//That have some meaningful values in them?
for( int counter = 0; counter < count_max; ++counter )
    do_something (counter+i, counter+j);

Ou, mais especificamente referindo-se ao exemplo do OP:

for(int i = 0; i != 5; ++i)
    do_something(i, j+i);

Especialmente se você estiver passando para uma função por valor, deverá obter algo que faça exatamente o que você deseja.

xaviersjs
fonte