Saltar al contenido

Tipos de datos, variables y constantes

Dónde guarda un programa los datos con los que trabaja, qué clases de dato existen, en qué se diferencia una constante de una variable, qué significa que un lenguaje tenga tipado fuerte o débil y estático o dinámico, y hasta dónde llega el ámbito de una variable.

La variable: una caja con etiqueta

Un programa necesita guardar datos mientras trabaja: la nota que acaba de calcular, cuántas preguntas lleva corregidas, el nombre del alumno. Para eso están las variables. La imagen que mejor funciona es la de una caja con una etiqueta pegada: la etiqueta es el nombre de la variable y dentro de la caja está el dato. Cuando el programa necesita ese dato, no dice «lo que hay en la posición de memoria tal»: dice el nombre de la etiqueta y ya está.

Con una variable se hacen tres cosas: declararla (crear la caja y ponerle etiqueta), asignarle un valor (meter algo dentro) y consultarla (mirar qué hay). Declarar y asignar por primera vez suele hacerse en la misma línea, y a eso se le llama inicializar.

pseudocodigo
entero aciertos = 34        // declara la caja «aciertos» y mete un 34
entero fallos                // declara la caja «fallos», todavia vacia

fallos = 16                  // ahora si: mete un 16 en «fallos»
aciertos = aciertos + 1      // saca el 34, le suma 1, y vuelve a meter 35

escribir(aciertos)           // muestra por pantalla: 35
  • Línea 1: «entero» dice qué clase de dato va a guardar la caja, «aciertos» es la etiqueta y el 34 es el valor inicial.
  • Línea 2: la caja existe, tiene nombre y tipo, pero todavía no hay nada dentro. En muchos lenguajes leerla ahora sería un error.
  • Línea 4: el signo igual no compara nada. Significa «mete el 16 dentro de fallos», sustituyendo lo que hubiera.
  • Línea 5: la línea que más confunde al principio. Primero se calcula lo que hay a la derecha (aciertos, que vale 34, más 1, o sea 35) y ese resultado se mete en la caja de la izquierda. No es una ecuación imposible: es una orden que se lee de derecha a izquierda.

El nombre de una variable no cambia nunca; lo que cambia es su contenido, y cada asignación destruye el valor anterior. Por eso una asignación se lee siempre de derecha a izquierda: primero se calcula el valor, después se guarda.

Para el examen

  • Las tres operaciones: declarar, asignar y consultar

  • Cómo se lee una asignación: de derecha a izquierda: primero calcula, después guarda

Los tipos de datos básicos

Al declarar una variable hay que decir qué clase de dato va a contener. Eso es el tipo, y no es una formalidad: decide cuánta memoria se reserva, qué valores caben y, sobre todo, qué operaciones tienen sentido. A dos números se les puede restar; a dos nombres, no.

TipoQué guardaEjemplo de valorTamaño habitual
Entero (int)Números sin parte decimal, positivos o negativos34, 0, -74 bytes (32 bits)
Real (float, double)Números con parte decimal, en coma flotante6.75, -0.54 u 8 bytes
Carácter (char)Un único símbolo'A', '7', '?'1 o 2 bytes
Cadena (string)Una secuencia de caracteres, de longitud variable"Tema 12 pendiente"Depende de la longitud
Booleano (bool)Solo dos valores posibles: verdadero o falsotrue, false1 byte o incluso 1 bit

El booleano es el que más sorprende a quien empieza, porque parece pobre. Es justo al contrario: es el tipo que devuelven todas las comparaciones y el que consultan los condicionales y los bucles para decidir por dónde seguir. Sin booleanos no habría ni un if.

Cuidado con la diferencia entre el número 5 y la cadena de texto «5». Se parecen en la pantalla y no tienen nada que ver: el primero admite operaciones aritméticas y el segundo no. En un lenguaje que permita sumar cadenas, «5» + «3» da como resultado «53», porque para una cadena sumar significa pegar una detrás de otra, no calcular.

El carácter guarda un solo símbolo y va entre comillas simples; la cadena guarda una secuencia y va entre comillas dobles. Es la distinción que más se pregunta de este apartado.

Para el examen

  • Los cinco básicos: entero, real, carácter, cadena y booleano

  • Booleano: solo verdadero o falso; es lo que devuelven las comparaciones

  • Carácter frente a cadena: un símbolo frente a una secuencia

  • Trampa clásica: «5» + «3» concatena y da «53»

Constantes y valores literales

Una constante es como una variable pero con la caja sellada: se le da un valor una sola vez, al declararla, y a partir de ahí cualquier intento de cambiarlo es un error que el traductor detecta. Se usa para los datos que forman parte de las reglas del programa y no dependen de la ejecución.

pseudocodigo
constante entero PREGUNTAS_EXAMEN = 100
constante real  NOTA_DE_CORTE   = 5.0

real nota = (aciertos * 10.0) / PREGUNTAS_EXAMEN

if (nota >= NOTA_DE_CORTE) {
    escribir("Aprobado")
}

PREGUNTAS_EXAMEN = 120     // ERROR: una constante no se puede reasignar

El bloque anterior calcula la nota sobre diez a partir de los aciertos y la compara con el corte. Lo interesante no es el cálculo, sino los nombres: leyendo la línea se entiende qué representa cada número. La última línea es el error que se busca a propósito, porque es el que demuestra para qué sirve declarar algo como constante.

A los valores escritos directamente en el código (el 100, el 5.0, el texto «Aprobado») se les llama literales. Un literal suelto en medio del código, sin nombre que lo explique, se conoce como número mágico y es mala práctica por dos motivos: nadie sabe qué significa al leerlo meses después, y si el examen pasa a tener 120 preguntas hay que buscarlo y cambiarlo en todos los sitios donde aparezca, con el riesgo de olvidarse de uno.

La constante se fija al declararla y no admite reasignación; la variable puede cambiar tantas veces como haga falta. Por convención, los nombres de constantes se escriben en mayúsculas para distinguirlas de un vistazo.

Para el examen

  • Constante: recibe su valor al declararla; reasignarla es error

  • Número mágico: un literal suelto sin nombre: mala práctica

Tipado fuerte y débil, estático y dinámico

Aquí hay dos parejas de términos que suenan parecido, se mezclan constantemente y son dos preguntas distintas. Conviene separarlas antes de nada: una habla de CUÁNDO se comprueban los tipos y la otra de CÓMO DE ESTRICTO es el lenguaje al comprobarlos.

Tipado estático significa que el tipo de cada variable se conoce y se comprueba antes de ejecutar, al compilar, y no cambia. Tipado dinámico significa que el tipo se determina en tiempo de ejecución, según lo que se le vaya asignando, de modo que una variable puede guardar hoy un número y dos líneas después un texto.

Tipado fuerte significa que el lenguaje no tolera mezclas incoherentes: si se intenta sumar un número y un texto, protesta y se planta. Tipado débil significa que, ante esa mezcla, el lenguaje se inventa una conversión por su cuenta y sigue adelante, lo que evita el error inmediato pero puede producir resultados absurdos silenciosamente.

Tipado fuerteTipado débil
Tipado estáticoJava, C#: el tipo se fija al declarar y no se permiten mezclasC: el tipo se fija al declarar pero el lenguaje deja convertir casi cualquier cosa
Tipado dinámicoPython: el tipo se decide al ejecutar, pero sumar texto y número da errorJavaScript, PHP: el tipo se decide al ejecutar y además convierte por su cuenta
pseudocodigo
// Con tipado FUERTE
"12" + 3     ->  error: no se puede sumar un texto y un numero

// Con tipado DEBIL
"12" + 3     ->  "123"   el lenguaje convierte el 3 en texto y los pega
 12  + "3"   ->   15     o al reves, segun el lenguaje y el operador

// Con tipado ESTATICO
entero temas = 33
temas = "muchos"     ->  error detectado al compilar

// Con tipado DINAMICO
temas = 33           //  ahora «temas» es un entero
temas = "muchos"     //  y ahora es una cadena; el lenguaje lo acepta

Son dos ejes independientes y se combinan de las cuatro formas posibles. Estático o dinámico responde a cuándo se comprueba el tipo; fuerte o débil responde a si el lenguaje tolera mezclarlos.

Para el examen

  • Estático: el tipo se comprueba al compilar y no cambia: Java

  • Dinámico: se decide al ejecutar: Python

  • Fuerte: no tolera mezclas de tipos

  • Débil: convierte por su cuenta: en JavaScript «12» + 3 da «123»

El ámbito de una variable

El ámbito de una variable es la zona del programa donde su nombre existe y se puede usar. Fuera de ahí, para el programa esa variable no ha existido nunca, y nombrarla da error. La regla general es sencilla: una variable vive dentro del bloque donde se declaró, entendiendo por bloque el trozo de código encerrado entre llaves.

pseudocodigo
entero temasTotales = 33        // GLOBAL: se ve en todo el programa

function contarPendientes() {
    entero pendientes = 0       // LOCAL: solo existe dentro de esta funcion

    if (temasTotales > 0) {
        entero aviso = 1        // DE BLOQUE: solo existe dentro del if
        pendientes = temasTotales
    }

    escribir(aviso)             // ERROR: «aviso» murio al cerrar el if
    return pendientes
}

escribir(pendientes)            // ERROR: «pendientes» solo vive en la funcion
escribir(temasTotales)          // correcto: es global
  • «temasTotales» se declara fuera de todo, así que es global: la función puede leerla sin más y la última línea también.
  • «pendientes» se declara dentro de la función, así que es local a ella: la función la usa con normalidad, pero desde fuera no existe.
  • «aviso» se declara dentro del if, así que su ámbito es solo ese bloque. Al cerrarse la llave del if desaparece, y por eso la línea que intenta escribirla falla aunque esté en la misma función.

Conviene distinguir el ámbito del tiempo de vida, que son cosas distintas aunque en la práctica coincidan casi siempre. El ámbito es dónde se puede escribir el nombre; el tiempo de vida es durante cuánto tiempo el dato ocupa memoria. Una variable local nace al entrar en el bloque y se destruye al salir, y por eso una función que se llama mil veces no acumula mil copias de sus variables.

Las variables globales son cómodas y peligrosas a partes iguales: cualquier parte del programa puede cambiarlas, así que cuando una toma un valor raro no hay forma fácil de saber quién ha sido. La recomendación general es declarar cada variable en el ámbito más pequeño que le sirva.

Global es la que se declara fuera de toda función y se ve en el programa entero; local es la que se declara dentro de un bloque y muere al cerrarse ese bloque. Usar una variable fuera de su ámbito no es un fallo de lógica, es un error que impide compilar.

Para el examen

  • Ámbito global: declarada fuera de toda función; visible en todo el programa

  • Ámbito local: muere al cerrarse su bloque

  • Buena práctica: declarar en el ámbito más pequeño posible

Conversión de tipos y precisión de los reales

Cuando en una misma expresión aparecen tipos distintos hay que convertir alguno, y esa conversión puede ser implícita, cuando la hace el lenguaje solo, o explícita, cuando la pide el programador. A la conversión explícita se la suele llamar casting.

La conversión implícita se acepta sin problema cuando es de ensanchamiento, es decir, cuando el tipo de destino es más amplio y no se pierde nada: un entero cabe en un real de sobra. La de estrechamiento va al revés y sí pierde información, así que casi siempre hay que pedirla a mano, precisamente para que quede constancia de que se asume esa pérdida.

pseudocodigo
entero aciertos = 7
entero total    = 10

real media1 = aciertos / total          // 0.0   <- la trampa
real media2 = (real) aciertos / total   // 0.7   <- correcto

real nota = 6.9
entero redondeada = (entero) nota       // 6, no 7: trunca, no redondea

La primera división es el clásico de examen: los dos operandos son enteros, así que el lenguaje hace una división entera, obtiene 0 y solo después lo convierte a real, con lo que queda 0.0. Convertir uno de los dos a real antes de dividir cambia por completo el resultado. Y la última línea recuerda que el paso de real a entero corta la parte decimal en vez de redondear.

Hay una segunda sorpresa con los reales. Se almacenan en coma flotante y en binario, y muchos decimales que en base diez son exactos no lo son en base dos, igual que un tercio no tiene expresión decimal exacta. Por eso 0.1 más 0.2 no da exactamente 0.3, sino un número ligerísimamente distinto, y la consecuencia práctica es que dos números reales no deben compararse con igualdad: se comprueba que su diferencia sea menor que un margen aceptado.

El otro límite que conviene conocer es el desbordamiento: cada tipo entero tiene un rango finito, y al superar su valor máximo no se produce un error sino que el resultado da la vuelta y aparece un número negativo enorme, sin aviso ninguno.

Dividir dos enteros da un entero aunque el resultado se guarde en un real. Y nunca se comparan dos reales con igualdad exacta: se compara si su diferencia cae por debajo de un margen.

Para el examen

  • División entera: 7 / 10 da 0, aunque se guarde en un real

  • Casting de real a entero: trunca, no redondea

  • Comparar reales: nunca con igualdad exacta: 0.1 + 0.2 no es exactamente 0.3