Pregunta

    

Esta pregunta ya tiene una respuesta aquí:

    
            
  •              ¿Qué hace el operador de coma?                                      9 respuestas                          
  •     
    

Lo ves usado en las declaraciones de bucle, pero es una sintaxis legal en cualquier parte. ¿Qué usos has encontrado para él en otro lugar, si alguno?

¿Fue útil?

Solución

El lenguaje C (así como C ++) es históricamente una mezcla de dos estilos de programación completamente diferentes, a los que uno puede referirse como " programación de instrucciones " y " programación de expresiones " ;. Como usted sabe, cada lenguaje de programación de procedimientos normalmente admite construcciones fundamentales como secuenciación y ramificación (consulte Programación estructurada ). Estas construcciones fundamentales están presentes en los lenguajes C / C ++ en dos formas: una para la programación de sentencias y otra para la programación de expresiones.

Por ejemplo, cuando escribe su programa en términos de declaraciones, puede usar una secuencia de declaraciones separadas por ; . Cuando quiera hacer alguna bifurcación, use las declaraciones if . También puede utilizar ciclos y otros tipos de declaraciones de transferencia de control.

En la programación de expresiones, las mismas construcciones también están disponibles para ti. Aquí es donde realmente entra en juego el operador , . Operator , en nada más que un separador de expresiones secuenciales en C, es decir, operator , en la programación de expresiones tiene el mismo rol que ; hace en la declaración programación. La ramificación en la programación de expresiones se realiza a través del operador ?: y, alternativamente, a través de las propiedades de evaluación de cortocircuito de los operadores & amp; & amp; y || . (Sin embargo, la programación de expresiones no tiene ciclos. Y para reemplazarlos con la recursión tendrías que aplicar la programación de sentencias).

Por ejemplo, el siguiente código

a = rand();
++a;
b = rand();
c = a + b / 2;
if (a < c - 5)
  d = a;
else
  d = b;

que es un ejemplo de la programación de sentencias tradicional, puede reescribirse en términos de programación de expresiones como

a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? d = a : d = b;

o como

a = rand(), ++a, b = rand(), c = a + b / 2, d = a < c - 5 ? a : b;

o

d = (a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? a : b);

o

a = rand(), ++a, b = rand(), c = a + b / 2, (a < c - 5 && (d = a, 1)) || (d = b);

No hace falta decir que, en la práctica, la programación de sentencias generalmente produce un código C / C ++ mucho más legible, por lo que normalmente usamos la programación de expresiones en cantidades muy bien medidas y restringidas. Pero en muchos casos resulta práctico. Y la línea divisoria entre lo que es aceptable y lo que no lo es es, en gran medida, una cuestión de preferencia personal y la capacidad de reconocer y leer expresiones idiomáticas establecidas.

Como nota adicional: el diseño mismo del lenguaje está obviamente adaptado a las declaraciones. Las declaraciones pueden invocar libremente expresiones, pero las expresiones no pueden invocar declaraciones (aparte de llamar a funciones predefinidas). Esta situación se modifica de una manera bastante interesante en el compilador GCC, que admite el llamado " expresiones de declaración " como una extensión (simétricas a " instrucciones de expresión " en el estándar C). " Expresiones de declaración " permite al usuario insertar directamente código basado en sentencias en expresiones, al igual que pueden insertar código basado en expresiones en sentencias en el estándar C.

Como otra nota adicional: en el lenguaje C ++, la programación basada en el functor desempeña un papel importante, que puede verse como otra forma de programación de expresiones. De acuerdo con las tendencias actuales en el diseño de C ++, podría considerarse preferible a la programación tradicional de sentencias en muchas situaciones.

Otros consejos

Creo que, en general, la coma de C no es un buen estilo de uso simplemente porque es muy fácil de ignorar, ya sea por otra persona que intenta leer / comprender / corregir su código, o usted mismo un mes más tarde. Fuera de las declaraciones de variables y de los bucles, por supuesto, donde es idiomático.

Puede usarlo, por ejemplo, para empaquetar varias declaraciones en un operador ternario (? :), ala:

int x = some_bool ? printf("WTF"), 5 : fprintf(stderr, "No, really, WTF"), 117;

pero mis dioses, ¿por qué?!? (Lo he visto usado de esta manera en código real, pero no tengo acceso a él para mostrarlo, lamentablemente)

Lo he visto usado en macros donde la macro pretende ser una función y quiere devolver un valor, pero primero debe hacer algún otro trabajo. Sin embargo, siempre es feo y, a menudo, parece un hack peligroso.

Ejemplo simplificado:

#define SomeMacro(A) ( DoWork(A), Permute(A) )

Aquí B = SomeMacro (A) " devuelve " el resultado de Permutar (A) y lo asigna a " B " ;.

Dos funciones de operador de coma asesino en C ++:

a) Leer de la secuencia hasta que se encuentre una cadena específica (ayuda a mantener el código DRY):

 while (cin >> str, str != "STOP") {
   //process str
 }

b) Escriba código complejo en inicializadores de constructor:

class X : public A {
  X() : A( (global_function(), global_result) ) {};
};

Tuve que usar una coma para depurar los bloqueos mutex para poner un mensaje antes el bloqueo comienza a esperar.

No pude encontrar el mensaje de registro en el cuerpo del constructor de bloqueo derivado, por lo que tuve que ponerlo en los argumentos del constructor de clase base usando: baseclass ((log (" mensaje "), actual_arg)) en la lista de inicialización. Tenga en cuenta el paréntesis adicional.

Aquí hay un extracto de las clases:

class NamedMutex : public boost::timed_mutex
{
public:
    ...

private:
    std::string name_ ;
};

void log( NamedMutex & ref__ , std::string const& name__ )
{
    LOG( name__ << " waits for " << ref__.name_ );
}

class NamedUniqueLock : public boost::unique_lock< NamedMutex >
{
public:

    NamedUniqueLock::NamedUniqueLock(
        NamedMutex & ref__ ,
        std::string const& name__ ,
        size_t const& nbmilliseconds )
    :
        boost::unique_lock< NamedMutex >( ( log( ref__ , name__ ) , ref__ ) ,
            boost::get_system_time() + boost::posix_time::milliseconds( nbmilliseconds ) ),
            ref_( ref__ ),
            name_( name__ )
    {
    }

  ....

};

La biblioteca Boost Assignment es un buen ejemplo de sobrecargar el operador de coma de una manera útil, legible. Por ejemplo:

using namespace boost::assign;

vector<int> v; 
v += 1,2,3,4,5,6,7,8,9;

Desde el estándar C:

  

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. (Un operador de coma no genera un lvalor).) Si se intenta modificar el resultado de un operador de coma o acceder a él después del siguiente punto de secuencia, el comportamiento no está definido.

En resumen, le permite especificar más de una expresión en la que C espera solo una. Pero en la práctica se usa principalmente en bucles.

Tenga en cuenta que:

int a, b, c;

NO es el operador de coma, es una lista de declaradores.

A veces se usa en macros, como las macros de depuración como esta:

#define malloc(size) (printf("malloc(%d)\n", (int)(size)), malloc((size)))

(Pero mira este horrible fallo , por su cuenta, por lo que puede pasar cuando se excede.)

Pero a menos que realmente lo necesite, o si está seguro de que hace que el código sea más fácil de leer y mantener, recomendaría no usar el operador de coma.

Puede sobrecargarlo (siempre que esta pregunta tenga una etiqueta " C ++ "). He visto algo de código, donde se usaba una coma sobrecargada para generar matrices. O vectores, no recuerdo exactamente. ¿No es bonito (aunque un poco confuso):

MyVector foo = 2, 3, 4, 5, 6;

Fuera de un bucle for, e incluso existe un olor a código, el único lugar que he visto como un buen uso para el operador de coma es como parte de una eliminación:

 delete p, p = 0;

El único valor sobre la alternativa es que puede copiar / pegar accidentalmente solo la mitad de esta operación si está en dos líneas.

También me gusta porque si lo haces por costumbre, nunca olvidarás la asignación de cero. (Por supuesto, ¿por qué p no está dentro de algún tipo de auto_ptr, smart_ptr, shared_ptr, etc. wrapper es una pregunta diferente).

Dada la cita de @Nicolas Goy del estándar, entonces suena como si pudieras escribir una sola línea para bucles como:

int a, b, c;
for(a = 0, b = 10; c += 2*a+b, a <= b; a++, b--);
printf("%d", c);

Pero, Dios mío, hombre, ¿realmente quieres que tu código C más sea más oscuro de esta manera?

En general, evito usar el operador de coma porque solo hace que el código sea menos legible. En casi todos los casos, sería más simple y claro hacer solo dos afirmaciones. Me gusta:

foo=bar*2, plugh=hoo+7;

no ofrece una ventaja clara sobre: ??

foo=bar*2;
plugh=hoo+7;

El único lugar, además de los bucles, donde lo he usado en if / else se construye, como:

if (a==1)
... do something ...
else if (function_with_side_effects_including_setting_b(), b==2)
... do something that relies on the side effects ...

Podría poner la función antes de la IF, pero si la función tarda mucho tiempo en ejecutarse, puede evitar hacerlo si no es necesario, y si la función no se debe realizar a menos que a! = 1, entonces Esa no es una opción. La alternativa es anidar el IF es una capa extra. Eso es en realidad lo que suelo hacer porque el código anterior es un poco críptico. Pero lo he hecho a la manera de coma de vez en cuando porque el anidamiento también es críptico.

Es muy útil para agregar comentarios a las macros de ASSERT :

ASSERT(("This value must be true.", x));

Dado que la mayoría de las macros de estilo de afirmación generarán el texto completo de su argumento, esto agrega un poco más de información útil a la afirmación.

A menudo lo uso para ejecutar una función de inicialización estática en algunos archivos cpp, para evitar problemas de initalización perezosa con singletons clásicos:

void* s_static_pointer = 0;

void init() {
    configureLib(); 
    s_static_pointer = calculateFancyStuff(x,y,z);
    regptr(s_static_pointer);
}

bool s_init = init(), true; // just run init() before anything else

Foo::Foo() {
  s_static_pointer->doStuff(); // works properly
}

Para mí, el único caso realmente útil con comas en C es usarlas para realizar algo condicionalmente.

  if (something) dothis(), dothat(), x++;

esto es equivalente a

  if (something) { dothis(); dothat(); x++; }

No se trata de " escribir menos " ;, a veces se ve muy claro.

También los bucles son así:

while(true) x++, y += 5;

Por supuesto, ambos solo pueden ser útiles cuando la parte condicional o la parte ejecutable del bucle es bastante pequeña, dos o tres operaciones.

La única vez que he visto el operador , usado fuera de un for fue para realizar una evaluación en una declaración ternaria. Fue hace mucho tiempo, así que no puedo recordar la declaración exacta, pero fue algo así como:

int ans = isRunning() ? total += 10, newAnswer(total) : 0;

Obviamente, ninguna persona sensata escribiría un código como este, pero el autor era un genio malvado que construía declaraciones c basadas en el código del ensamblador que generaban, no legible. Por ejemplo, a veces usaba bucles en lugar de declaraciones if porque prefería el ensamblador que generaba.

Su código fue muy rápido pero no se puede mantener, me alegro de no tener que seguir trabajando con él.

Lo he usado para que una macro " asigne un valor de cualquier tipo a un búfer de salida apuntado por un char *, y luego aumente el puntero en el número requerido de bytes " ;, así:

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

El uso del operador de coma significa que la macro se puede usar en expresiones o en declaraciones como se desee:

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

Redujo algunos tipos de escritura repetitiva, pero debes tener cuidado de que no se vuelva demasiado ilegible.

Consulte mi versión demasiado larga de esta respuesta aquí .

Puede ser útil para " código golf " ;:

Code Golf: Playing Cubes

El , en si (i > 0) t = i, i = 0; guarda dos caracteres.

qemu tiene algún código que utiliza el operador de coma dentro de la parte condicional de un bucle for (consulte QTAILQ_FOREACH_SAFE en qemu-queue.h). Lo que hicieron se reduce a lo siguiente:

#include <stdio.h>

int main( int argc, char* argv[] ){
  int x = 0, y = 0;

  for( x = 0; x < 3 && (y = x+1,1); x = y ){
    printf( "%d, %d\n", x, y );
  }

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

  for( x = 0, y = x+1; x < 3; x = y, y = x+1 ){
    printf( "%d, %d\n", x, y );
  }

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

... con el siguiente resultado:

0, 1
1, 2
2, 3

3, 3

0, 1
1, 2
2, 3

3, 4

La primera versión de este bucle tiene los siguientes efectos:

  • Evita hacer dos asignaciones, por lo que se reducen las posibilidades de que el código pierda la sincronización
  • Como usa & amp; & amp; , la asignación no se evalúa después de la última iteración
  • Dado que la asignación no se evalúa, no intentará quitar la referencia al siguiente elemento de la cola cuando esté al final (en el código de qemu, no en el código anterior).
  • Dentro del bucle, tienes acceso al elemento actual y al siguiente

Lo encontré en la inicialización de matriz:

En C, ¿qué sucede exactamente si uso () para inicializar una matriz de doble dimensión en lugar de {}?

Cuando inicializo una matriz a [] [] :

int a[2][5]={(8,9,7,67,11),(7,8,9,199,89)};

y luego mostrar los elementos de la matriz.

me sale:

11 89 0 0 0 
0 0 0 0 0
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top