¿Cómo coloco dos declaraciones de incremento en un bucle 'for' de C ++?

93

Me gustaría incrementar dos variables en una forcondición de bucle en lugar de una.

Entonces algo como:

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

¿Cuál es la sintaxis de esto?

Peter Smit
fuente

Respuestas:

154

Un modismo común es usar el operador de coma que evalúa ambos operandos y devuelve el segundo operando. Así:

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

¿Pero es realmente un operador de coma?

Ahora, habiendo escrito eso, un comentarista sugirió que en realidad era un azúcar sintáctico especial en la declaración for, y no un operador de coma en absoluto. Lo verifiqué en GCC de la siguiente manera:

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);
}

Esperaba que x tomara el valor original de a, por lo que debería haber mostrado 5,6,7 .. para x. Lo que obtuve fue esto

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

Sin embargo, si coloco entre corchetes la expresión para obligar al analizador a ver realmente un operador de coma, obtengo esto

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 pensé que esto mostraba que no se estaba comportando como un operador de coma en absoluto, pero resulta que esto es simplemente un problema de precedencia: el operador de coma tiene la precedencia más baja posible , por lo que la expresión x = i ++, a ++ es efectivamente analizado como (x = i ++), a ++

Gracias por todos los comentarios, fue una experiencia de aprendizaje interesante y he estado usando C durante muchos años.

Paul Dixon
fuente
1
He leído varias veces que la coma en la primera o tercera parte de un bucle for no es el operador de coma, sino solo un separador de coma. (Sin embargo, no puedo encontrar una fuente oficial para esto, ya que soy particularmente malo en analizar el estándar del lenguaje C ++)
Daniel Daranas
Primero pensé que estabas incorrecto, pero escribí un código de prueba y estás en lo correcto: no se comporta como un operador de coma. ¡Enmendará mi respuesta!
Paul Dixon
19
que es un operador de coma en ese contexto. La razón por la que no obtiene lo que espera es que el operador de comando tiene una precedencia menor que el operador de asignación, por lo que sin los paréntesis se analiza como (x = i ++), j ++.
caf
6
ES un operador de coma. La asignación se vincula más fuertemente que el operador de coma, por lo que x = i ++, a ++ se analiza (x = i ++), a ++ y no x = (i ++, a ++). Algunas bibliotecas hacen un mal uso de esa característica, de modo que v = 1,2,3; hace las cosas intuitivas, pero solo porque v = 1 devuelve un objeto proxy para el que el operador de coma sobrecargado realiza una adición.
Programador
3
Okay. De open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2857.pdf sección 6.5.3 la última parte es una "expresión". (Aunque 1.6 # 2 define una "lista de expresiones" como una "lista de expresiones separadas por comas", esta construcción no aparece en 6.5.3). Esto significa que cuando escribimos "++ i, ++ j" tiene que ser una expresión en un por sí mismo, y por lo tanto "," debe ser el operador de coma (5.18). (Esta no es una "lista de inicializadores" o "lista de argumentos para funciones", que son ejemplos en los que "la coma recibe un significado especial", como dice 5.18 # 2). Aunque lo encuentro un poco confuso.
Daniel Daranas
55

Prueba esto

for(int i = 0; i != 5; ++i, ++j)
    do_something(i,j);
yeyeyerman
fuente
17
+1 También puede declarar j en la primera parte. for (int i = 0, j = 0; i! = 5; ++ i, ++ j) {...}
Daniel Daranas
1
+1 Como nota al margen, esta misma sintaxis funciona en C # (llegué aquí de una búsqueda en Google de "C # para contadores de incremento de bucle 2", así que pensé en mencionar esto).
CodingWithSpike
@CodingWithSpike: Bueno, en C # la coma es especial, en realidad no es legal que aparezca una expresión de operador de coma allí. Ejemplo de uso legal del operador de coma en C ++, pero rechazado por C #:for( ; ; ((++i), (++j)) )
Ben Voigt
@BenVoigt no tiene nada que ver con la coma. Esto tampoco es legal en C #: for(int i = 0; i != 5; (++i)) {el paréntesis adicional engaña al compilador para que piense que ya no es una operación de "incremento".
CodingWithSpike
@CodingWithSpike: Eso es cierto, pero los paréntesis también cambian la forma en que C # ve la coma y evita el significado especial dentro de la acción for.
Ben Voigt
6

¡Intenta no hacerlo!

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

AV Regla 199
La expresión de incremento en un bucle for no realizará ninguna acción más que cambiar un parámetro de un solo bucle al siguiente valor del bucle.

Justificación: legibilidad.

chillido
fuente
4
Eso es cierto, pero para ser justos, estoy bastante seguro de que el estándar de reglas se escribió para el software integrado en un avión de combate, no para el programa C (++) de variedad de jardín. Dicho esto, probablemente sea un buen hábito de legibilidad, y quién sabe, tal vez esté diseñando el software del F-35, y será un hábito menos que romper.
Galois
3
for (int i = 0; i != 5; ++i, ++j) 
    do_something(i, j);
malayo
fuente
2

Vine aquí para recordarme a mí mismo cómo codificar un segundo índice en la cláusula de incremento de un bucle FOR, que sabía que se podía hacer principalmente al observarlo en una muestra que incorporé a otro proyecto, que estaba escrito en C ++.

Hoy estoy trabajando en C #, pero estaba seguro de que obedecería las mismas reglas a este respecto, ya que la instrucción FOR es una de las estructuras de control más antiguas de toda la programación. Afortunadamente, recientemente había pasado varios días documentando con precisión el comportamiento de un bucle FOR en uno de mis programas C más antiguos, y rápidamente me di cuenta de que esos estudios contenían lecciones que se aplicaban al problema actual de C #, en particular al comportamiento de la segunda variable de índice .

Para los incautos, a continuación se presenta un resumen de mis observaciones. Todo lo que vi sucediendo hoy, al observar cuidadosamente las variables en la ventana Locales, confirmó mi expectativa de que una instrucción FOR de C # se comporta exactamente como una instrucción FOR de C o C ++.

  1. La primera vez que se ejecuta un bucle FOR, se omite la cláusula de incremento (la tercera de sus tres). En Visual C y C ++, el incremento se genera como tres instrucciones de máquina en el medio del bloque que implementa el bucle, de modo que la pasada inicial ejecuta el código de inicialización solo una vez, luego salta sobre el bloque de incremento para ejecutar la prueba de terminación. Esto implementa la característica de que un bucle FOR se ejecuta cero o más veces, dependiendo del estado de sus variables de índice y límite.
  2. Si el cuerpo del bucle se ejecuta, su última instrucción es un salto a la primera de las tres instrucciones de incremento que se omitieron en la primera iteración. Después de que se ejecuten, el control cae naturalmente en el código de prueba de límite que implementa la cláusula intermedia. El resultado de esa prueba determina si el cuerpo del bucle FOR se ejecuta, o si el control se transfiere a la siguiente instrucción más allá del salto en la parte inferior de su alcance.
  3. Dado que el control se transfiere desde la parte inferior del bloque de bucle FOR al bloque de incremento, la variable de índice se incrementa antes de que se ejecute la prueba. Este comportamiento no solo explica por qué debe codificar sus cláusulas de límite de la manera que aprendió, sino que afecta cualquier incremento secundario que agregue, a través del operador de coma, porque se convierte en parte de la tercera cláusula. Por lo tanto, no se cambia en la primera iteración, sino en la última iteración, que nunca ejecuta el cuerpo.

Si alguna de sus variables de índice permanece dentro del alcance cuando finaliza el ciclo, su valor será uno más alto que el umbral que detiene el ciclo, en el caso de la variable de índice verdadera. Asimismo, si, por ejemplo, la segunda variable se inicializa a cero antes de que se ingrese el ciclo, su valor al final será el recuento de iteraciones, asumiendo que es un incremento (++), no una disminución, y que nada en el cuerpo del bucle cambia su valor.

David A. Gray
fuente
1

Estoy de acuerdo con Squelart. Incrementar dos variables es propenso a errores, especialmente si solo prueba una de ellas.

Esta es la forma legible de hacer esto:

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

ForLos bucles están pensados ​​para los casos en que su bucle se ejecuta en una variable creciente / decreciente. Para cualquier otra variable, cámbiela en el ciclo.

Si necesita jestar vinculado i, ¿por qué no dejar la variable original como está y agregar i?

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

Si su lógica es más compleja (por ejemplo, necesita monitorear más de una variable), usaría un whilebucle.

Ran Halprin
fuente
2
En el primer ejemplo, j se incrementa una vez más que i! ¿Qué pasa con un iterador en el que se debe realizar alguna acción para los primeros x pasos? (Y la colección siempre es lo suficientemente larga) Puede ascender el iterador en cada iteración, pero es mucho más limpio en mi humilde opinión.
Peter Smit
0
int main(){
    int i=0;
    int a=0;
    for(i;i<5;i++,a++){
        printf("%d %d\n",a,i);
    } 
}
Arkaitz Jiménez
fuente
1
Cuál es el punto de no hacer iy alocal para el bucle?
sbi
2
Ninguno, solo muestra cómo hacer ambos incrementos en el for, es solo un ejemplo de la sintaxis
Arkaitz Jimenez
0

Utilice matemáticas. Si las dos operaciones dependen matemáticamente de la iteración del ciclo, ¿por qué no hacer los cálculos?

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

O, más específicamente, refiriéndose al ejemplo del OP:

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

Especialmente si está pasando a una función por valor, entonces debería obtener algo que haga exactamente lo que desea.

xaviersjs
fuente