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í:
- 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.
- Si un operando es de tipo long double, el otro se convertirá a long double.
- Si un operando es de tipo double, el otro se convertirá a double.
- Si un operando es de tipo float, el otro se convertirá a float.
- Si un operando es de tipo unsigned long long, el otro se convertirá a unsigned long long.
- Si un operando es de tipo long long, el otro se convertirá a long long.
- Si un operando es de tipo unsigned long, el otro se convertirá a unsigned long.
- Si un operando es de tipo long, el otro se convertirá a long.
- Si un operando es de tipo unsigned int, el otro se convertirá a unsigned int.
- 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.
Ejemplos capítulos 8 y 9
Ejemplo 9.1
Volvamos al ejemplo del capítulo 1, aquél que sumaba dos más dos. Ahora podemos comprobar si el ordenador sabe realmente sumar, le pediremos que nos muestre el resultado:
// Este programa suma 2 + 2 y muestra el resultado // No me atrevo a firmarlo #include <iostream> using namespace std; int main() { int a; a = 2 + 2; cout << "2 + 2 = " << a << endl; return 0; }
Espero que tu ordenador fuera capaz de realizar este complicado cálculo, el resultado debe ser:
2 + 2 = 4
Nota:
Si estás compilando programas para consola en un compilador que trabaje en entorno Windows, probablemente no verás los resultados, esto es porque cuando el programa termina se cierra la ventana de consola automáticamente, como comentamos en el capítulo 7.
Hay varias opciones para evitar este inconveniente. Por ejemplo, ejecutar los programas desde una ventana de consola, o añadir líneas al código que detengan la ejecución del programa, como cin.get() o system("pause").
Otros IDEs, como Code::Blocks, no tienen ese inconveniente.
Ejemplo 9.2
Veamos un ejemplo algo más serio, hagamos un programa que muestre el alfabeto. Para complicarlo un poco más, debe imprimir dos líneas, la primera en mayúsculas y la segunda en minúsculas. Una pista, por si no sabes cómo se codifican los caracteres en el ordenador. A cada carácter le corresponde un número, conocido como código ASCII. Ya hemos hablado del ASCII de 256 y 128 caracteres, pero lo que interesa para este ejercicio es que las letras tienen códigos ASCII correlativos según el orden alfabético. Es decir, si al carácter 'A' le corresponde el código ASCII n, al carácter 'B' le corresponderá el n+1.
// Muestra el alfabeto de mayúsculas y minúsculas #include <iostream> using namespace std; int main() { char a; // Variable auxiliar para los bucles // El bucle de las mayúsculas lo haremos con un while a = 'A'; while(a <= 'Z') cout << a++; cout << endl; // Cambio de línea // El bucle de las minúsculas lo haremos con un for for(a = 'a'; a <= 'z'; a++) cout << a; cout << endl; // Cambio de línea return 0; }
Ejecutar este código en OnlineGDB.
Tal vez eches de menos algún carácter. Efectivamente la 'ñ' no sigue la norma del orden alfabético en ASCII, esto es porque el ASCII lo inventaron los anglosajones, y no se acordaron del español. De momento nos las apañaremos sin ella.
Ejemplo 9.3
Para este ejemplo queremos que se muestren cuatro líneas, la primera con el alfabeto, pero mostrando alternativamente las letras en mayúscula y minúscula, AbCdE... La segunda igual, pero cambiando mayúsculas por minúsculas, la tercera en grupos de dos, ABcdEFgh... y la cuarta igual pero cambiando mayúsculas por minúsculas.
Para este problema tendremos que echar mano de algunas funciones estándar, concretamente de toupper y tolower, declaradas en ctype.
También puedes consultar el apéndice sobre bibliotecas estándar en el apéndice C.
Piensa un poco sobre el modo de resolver el problema. Ahora te daré la solución.
Por supuesto, para cada problema existen cientos de soluciones posibles, en general, cuanto más complejo sea el problema más soluciones existirán, aunque hay problemas muy complejos que no tienen ninguna solución, en apariencia.
Bien, después de este paréntesis, vayamos con el problema. Almacenaremos el alfabeto en una cadena, no importa si almacenamos mayúsculas o minúsculas. Necesitaremos una cadena de 27 caracteres, 26 letras y el terminador de cadena.
Una vez tengamos la cadena le aplicaremos diferentes procedimientos para obtener las combinaciones del enunciado.
// Muestra el alfabeto de mayúsculas y minúsculas: // AbCdEfGhIjKlMnOpQrStUvWxYz // aBcDeFgHiJkLmNoPqRsTuVwXyZ // ABcdEFghIJklMNopQRstUVwxYZ // abCDefGHijKLmnOPqrSTuvWXyz #include <iostream> #include <cctype> using namespace std; int main() { char alfabeto[27]; // Cadena que contendrá el alfabeto int i; // variable auxiliar para los bucles // Aunque podríamos haber iniciado alfabeto directamente, // lo haremos con un bucle for(i = 0; i < 26; i++) alfabeto[i] = 'a' + i; alfabeto[26] = 0; // No olvidemos poner el fin de cadena // Aplicamos el primer procedimiento si la posición es // par convertimos el carácter a minúscula, si es impar // a mayúscula for(i = 0; alfabeto[i]; i++) if(i % 2) alfabeto[i] = tolower(alfabeto[i]); else alfabeto[i] = toupper(alfabeto[i]); cout << alfabeto << endl; // Mostrar resultado // Aplicamos el segundo procedimiento si el carácter era // una mayúscula lo cambiamos a minúscula, y viceversa for(i = 0; alfabeto[i]; i++) if(isupper(alfabeto[i])) alfabeto[i] = tolower(alfabeto[i]); else alfabeto[i] = toupper(alfabeto[i]); cout << alfabeto << endl; // Mostrar resultado // Aplicamos el tercer procedimiento, pondremos los dos // primeros caracteres directamente a mayúsculas, y // recorreremos el resto de la cadena, si el carácter // dos posiciones a la izquierda es mayúscula cambiamos // el carácter actual a minúscula, y viceversa alfabeto[0] = 'A'; alfabeto[1] = 'B'; for(i = 2; alfabeto[i]; i++) if(isupper(alfabeto[i-2])) alfabeto[i] = tolower(alfabeto[i]); else alfabeto[i] = toupper(alfabeto[i]); // Mostrar resultado: cout << alfabeto << endl; // El último procedimiento, es tan simple como aplicar // el segundo de nuevo for(i = 0; alfabeto[i]; i++) if(isupper(alfabeto[i])) alfabeto[i] = tolower(alfabeto[i]); alfabeto[i] = toupper(alfabeto[i]); // Mostrar resultado: cout << alfabeto << endl; return 0; }
Ejecutar este código en OnlineGDB.
Ejemplo 9.4
Bien, ahora veamos un ejemplo tradicional en todos los cursos de C++.
Se trata de leer caracteres desde el teclado y contar cuántos hay de cada tipo. Los tipos que deberemos contar serán: consonantes, vocales, dígitos, signos de puntuación, mayúsculas, minúsculas y espacios. Cada carácter puede pertenecer a uno o varios grupos. Los espacios son utilizados frecuentemente para contar palabras.
De nuevo tendremos que recurrir a funciones de estándar. En concreto la familia de macros is<conjunto>.
Para leer caracteres podemos usar la función getchar, perteneciente a stdio.
// Cuenta letras #include <iostream> #include <cstdio> #include <cctype> using namespace std; int main() { int consonantes = 0; int vocales = 0; int digitos = 0; int mayusculas = 0; int minusculas = 0; int espacios = 0; int puntuacion = 0; char c; // caracteres leídos desde el teclado cout << "Contaremos caracteres hasta que se pulse '&'" << endl; while((c = getchar()) != '&') { if(isdigit(c)) digitos++; else if(isspace(c)) espacios++; else if(ispunct(c)) puntuacion++; else if(isalpha(c)) { if(isupper(c)) mayusculas++; else minusculas++; switch(tolower(c)) { case 'a': case 'e': case 'i': case 'o': case 'u': vocales++; break; default: consonantes++; } } } cout << "Resultados:" << endl; cout << "Dígitos: " << digitos << endl; cout << "Espacios: " << espacios << endl; cout << "Puntuación: " << puntuacion << endl; cout << "Alfabéticos: " << mayusculas+minusculas << endl; cout << "Mayúsculas: " << mayusculas << endl; cout << "Minúsculas: " << minusculas << endl; cout << "Vocales: " << vocales << endl; cout << "Consonantes: " << consonantes << endl; cout << "Total: " << digitos + espacios + vocales + consonantes + puntuacion << endl; return 0; }
Ejecutar este código en OnlineGDB.