Revista Tecnología

Operador coma. Cómo incorporarlo a nuestro día a día con muchos ejemplos en C

Publicado el 05 diciembre 2016 por Gaspar Fernández Moreno @gaspar_fm

Operador coma. Cómo incorporarlo nuestro muchos ejemplosOperador coma. Cómo incorporarlo a nuestro día a día con muchos ejemplos en C
Seguro que lo has visto cientos de veces por ahí, pasando desapercibido en multitud de programas hechos en C. Es más, incluso el primer día que empezaste a ver programación os visteis las caras y casi no te diste cuenta. Incluso puede que lo hayas utilizado sin siquiera ser consciente de lo que hace en realidad.

Otros muchos sí que sabréis de qué va este operador, y me gustaría que los que sí sabéis de qué va, sugirierais más ejemplos en los comentarios.

Para situarnos

Seguramente hayas utilizado la coma en varias situaciones como por ejemplo la creación de variables del mismo tipo:

O en declaraciones y llamadas a funciones:

Pero este símbolo tiene muchos más usos en C, C++ y otros lenguajes derivados (Java, Perl o Javascript, por ejemplo) en los que podemos utilizarlo como algo más que un simple separador y con el que podemos ahorrar muchas líneas de código.

Para probar estos programas

Si queréis probar todos los ejemplos que muestro en el post, no tenéis más que copiar y pegar en vuestro editor favorito y compilar con vuestro compilador preferido. Yo utilizo GCC (no hace falta ninguna biblioteca ni nada):

¿ Para qué vale el operador coma ?

Este operador se utiliza generalmente para evaluar varias expresiones dentro de la misma línea de código. Es, además, el operador con menor preferencia de todos. Esto último quiere decir que asignaciones, operaciones matemáticas, lógicas, direccionamiento, ternarios, etc; se ejecutarán antes que la coma y esto nos puede llevar a pensar que no funciona bien, pero tiene su lógica. Además, una vez que hemos ejecutado todas las expresiones que están separadas por coma de izquierda a derecha, devolveremos como resultado el valor de la última (la que más a la derecha estará).

Como todos los elementos de un lenguaje, pueden ser utilizados con buenos o males fines. Por un lado, podemos hacer un código muy críptico con este operador, y hacer que el próximo que se siente a analizar nuestro código lo pase mal para entender qué estamos haciendo, y todo para ahorrar unas cuántas líneas de código. Aunque también puede utilizarse con buenos fines, y hacer que todo se entienda mucho mejor.

Como consejo para beneficiarnos en la legibilidad y el mantenimiento del código, debemos utilizar el operador coma para ejecutar expresiones que estén relacionadas, incluso que para ejecutar las expresiones de la derecha sea necesario que las de la izquierda se hayan ejecutado antes, ya que si vamos a ejecutar cosas que no tienen nada que ver, es mejor separarlas con punto y coma (;) y ponerlas en otra línea, por el bien de los que vayan a leer nuestro código en el futuro.

Aunque la mejor forma de conocer el funcionamiento es este operador es viendo ejemplos y jugando con ellos. Así que vamos a ver una buena selección de ejemplos para empezar a poner en práctica ya. He de decir que la coma siempre hace lo mismo, todos estos son diferentes escenarios donde podemos hacerlo, no estamos inventando nada en cada uno de los ejemplos.

La coma en asignaciones

Este código puede parecer (y de hecho lo es) una tontería y no sirve para nada. Pero nos ayuda a comprender la preferencia de los operadores. En realidad, si hacemos un programa y ponemos un 7;, éste compilará bien, aunque será cosa del compilador ignorar lo que hemos hecho, ya que no estamos haciendo nada con ese valor. En realidad, en este código estamos evaluando las expresiones:

Por lo que el resultado será:

¿No dije yo antes que el valor devuelto era el de la derecha? Y, ¿el de la derecha no es el 4? Sí, pero si vemos las expresiones, la primera es a=1, por lo que se realiza dicha asignación. Sería distinto decir lo siguiente:

Esto sí que devolverá 4

Modificando los valores dentro de la igualación

Podemos intentar cosas como:

Es decir, aqui, hemos cogido a y la hemos multiplicado por sí misma, b también, y luego en c hemos metido a y b (que ahora son los cuadrados de los originales). Como resultado, hemos modificado las tres variables.

Intercambiando valores en una sola línea

Tenemos dos valores (a y b) y queremos que b sea a y a sea b. ( Podemos ver algunas técnicas aquí). Aunque esto podemos resumirlo en una línea de esta manera:

Necesitaríamos una variable temporal, declarada (temp), pero lo demás sería ejecutar las expresiones:

En un orden determinado y con una preferencia determinada.

Un ejemplo para explotarnos la cabeza

Veamos ahora:

¿Cuánto debe valer a y b? La lógica nos diría que a y b valen lo mismo, 4. Pero no es así. A termina valiendo 4, es cierto, porque inicialmente vale 1 y lo incrementamos 3 veces (1 + 1 + 1 + 1 = 4), bien. Pero ¿b? En realidad, las expresiones se evalúan todas, pero a++ en realidad es un post-incremento, esto es, que primero devolverá el valor y luego incrementará, por lo que antes del tercer incremento a vale 4 y es lo que llega a b. Después de esto se incrementará.

Operación pop_first() en listas enlazadas

Una de las posibles operaciones es la extracción del primer elemento de la lista. Es decir, devolvemos el valor del primer elemento y lo quitamos al mismo tiempo de la lista. Imaginemos que nuestra lista está formada por (TNodo)s y éstos tienen un TDato (que podrá ser un entero, una cadena, un struct, etc):

Cálculo de salarios

Imaginémonos que vamos a calcular el salario de un empleado. Éste es el salario base + 0.1 * años de antigüedad * salario base + bonificación. Eso sí, cada una de las variables (salario base, antigüedad y bonificación debe obtenerse con llamadas a varias funciones), podríamos hacer lo siguiente:

Otro ejemplo curioso más

#include

j = 10;
i = (j++, j+100, 999+j);

printf ("i=%d\nj=%d\n", i, j);
return 0;
}
¿ Y el resultado ?

Esto es así porque el j++ se ejecuta (por lo que j vale 11 ya. La segunta expresión j+100 no hace nada, se ejecuta, pero no se guarda en ningún lado (seguramente un compilador listo la ignore), la última expresión 999+j será la que de verdad se guarde en i, por tanto i=999+11 = 1010.

Resolviendo una ecuación de segundo grado

En este código vemos cómo para calcular la primera solución, evaluamos varias expresiones seguidas:

Y a la variable x1 le asignamos este último valor. En este caso, vemos que las tres expresiones deben ejecutarse en este orden ya que hay dependencias de unas sobre otras. Si cambiamos el orden puede ser fatal y no se ejecutará bien nuestro código.
Para compilar esto, se debe incluir la biblioteca matemática (en gcc es utilizando -lm).

Inicializando estructuras

Este ejemplo está pensado como algo más complejo. Imaginemos que hemos creado una lista enlazada, o una lectura desde archivo (primero tenemos que abrir el archivo antes de leer). Es decir, antes de poder obtener el valor, tenemos que llamar a una función:

Es más, fijaos que inicializa() es de tipo void y no devuelve ningún valor. El compilador podía quejarse al querer dar ese valor a una variable de tipo entero, pero como en realidad el valor que se le da es el de *a, no hay ningún problema.

O con ficheros:

Hay que tener en cuenta que esto son sólo ejemplos, en la práctica puede que todo no sea tan fantástico y debamos hacer comprobaciones de error. Por ejemplo, en el ejemplo anterior (de ficheros), sería más conveniente utilizar & en lugar de , así cuando falla la expresión de más a la izquierda, no se ejecutan las siguientes.

Comas dentro de un bucle for

Muchos de los que empiezan a programar en C, separan las expresiones del bucle for con comas, cuando en realidad es con punto y coma. Es decir:

Pero utilizar comas aquí tiene utilidades bien distintas, por ejemplo cuando intervienen varias variables en el bucle, cuando debemos inicializar dos variables, en lugar de inicializar una variable y luego hacer el for, podemos hacer:

Para complicar esto, podemos contar 20 con dos variables, una desde 0 a 19 y otra desde 19 hasta 0:

Comas dentro de un bucle while

Vamos, con otro pequeño ejemplo. Un típico ejercicio de adivinar un número:

Que podemos escribir de la siguiente forma:

En este caso, dentro del while, hemos incluido varias expresiones:

  • printf ("Introduce un número: ")
  • scanf("%d", &input)
  • input!=numero

Siendo la última de éstas la que en realidad sirve como condición del while.

Leyendo un archivo (Unix)

Una lectura de fichero utilizando la biblioteca (unistd), podemos escribirla así:

Aunque podemos escribirla también así, utilizando el operador coma:

Pero, por ejemplo, si queremos que el bucle pare cuando encontremos un carácter punto y coma ';', podemos hacer esto:

Comas en condicionales

En este caso, estaremos evaluando alguna expresión o ejecutando una función por ejemplo, antes de preguntar por la condición. Por ejemplo:

Aunque es verdad que el condicional también podía haberse expresado como:

evitando así repetir la variable. Aunque con la coma podríamos incluir más expresiones para ejecutar, incluyendo operaciones y varias asignaciones:

En este ejemplo, podemos ver cómo evaluar si el usuario de una tienda online puede optar por financiación de su pedido. Para ello, utilizamos la función calcula_coste_pedido, a la que le pasamos un struct (por ejemplo) con información del pedido y calcula su precio total. Luego la variable financiacion (que es otro struct) se rellenará con datos gracias a calcula_financiacion() a la que le pasamos el precio y el usuario (nuestra tienda ofrece condiciones especiales a ciertos usuarios) y luego devolvemos financiacion.ok (un campo de nuestro struct).

Dentro de un return

Utilizar este operador dentro de un return es de los usos más comunes, y es que muchas veces, antes de salir de una función debemos realizar pequeñas tareas, llamar a otras funciones, liberar memoria y cosas del estilo.
En este ejemplo, vamos a utilizar strtok(), una función conocida por arruinar las cadenas originales que le pasamos, es decir, cuando utilizamos strtok() la cadena original se modifica. Por tanto, si queremos que ésta no cambie, debemos hacer una copia y aplicar strtok() a la cadena copiada, luego podremos hacer lo que queramos con ella:

Como vemos, en el mismo return de la función cuenta_palabras(), hacemos free(copia) para liberar memoria de la cadena copiada y luego devolvemos tokens. Podíamos hacerlo en dos líneas, pero lo hacemos en una. Es más free() es void.

Para evitar tener que abrir bloques

Esto es más pereza que otra cosa. Mientras en varios manuales de estilo (depende de las aplicaciones o del grupo que vaya a programar) se recomienda abrir y cerrar llaves siempre que estemos ante un bloque aunque sólo sea de una línea (cosa que en C es opcional), otros prefieren no usar llaves si no es necesario para así evitar que el número de líneas crezca.
Aunque hay veces que vamos a realizar acciones comunes, y podemos pensar que son un poco tontas, como por ejemplo mostrar un mensaje de error en pantalla y devolver un código (de error):

Si miramos la función analiza_datos(), vemos que en lugar de abrir llaves y poner un printf() y return x lo ponemos todo en la misma línea.

Llamadas a funciones

Bueno, llegamos a un punto peliagudo, porque tal vez tomemos las cosas muy a la ligera en ocasiones. Tenemos que tener especial cuidado en este punto, cuando toque pasar valores a funciones y queramos meter el operador coma para modificar ciertos valores. Lo que está claro es que cuando vayamos a utilizar el operador, debemos poner las expresiones entre paréntesis , porque de otro modo la coma actuará como separador. Por ejemplo si tengo esta función:

No puedo hacer lo siguiente cuando vaya a llamarla:

Porque estaré diciendo que hay tres argumentos, y la función sólo admite dos.

Lo que sí podemos hacer

En este caso:

Si hemos estado atentos no habrá sido muy difícil intuir el resultado.

Cuidado con este caso!!

Aunque, ¿qué pasaría si utilizo a en todos los valores?

Podemos pensar que se llamará a la función con los valores 3 y 6, pero depende del compilador, porque los dos argumentos, al fin y al cabo son a. Si el compilador, a medida que va haciendo las operaciones va recopilando los argumentos, sí sucederá así, se llamará a la función con 3 y 6. Pero si por el contrario primero se hacen las operaciones, y luego se van enviando los resultados como los diferentes argumentos, terminaremos enviando dos 6, ya que primero se hace el a+=3 y luego se llama a mi_funcion(a, a). Es cierto que la coma tiene la menor preferencia posible, pero cuando encontramos la coma (separadora de argumentos) normalmente estaremos hablando de cosas distintas, por lo que contarían como instrucciones aparte y en cierto modo se reinician las preferencias.

¿Se te ocurren más ejemplos?

Si tienes más ejemplos, o alguna cuestión relacionada, ¡no dudes en poner un comentario!

Foto: Providence Doucet

También podría interesarte...


Volver a la Portada de Logo Paperblog