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 essizeof(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:
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
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, consideraunsigned(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ípicamenteintsi 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 esdouble.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.21Ventajas 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:
intsuele ser suficiente y simple. Si decidesunsigned, recuerda que restas o comparaciones con valores negativos pueden dar sorpresas; en muchos programas,intes 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; ¿
nes int? ¿puede ser grande? - Sugerencia: si
nesint, usaint ipara evitar conversiones. Sinrepresenta tamaños (por ejemplo, longitud de arrays), en C se usa muchosize_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,longolong 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, devolverlongy 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
unsignedcon anchura suficiente. En código real, para anchuras exactas se usan tipos de<stdint.h>(comouint32_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
inten 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)
| Necesidad | Tipo típico | Comentarios |
|---|---|---|
| Entero general | int | Buena opción por defecto para lógica y contadores moderados. |
| Entero grande | long long | Útil cuando el rango de int puede quedarse corto. |
| Solo no negativos | unsigned int / unsigned long | Ojo con restas y comparaciones con signed. |
| Decimales | double | Más precisión que float; literales por defecto son double. |
| Caracteres / bytes | char / unsigned char | unsigned char es común para datos binarios. |