Conversión entre tipos de datos

El hecho de que C++ sea un lenguaje estáticamente tipado, significa que los tipos asignados a las variables se comprueban en tiempo de compilación.

Así, el compilador verifica que las operaciones que el programador ha escrito en el código fuente están soportadas por el lenguaje. Si existe alguna incompatibilidad, el compilador avisará con un mensaje de error.

Como acabamos de ver, difícilmente el compilador podría realizar esta tarea si previamente no hemos declarado los tipos de las variables antes de ser utilizadas.

No obstante, los lenguajes estáticamente tipados sí que permiten mezclar tipos de dato en las expresiones a pesar de que sus representaciones internas sean diferentes.

Para trabajar de forma cómoda con estas situaciones, los lenguajes permiten que el programador adopte dos tipos de decisión:

  • Conversión implícita: El compilador se encarga de forma automática de adaptar las representaciones para poder efectuar los cálculos de forma correcta.

  • Conversión explícita: El programador escribe explícitamente en el código fuente cómo quiere que se haga la conversión entre las representaciones.

Conversión implícita

Inicialización directa y asignaciones

C++ evalúa la expresión a la derecha del operador = y el resultado se convierte implícitamente al tipo de dato situado a su izquierda.

  • La conversión de una expresión con un valor de tipo real a una variable de tipo entero se trunca al entero resultante de eliminar la parte fraccionaria.

    int r = 48.1;  // r se almacenará como entero con valor 48
    int s = -28.1; // s se almacenará como entero con valor -28
    
  • La conversión de una expresión con un valor de tipo entero a una variable de tipo real se realizará según la norma IEEE 754. Puede conllevar la pérdida de precisión en números grandes para los float, no así para los double, que tienen suficientes bits (una razón más para usar siempre double).

    float x = 19999999;  // x se almacenara como real 20000000
    double y = 19999999; // y se almacenara como real 19999999
    
  • Una expresión no boolena solo se asigna a false si el valor es 0. En caso contrario, el resultado de la asignación es true.

    bool b = 48.1; // b es cierto
    bool b{1};     // OK: inicialización uniforme sólo valores 0 (false) y 1 (true)
    bool b{48.1};  // ERROR: inicialización uniforme
    

Edita, compila y ejecuta el código

Operaciones en expresiones

Cuando aparece una expresión con distintos tipos de datos mezclados, C++ promueve todos los tipos a un único tipo, el que tiene más jerarquía de entre los presentes. De esta forma, la operación (aritmética o lógica) se realiza según la representación del tipo de mayor jerarquía.

Para los tipos de datos que vamos a manejar en la asignatura, la jerarquía de menor a mayor en C++ es:

bool < int

char < int < float < double

Véase el siguiente ejemplo.

int x = 1;
double y = 19999998;
// x + y es una expresión RValue que se evalúa como double.
// Al ejecutarse el programa, su valor será 19999999.
float w = x + y;   // w se almacena con valor float  20000000
double z = x + y;  //z se almacena con valor double 19999999

Edita, compila y ejecuta el código

Conversión explícita

En ocasiones el programador necesita forzar la conversión.

Imaginemos un problema en el cual deseamos calcular la pendiente media de un puerto de montaña. Para la longitud del puerto y para la altitud se han elegido variables enteras que representan metros.

La fórmula será \(pendiente = altitud/longitud\).

Un programa ejemplo podría ser el siguiente:

#include <iostream>
using namespace std;
int main()
{
   int longitud = 18342;
   int altitud = 1123; //Respecto a la base del puerto

   double pendiente = altitud/longitud;

   cout << "La pendiente es " << pendiente << endl;
}

Edita, compila y ejecuta el código

Quizás para muchos sea una sorpresa que el resultado para la pendiente sea 0, pero es totalmente lógico.

Las expresiones a la derecha de una asignación = se evalúan antes de realizarse la asignación, almacenándose en un registro interno. En el ejemplo, la operación altitud/longitud es una expresión que involucra 2 variables tipo int y puesto que el resultado real es 0.xxxx se trunca a 0. Y es este valor 0 el que se asigna a la variable pendiente.

Un programa para obtener el valor correcto sustituiría la línea de cálculo de la pendiente por la siguiente:

double pendiente = static_cast<double>(altitud)/longitud;
// double pendiente = double(altitud)/longitud;  //Estilo C++ antiguo
// double pendiente = (double)altitud/longitud;  //Estilo C

Edita, compila y ejecuta el código

La conversión forzada de tipos static_cast<tipo_de_dato>(expresion) convierte la representación interna de expresion a la representación marcada por tipo_de_dato.

En nuestro ejemplo, forzamos a que la representación interna de la variable altitud sea double. De esta forma, la expresión altitud/longitud se convertirá implícitamente a double y obtendremos el resultado correcto.

Existen otras alternativas para realizar conversiones forzadas tales como:

  • double pendiente=double(altitud)/longitud;

  • double pendiente=(double)altitud/longitud;

Aunque para el ejemplo que nos ocupa todas las opciones son válidas y compilarán perfectamente, se recomienda usar static_cast.

La conversión forzada:

double pendiente = static_cast<double>(altitud/longitud);

no funciona correctamente. ¿Por qué?

Por otro lado, en la medida de lo posible, debemos evitar el uso de las conversiones forzadas. En el ejemplo hubiese bastado con declarar:

double longitud = 18342;
double altitud = 1123;

Peligro

Las conversiones descontroladas conllevan graves riesgos. Un ejemplo clásico es el ocurrido con el cohete Ariane, que se produjo por una conversión desde un número en coma flotante de 64 bits (que era mayor de 32768) a un número entero de 16 bits.

../../_images/ariane.png