9 Conversión de tipos

Quizás te hayas preguntado qué pasa cuando escribimos expresiones numéricas en las que no todos los operandos son del mismo tipo. Por ejemplo:

char n;
int a, b, c, d; 
float r, s, t; 
... 
a = 10; 
b = 100; 
r = 1000; 
c = a + b; 
s = r + a; 
d = r + b; 
d = n + a + r; 
t = r + a - s + c;
...

En estos casos, cuando los operandos de cada operación binaria asociados a un operador son de distinto tipo, el compilador los convierte a un tipo común. Existen reglas que rigen estas conversiones, y aunque pueden cambiar ligeramente de un compilador a otro, en general serán más o menos así:

  1. Cualquier tipo entero pequeño como char o short es convertido a int o unsigned int. En este punto cualquier pareja de operandos será int (con o sin signo), long, long long, double, float o long double.
  2. Si un operando es de tipo long double, el otro se convertirá a long double.
  3. Si un operando es de tipo double, el otro se convertirá a double.
  4. Si un operando es de tipo float, el otro se convertirá a float.
  5. Si un operando es de tipo unsigned long long, el otro se convertirá a unsigned long long.
  6. Si un operando es de tipo long long, el otro se convertirá a long long.
  7. Si un operando es de tipo unsigned long, el otro se convertirá a unsigned long.
  8. Si un operando es de tipo long, el otro se convertirá a long.
  9. Si un operando es de tipo unsigned int, el otro se convertirá a unsigned int.
  10. Llegados a este punto ambos operandos son int.

Veamos ahora el ejemplo:

c = a + b; caso 10, ambas son int.

s = r + a; caso 4, a se convierte a float.

d = r + b; caso 4, b se convierte a float.

d = n + a + r; caso 1, n se convierte a int, la operación resultante corresponde al caso 4, el resultado (n+a) se convierte a float.

t = r + a - s + c; caso 4, a se convierte a float, caso 4 (r+a) y s son float, caso 4, c se convierte a float.

También se aplica conversión de tipos en las asignaciones, cuando la variable receptora es de distinto tipo que el resultado de la expresión de la derecha.

En el caso de las asignaciones, cuando la conversión no implica pérdida de precisión, se aplican las mismas reglas que para los operandos, estas conversiones se conocen también como promoción de tipos. Cuando hay pérdida de precisión, las conversiones se conocen como democión de tipos. El compilador normalmente emite un aviso o warning, cuando se hace una democión implícita, es decir cuando hay una democión automática.

En el caso de los ejemplos 3 y 4, es eso precisamente lo que ocurre, ya que estamos asignando expresiones de tipo float a variables de tipo int.

Conversiones a bool

En C++ podemos hablar de otro tipo de conversión de tipo implícita, que se realiza cuando se usa cualquier expresión entera en una condición, y más generalmente, cuando se usa cualquier expresión donde se espera una expresión booleana.

El dominio del tipo bool es muy limitado, ya que sólo puede tomar dos valores: true y false. Por convenio se considera que el valor cero es false, y cualquier otro valor entero es true.

Por lo tanto, hay una conversión implícita entre cualquier entero y el tipo bool, y si añadimos esta regla a las explicadas antes, cualquier valor double, long double, float o cualquiera de los enteros, incluso char, se puede convertir a bool.

Esto nos permite usar condiciones abreviadas en sentencias if, for, while o do..while, cuando el valor a comparar es cero.

Por ejemplo, las siguientes expresiones booleanas son equivalentes:

0 == x equivale a !x.

0 != x equivale a x.

En el primer caso, usamos el operador == para comparar el valor de x con cero, pero al aplicar el operador ! directamente a x obligamos al compilador a reinterpretar su valor como un bool, de modo que si x vale 0 el valor es false, y !false es true. De forma simétrica, si x es distinto de cero, se interpretará como true, y !true es false. El resultado es el mismo que usando la expresión 0 == x.

En el segundo caso pasa algo análogo. Ahora usamos el operador != para comparar el valor de x también con cero, pero ahora interpretamos directamente x como bool, de modo que si x vale 0 el valor es false, y si x es distinto de cero, se interpretará como true. El resultado es el mismo que usando la expresión 0 != x.

No está claro cual de las dos opciones es más eficaz, a la hora de compilar el programa. Probablemente, la segunda requiera menos instrucciones del procesador, ya que existen instrucciones de ensamblador específicas para comparar un entero con cero. Del otro modo estaremos comparando con un valor literal, y salvo que el compilador optimice este código, generalmente se requerirán más instrucciones de este modo.

Añadir que los ejemplos anteriores funcionan aunque el tipo de x no sea un entero. Si se trata de un valor en coma flotante se realizará una conversión implícita a entero antes de evaluar la expresión.

Casting: conversiones explícitas de tipo

Para eludir estos avisos del compilador se usa el casting, o conversión explícita.

Nota: de nuevo nos encontramos ante un término que suele aparecer en inglés en los documentos. Se podría traducir como amoldar o moldear, pero no se hace. También es un término que se usa en cine y teatro, y se aplica al proceso de asignar papeles a los actores. La idea es análoga, en el caso de las variables, asignamos papeles a los valores, según sus características. Por ejemplo, para convertir el valor en coma flotante 14.232 a entero se usa el valor 14, podríamos decir que 14 está haciendo el papel de 14.232 en la representación. O que se ajusta a un molde o troquel: lo que sobra se elimina.

En general, el uso de casting es obligatorio cuando se hacen asignaciones, o cuando se pasan argumentos a funciones con pérdida de precisión. En el caso de los argumentos pasados a funciones es también muy recomendable aunque no haya pérdida de precisión. Eliminar los avisos del compilador demostrará que sabemos lo que hacemos con nuestras variables, aún cuando estemos haciendo conversiones de tipo extrañas.

En C++ hay varios tipos diferentes de casting, pero de momento veremos sólo el que existe también en C.

Un casting tiene una de las siguientes sintaxis:

(<nombre de tipo>)<expresión>
<nombre de tipo>(<expresión>) 

Esta última es conocida como notación funcional, ya que tiene la forma de una llamada a función.

En el ejemplo anterior, las líneas 3 y 4 quedarían:

d = (int)(r + b);
d = (int)(n + a + r);

O bien:

d = int(r + b);
d = int(n + a + r);

Hacer un casting indica que sabemos que el resultado de estas operaciones no es un int, que la variable receptora sí lo es, y que lo que hacemos lo estamos haciendo a propósito. Veremos más adelante, cuando hablemos de punteros, más situaciones donde también es obligatorio el uso de casting.

Comentarios de los usuarios (3)

Anónimo
2012-12-24 04:31:43

5. Si un operando es de tipo unsigned long long, el otro se convertirá a unsigned long long.

¿Quiere decir que si sumo un long long con un unsigned long long, el long long promociona a unsigned long?. Si es asi, entonces un long long de valor negativo pasaria a ser positivo ¿no? y en consecuencia no daria un resultado esperado la suma debiendo dar un warning ¿no?

Milton Parra
2014-04-08 17:44:00

En alguna parte de esta página leí que se puede cambiar el formato predeterminado de COUT. Me explico: de manera predeterminada 12/10 muestra 1, pero se puede modificar para que muestre 1.2 que es el correcto. Además cómo puedo hacer para consultar algo que no recuerdo dónde lo leí?.

Steven R. Davidson
2014-04-08 19:23:09

Hola Milton,

No puedes conseguir 1.2 al hacer 12 / 10, ya que son operandos enteros. Sí puedes hacer 12.0 / 10.0 para conseguir 1.2 (de tipo 'double'), que por defecto 'cout <<' debería mostrar "1.2". Puedes cambiar el formato de la impresión de los números de coma flotante cambiando los banderines (o "flags") internos invocando 'setf()'. Por ejemplo,

cout.setf( ios::scientific, ios::floatfield );
cout << 12.0 / 10.0 << endl;
cout.setf( ios::fixed, ios::floatfield );
cout << 12.0 / 10.0 << endl;

Aparecerá en pantalla:

1.200000e+00

1.200000

O podemos deshabilitar estos formatos:

cout.unsetf( ios::scientific | ios::fixed );
cout << 12.0 / 10.0 << endl;

Aparecerá: 1.2

También puedes cambiar la precisión, que por defecto es 6; por ejemplo,

cout.setf( ios::scientific, ios::floatfield );
cout.precision( 4 );
cout << 12.0 / 10.0 << endl;

Aparecerá: 1.2000e+00

Puedes consultar el apéndice E acerca de las bibliotecas directamente involucradas con los canales (o flujos): http://c.conclase.net/curso/index.php?cap=904 Puedes dirigirte directamente a 'ios' yendo a: http://c.conclase.net/curso/index.php?cap=904b#APD_ios

Espero que esto te sirva.

Steven