¿Qué hace el operador de coma?

167

¿Qué hace el ,operador en C?

lillq
fuente
1
Como noto en mi respuesta, hay un punto de secuencia después de la evaluación del operando izquierdo. Esto es diferente a la coma en una llamada de función que es simplemente gramatical.
Shafik Yaghmour
2
@SergeyK. - Dado que esto fue preguntado y respondido años antes que el otro, es más probable que el otro sea un duplicado de esta pregunta. Sin embargo, el otro también tiene doble etiqueta con c y c ++ , lo cual es una molestia. Esta es una pregunta y respuesta de solo C, con respuestas decentes.
Jonathan Leffler

Respuestas:

129

La expresion:

(expression1,  expression2)

Primero se evalúa la expresión1, luego se evalúa la expresión2 y se devuelve el valor de la expresión2 para toda la expresión.

lillq
fuente
3
entonces si escribo i = (5,4,3,2,1,0) entonces idealmente debería devolver 0, ¿correcto? pero se me asigna un valor de 5? ¿Pueden ayudarme a entender dónde me estoy equivocando?
Jayesh el
19
@James: el valor de una operación de coma siempre será el valor de la última expresión. En ningún momento tendrá ilos valores 5, 4, 3, 2 o 1. Es simplemente 0. Es prácticamente inútil a menos que las expresiones tengan efectos secundarios.
Jeff Mercado el
66
Tenga en cuenta que existe un punto de secuencia completo entre la evaluación del LHS de la expresión de coma y la evaluación del RHS (consulte la respuesta de Shafik Yaghmour para obtener una cita del estándar C99). Esta es una propiedad importante del operador de coma.
Jonathan Leffler
55
i = b, c;es equivalente a (i = b), cporque porque la asignación =tiene mayor prioridad que el operador de coma ,. El operador de coma tiene la precedencia más baja de todas.
ciclista
1
Me preocupa que los paréntesis sean engañosos en dos aspectos: (1) no son necesarios: el operador de coma no tiene que estar rodeado de paréntesis; y (2) podrían confundirse con los paréntesis alrededor de la lista de argumentos de una llamada de función, pero la coma en la lista de argumentos no es el operador de coma. Sin embargo, arreglarlo no es del todo trivial. Quizás: en la declaración: expression1, expression2;primero expression1se evalúa, presumiblemente por sus efectos secundarios (como llamar a una función), luego hay un punto de secuencia, luego expression2se evalúa y se devuelve el valor ...
Jonathan Leffler
119

He visto más utilizado en whilebucles:

string s;
while(read_string(s), s.len() > 5)
{
   //do something
}

Hará la operación, luego hará una prueba basada en un efecto secundario. La otra forma sería hacerlo así:

string s;
read_string(s);
while(s.len() > 5)
{
   //do something
   read_string(s);
}
crashmstr
fuente
21
Oye, eso es ingenioso! A menudo he tenido que hacer cosas poco ortodoxas en un bucle para solucionar ese problema.
staticsan
66
A pesar de que probablemente sería menos oscuro y más legible si se hizo algo como: while (read_string(s) && s.len() > 5). Obviamente, eso no funcionaría si read_stringno tiene un valor de retorno (o no tiene un valor significativo). (Edit: Lo siento, no se dio cuenta qué edad tenía este post.)
jamesdlin
11
@staticsan No tenga miedo de usar while (1)con una break;declaración en el cuerpo. Intentar forzar la parte de ruptura del código hacia arriba en la prueba while o hacia abajo en la prueba do-while, a menudo es un desperdicio de energía y hace que el código sea más difícil de entender.
potrzebie
8
@jamesdlin ... y la gente todavía lo lee. Si tiene algo útil que decir, dígalo. Los foros tienen problemas con los hilos resucitados porque los hilos generalmente están ordenados por fecha de la última publicación. StackOverflow no tiene tales problemas.
Dimitar Slavchev
3
@potrzebie Me gusta el enfoque de coma mucho mejor que while(1)y break;
Michael
38

El operador de coma evaluará el operando izquierdo, descartará el resultado y luego evaluará el operando derecho y ese será el resultado. El uso idiomático como se señala en el enlace es al inicializar las variables utilizadas en un forbucle, y da el siguiente ejemplo:

void rev(char *s, size_t len)
{
  char *first;
  for ( first = s, s += len - 1; s >= first; --s)
      /*^^^^^^^^^^^^^^^^^^^^^^^*/ 
      putchar(*s);
}

De lo contrario, no hay muchos usos excelentes del operador de coma , aunque es fácil abusar de generar código que es difícil de leer y mantener.

Del borrador del estándar C99, la gramática es la siguiente:

expression:
  assignment-expression
  expression , assignment-expression

y el párrafo 2 dice:

El operando izquierdo de un operador de coma se evalúa como una expresión vacía; Hay un punto de secuencia después de su evaluación. Luego se evalúa el operando correcto; El resultado tiene su tipo y valor. 97) Si se intenta modificar el resultado de un operador de coma o acceder a él después del siguiente punto de secuencia, el comportamiento es indefinido.

La nota a pie de página 97 dice:

Un operador de coma no produce un valor l .

lo que significa que no puede asignar el resultado del operador de coma .

Es importante tener en cuenta que el operador de coma tiene la prioridad más baja y, por lo tanto, hay casos en los que el uso ()puede marcar una gran diferencia, por ejemplo:

#include <stdio.h>

int main()
{
    int x, y ;

    x = 1, 2 ;
    y = (3,4) ;

    printf( "%d %d\n", x, y ) ;
}

tendrá el siguiente resultado:

1 4
Shafik Yaghmour
fuente
28

El operador de coma combina las dos expresiones a cada lado en una sola, y las evalúa en orden de izquierda a derecha. El valor del lado derecho se devuelve como el valor de toda la expresión. (expr1, expr2)es como, { expr1; expr2; }pero puede usar el resultado de expr2una llamada de función o asignación.

A menudo se ve en forbucles para inicializar o mantener múltiples variables como esta:

for (low = 0, high = MAXSIZE; low < high; low = newlow, high = newhigh)
{
    /* do something with low and high and put new values
       in newlow and newhigh */
}

Aparte de esto, solo lo he usado "enfadado" en otro caso, al concluir dos operaciones que siempre deben ir juntas en una macro. Teníamos código que copiaba varios valores binarios en un búfer de bytes para enviarlos a una red, y manteníamos un puntero donde habíamos llegado a:

unsigned char outbuff[BUFFSIZE];
unsigned char *ptr = outbuff;

*ptr++ = first_byte_value;
*ptr++ = second_byte_value;

send_buff(outbuff, (int)(ptr - outbuff));

Donde los valores fueron shorts o ints lo hicimos:

*((short *)ptr)++ = short_value;
*((int *)ptr)++ = int_value;

Más tarde leemos que esto no era realmente válido C, porque (short *)ptrya no es un valor l y no se puede incrementar, aunque a nuestro compilador en ese momento no le importó. Para solucionar esto, dividimos la expresión en dos:

*(short *)ptr = short_value;
ptr += sizeof(short);

Sin embargo, este enfoque se basó en que todos los desarrolladores recordaran poner ambas declaraciones todo el tiempo. Queríamos una función en la que pudieras pasar el puntero de salida, el valor y el tipo de valor. Siendo C, no C ++ con plantillas, no podíamos tener una función que tomara un tipo arbitrario, por lo que nos decidimos por una macro:

#define ASSIGN_INCR(p, val, type)  ((*((type) *)(p) = (val)), (p) += sizeof(type))

Al usar el operador de coma pudimos usar esto en expresiones o como declaraciones como deseábamos:

if (need_to_output_short)
    ASSIGN_INCR(ptr, short_value, short);

latest_pos = ASSIGN_INCR(ptr, int_value, int);

send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff));

¡No estoy sugiriendo que ninguno de estos ejemplos tenga buen estilo! De hecho, me parece recordar el Código Completo de Steve McConnell que aconseja incluso no usar operadores de coma en un forbucle: para facilitar la lectura y la mantenibilidad, el bucle debe estar controlado por una sola variable y las expresiones en la forlínea solo deben contener código de control de bucle, no otros bits adicionales de inicialización o mantenimiento de bucle.

Paul Stephenson
fuente
¡Gracias! Fue mi primera respuesta en StackOverflow: desde entonces quizás he aprendido que la concisión debe ser valorada :-).
Paul Stephenson el
A veces valoro un poco de verbosidad, como es el caso aquí donde usted describe la evolución de una solución (cómo llegó allí).
diodo verde
8

Provoca la evaluación de múltiples declaraciones, pero usa solo la última como valor resultante (valor, creo).

Entonces...

int f() { return 7; }
int g() { return 8; }

int x = (printf("assigning x"), f(), g() );

debería dar como resultado que x se establezca en 8.

Ben Collins
fuente
3

Como han dicho las respuestas anteriores, evalúa todas las declaraciones pero usa la última como el valor de la expresión. Personalmente, solo lo he encontrado útil en expresiones de bucle:

for (tmp=0, i = MAX; i > 0; i--)
DGentry
fuente
2

El único lugar que he visto que es útil es cuando escribes un bucle funky donde quieres hacer varias cosas en una de las expresiones (probablemente la expresión init o la expresión de bucle. Algo así como:

bool arraysAreMirrored(int a1[], int a2[], size_t size)
{
  size_t i1, i2;
  for(i1 = 0, i2 = size - 1; i1 < size; i1++, i2--)
  {
    if(a1[i1] != a2[i2])
    {
      return false;
    }
  }

  return true;
}

Disculpe si hay algún error de sintaxis o si mezclé algo que no es estricto C. No estoy argumentando que el operador es una buena forma, pero para eso podría usarlo. En el caso anterior, probablemente usaría un whilebucle en su lugar, por lo que las múltiples expresiones en init y loop serían más obvias. (E inicializaría i1 e i2 en línea en lugar de declarar y luego inicializar ... bla, bla, bla.)

Owen
fuente
Supongo que te refieres a i1 = 0, i2 = tamaño -1
frankster
-2

Estoy reviviendo esto simplemente para responder las preguntas de @Rajesh y @JeffMercado, que creo que son muy importantes, ya que este es uno de los principales éxitos del motor de búsqueda.

Tome el siguiente fragmento de código, por ejemplo

int i = (5,4,3,2,1);
int j;
j = 5,4,3,2,1;
printf("%d %d\n", i , j);

Imprimirá

1 5

El icaso se maneja como se explica en la mayoría de las respuestas. Todas las expresiones se evalúan en orden de izquierda a derecha, pero solo se asigna la última i. El resultado de la ( expresión ) is1`.

El jcaso sigue diferentes reglas de precedencia ya que ,tiene la precedencia de operador más baja. Debido a esas reglas, el compilador ve asignación de expresión, constante, constante ... . Las expresiones se evalúan nuevamente en orden de izquierda a derecha y sus efectos secundarios permanecen visibles, por lo tanto, jes el 5resultado dej = 5 .

Interesantemente, int j = 5,4,3,2,1;no está permitido por la especificación del idioma. Un inicializador espera una expresión de asignación, por lo ,que no se permite un operador directo .

Espero que esto ayude.

ViNi89
fuente