Tipos de datos en C, variables y constantes

Capítulo 2

Tiempo estimado de lectura: 10 minutos

+ Ejercicio

Tipos fundamentales y la idea clave: el tamaño depende de la plataforma

En C, los tipos de datos básicos representan categorías de valores (enteros y reales) y determinan cuánta memoria se reserva y cómo se interpretan los bits. Un punto importante: el estándar del lenguaje define relaciones y mínimos, pero no fija un tamaño único para cada tipo. Por eso, el rango exacto de un int o un long puede variar según el sistema y el compilador.

Para escribir código portable, conviene pensar en: (1) qué rango necesitas, (2) si habrá valores negativos, (3) si necesitas decimales, y (4) qué operaciones harás (por ejemplo, multiplicaciones que pueden desbordar).

Enteros: char, short, int, long, long long

Los enteros almacenan valores sin parte decimal. En general, a mayor “anchura” del tipo, mayor rango (pero también más memoria). Los tipos enteros fundamentales son:

  • char: pensado para caracteres, pero en realidad es un entero pequeño. Es el único tipo cuyo tamaño en bytes es 1 por definición (un byte en C es sizeof(char)).
  • short: entero “corto”.
  • int: entero “natural” para la plataforma; suele ser el más eficiente para operaciones generales.
  • long: entero “largo”.
  • long long: entero “más largo”, útil cuando necesitas rangos grandes.

Signed y unsigned

La mayoría de enteros pueden ser signed (con signo) o unsigned (sin signo):

  • signed: permite negativos y positivos.
  • unsigned: solo valores 0 o positivos, pero con un rango máximo mayor para el mismo tamaño.

Ejemplos de declaraciones:

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

signed int a;        /* equivalente a: int a; (normalmente con signo) */
unsigned int b;
unsigned long ul;
unsigned char uc;
long long big;
unsigned long long ubig;

Nota práctica: el tipo char puede ser signed o unsigned según la plataforma si escribes solo char. Si necesitas asegurar el comportamiento, usa explícitamente signed char o unsigned char.

Reales: float y double

Los tipos de coma flotante representan números con parte decimal, pero con precisión finita (no todos los decimales se pueden representar exactamente). Los más usados:

  • float: menos precisión, suele ocupar menos memoria.
  • double: más precisión; suele ser la opción por defecto para cálculos generales.
float temperatura = 36.5f;
double pi = 3.141592653589793;

Observa el sufijo f en 36.5f: sin él, el literal decimal se interpreta como double por defecto.

Tamaño y rangos: cómo consultarlos sin “adivinar”

Como los tamaños varían, es buena práctica consultarlos con sizeof y, si necesitas rangos, usar cabeceras estándar.

Consultar tamaños con sizeof

#include <stdio.h>

int main(void) {
    printf("sizeof(char) = %zu\n", sizeof(char));
    printf("sizeof(short) = %zu\n", sizeof(short));
    printf("sizeof(int) = %zu\n", sizeof(int));
    printf("sizeof(long) = %zu\n", sizeof(long));
    printf("sizeof(long long) = %zu\n", sizeof(long long));
    printf("sizeof(float) = %zu\n", sizeof(float));
    printf("sizeof(double) = %zu\n", sizeof(double));
    return 0;
}

sizeof devuelve un número de tipo size_t, por eso se imprime con %zu.

Consultar rangos con cabeceras estándar

Para enteros, puedes usar <limits.h> (por ejemplo, INT_MAX, UINT_MAX). Para flotantes, <float.h> (por ejemplo, DBL_MAX). Esto evita fijar números “mágicos”.

#include <stdio.h>
#include <limits.h>
#include <float.h>

int main(void) {
    printf("INT_MAX = %d\n", INT_MAX);
    printf("UINT_MAX = %u\n", UINT_MAX);
    printf("DBL_MAX = %e\n", DBL_MAX);
    return 0;
}

Variables: declaración, inicialización y asignación

Declaración

Declarar una variable significa indicar su tipo y su nombre para reservar espacio y definir cómo se interpretará su contenido.

int edad;
double saldo;
unsigned long contador;

Inicialización

Inicializar es dar un valor inicial en el momento de la declaración. Es una buena práctica porque evita usar valores indeterminados.

int edad = 18;
double saldo = 0.0;
unsigned long contador = 0;

Asignación

Asignar es cambiar el valor después de haber declarado la variable.

edad = 19;
saldo = saldo + 250.75;
contador = contador + 1;

Guía práctica paso a paso: elegir tipo y declarar correctamente

  • Paso 1: define el dominio del dato. ¿Puede ser negativo? ¿Necesita decimales? ¿Qué rango aproximado esperas?
  • Paso 2: elige el tipo más simple que cubra el rango. Para contadores generales, suele bastar int. Para cantidades que nunca son negativas, considera unsigned (con cuidado en comparaciones y restas).
  • Paso 3: inicializa. Evita variables sin valor inicial.
  • Paso 4: revisa operaciones críticas. Multiplicaciones, sumas acumuladas o conversiones pueden desbordar; si el resultado puede exceder el rango, usa un tipo mayor o convierte antes de operar.

Literales: el “tipo” de los números que escribes

Un literal es un valor escrito directamente en el código. En C, los literales también tienen tipo, y eso afecta conversiones y resultados.

Literales enteros

  • 42: literal entero decimal (tipo entero por defecto, típicamente int si cabe).
  • 42U: unsigned.
  • 42L: long.
  • 42LL: long long.
unsigned int x = 4000000000U;   /* el sufijo U evita interpretarlo como signed */
long long y = 9000000000LL;     /* LL para forzar long long */

Literales de coma flotante

  • 3.14: por defecto es double.
  • 3.14f: float.
float a = 0.1f;
double b = 0.1; /* double */

Literales de caracteres

'A' es un literal de carácter. Se almacena como un valor entero asociado al código del carácter (por ejemplo, ASCII en muchos entornos, pero no lo asumas para lógica compleja).

char c = 'A';
int code = 'A'; /* válido: se promociona a int */

Constantes: const vs #define

const: constante con tipo

const declara un objeto cuyo valor no debe modificarse a través de ese identificador. Tiene tipo, participa en el sistema de tipos y el compilador puede detectar usos incorrectos.

const int DIAS_SEMANA = 7;
const double IVA = 0.21;

/* DIAS_SEMANA = 8;  // error: no se puede asignar a una variable const */

Ventajas de const: tiene tipo, respeta el ámbito (scope), mejora la legibilidad y permite diagnósticos del compilador.

Riesgo común: creer que const siempre implica “valor en tiempo de compilación” para cualquier uso. En C, depende del contexto; aun así, para la mayoría de usos cotidianos funciona como constante lógica.

#define: macro simple

Una macro reemplaza texto antes de compilar (preprocesador). No tiene tipo y no respeta el sistema de tipos por sí misma.

#define DIAS_SEMANA 7
#define IVA 0.21

Ventajas de #define: útil para constantes muy simples, para condicionales de preprocesador, o cuando necesitas que exista antes del compilador (por ejemplo, para #if).

Riesgos: al ser sustitución textual, puede producir errores sutiles si se usa en expresiones sin paréntesis.

#define DOBLE(x) x + x

int a = DOBLE(3) * 2;  /* se expande a: 3 + 3 * 2, resultado inesperado */

Versión más segura:

#define DOBLE(x) ((x) + (x))

Para constantes numéricas, en muchos casos es preferible const (por tipo y ámbito), y reservar #define para necesidades del preprocesador o macros cuidadosamente escritas.

Representación en memoria: bytes, bits y por qué ocurren desbordamientos

La memoria se organiza en bytes. Un tipo ocupa un número de bytes (sizeof) y esos bytes contienen bits que codifican el valor. Como el número de combinaciones de bits es finito, el rango de valores también lo es. Si intentas almacenar un valor fuera del rango, ocurre un desbordamiento (overflow) o una conversión con pérdida.

Ejemplo conceptual con enteros sin signo

Un entero unsigned de N bits puede representar desde 0 hasta un máximo. Si sumas 1 al máximo, el valor “vuelve” a 0 (aritmética modular). Esto puede ser útil en algunos algoritmos, pero también puede causar bugs si no lo esperas.

unsigned int u = 0;
/* ... imagina que u llega a su máximo ... */
u = u + 1; /* puede volver a 0 si estaba en el máximo */

Enteros con signo y comportamiento indefinido

En enteros con signo, el desbordamiento en C puede ser comportamiento indefinido (el compilador puede asumir que no ocurre y optimizar de forma que el programa se comporte “raro”). Por eso, para cálculos que pueden exceder el rango, conviene:

  • usar un tipo más grande,
  • convertir a un tipo más grande antes de operar,
  • o validar rangos antes de sumar/multiplicar.
int a = 100000;
int b = 100000;
long long prod = (long long)a * b; /* convertir antes evita overflow en int */

Flotantes: precisión finita

En coma flotante, el problema típico no es “desbordar” en operaciones simples, sino la pérdida de precisión y errores de redondeo.

#include <stdio.h>

int main(void) {
    double x = 0.1 + 0.2;
    printf("%.17f\n", x); /* puede no ser exactamente 0.3 */
    return 0;
}

Esto justifica comparar flotantes con tolerancia en lugar de igualdad exacta en muchos casos.

Ejercicios de lectura de código: ¿qué tipo conviene usar y por qué?

Lee cada fragmento y responde: (1) qué tipo usarías, (2) si sería signed o unsigned, y (3) por qué. Luego compara con las sugerencias.

Ejercicio 1: edad de una persona

/* Queremos guardar la edad de un usuario */
_____ edad = 0;
  • Pistas: no hay decimales; no debería ser negativa; el rango es pequeño.
  • Sugerencia: int suele ser suficiente y simple. Si decides unsigned, recuerda que restas o comparaciones con valores negativos pueden dar sorpresas; en muchos programas, int es la opción más práctica.

Ejercicio 2: contador de elementos en un bucle

/* Contar elementos procesados */
for (_____ i = 0; i < n; i++) {
    /* ... */
}
  • Pistas: contador que crece desde 0; ¿n es int? ¿puede ser grande?
  • Sugerencia: si n es int, usa int i para evitar conversiones. Si n representa tamaños (por ejemplo, longitud de arrays), en C se usa mucho size_t, pero eso implica ajustar comparaciones y formatos de impresión.

Ejercicio 3: precio con decimales

/* Precio en euros con céntimos */
_____ precio = 19.99;
  • Pistas: hay decimales; ¿necesitas precisión exacta de dinero?
  • Sugerencia: para cálculos generales, double. Para dinero real en aplicaciones serias, a menudo se prefiere almacenar en céntimos como entero (por ejemplo, long o long long) para evitar errores de redondeo.

Ejercicio 4: bytes leídos de un archivo

/* bytes leídos en una operación */
_____ bytes_leidos = leer();
  • Pistas: no negativo; puede crecer; representa tamaño.
  • Sugerencia: un tipo sin signo orientado a tamaños suele ser adecuado (frecuentemente size_t), porque modela “cantidad de bytes”. Si la función puede devolver error con -1, entonces necesitas un tipo con signo o un protocolo distinto (por ejemplo, devolver long y documentar -1 como error).

Ejercicio 5: ID que viene de un protocolo

/* ID de 32 bits sin signo proveniente de la red */
_____ id = 0;
  • Pistas: el protocolo define bits, no “int”.
  • Sugerencia: usa un tipo unsigned con anchura suficiente. En código real, para anchuras exactas se usan tipos de <stdint.h> (como uint32_t), porque expresan la intención de “32 bits” de forma clara y portable.

Ejercicio 6: multiplicación que puede desbordar

int a = 50000;
int b = 50000;
int area = a * b; /* ¿problema? */
  • Pistas: el resultado puede exceder el rango de int en algunas plataformas.
  • Sugerencia: cambia el tipo del resultado y convierte antes: long long area = (long long)a * b;. Si el dominio puede ser aún mayor, revisa el rango esperado y ajusta.

Tabla rápida de decisión (orientativa)

NecesidadTipo típicoComentarios
Entero generalintBuena opción por defecto para lógica y contadores moderados.
Entero grandelong longÚtil cuando el rango de int puede quedarse corto.
Solo no negativosunsigned int / unsigned longOjo con restas y comparaciones con signed.
DecimalesdoubleMás precisión que float; literales por defecto son double.
Caracteres / byteschar / unsigned charunsigned char es común para datos binarios.

Ahora responde el ejercicio sobre el contenido:

¿Cuál es la forma correcta de evitar un desbordamiento al multiplicar dos variables int cuando el resultado puede exceder el rango de int?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Si la multiplicación puede superar el rango de int, conviene promover los operandos a un tipo mayor antes de operar (por ejemplo, (long long)a * b) y almacenar el resultado en long long, evitando overflow en int.

Siguiente capítulo

Operadores y expresiones con énfasis en conversiones

Arrow Right Icon
Portada de libro electrónico gratuitaFundamentos de programación en C desde cero: variables, control de flujo y funciones
18%

Fundamentos de programación en C desde cero: variables, control de flujo y funciones

Nuevo curso

11 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.