🔎 Buscar

💻 Programación desde cero

Qué es programar, cómo elegir tu primer lenguaje, tipos de datos, variables, control de flujo, funciones, alcance y el modelo mental del programa. Teoría y práctica con C y Python.

Fundamentos CS📖 Contenido

Programación desde cero

Programar es darle instrucciones a una máquina. Pero entre tú y la máquina hay un abismo: tú piensas en conceptos («guarda la lista de usuarios»), la máquina solo ejecuta operaciones sobre bytes. Tu trabajo como programador es traducir ideas en un lenguaje que la máquina pueda ejecutar, y hacerlo de forma que otro humano (o tú mismo en 6 meses) lo pueda entender.

Esta página te da el vocabulario completo que necesitas para entender cualquier tutorial: qué es un lenguaje, qué es un tipo, qué es una variable, qué es una función. Con C y Python como lenguajes de referencia.

Qué es un lenguaje de programación

Un lenguaje de programación es un sistema de notación para describir computaciones. Tiene dos caras:

  • Sintaxis: las reglas de escritura (cómo se escriben las instrucciones).
  • Semántica: el significado de cada construcción (qué hace).
# Python: sintaxis limpia, legible casi como inglés
total = 0
for i in range(10):
    total += i
print(total)
// C: sintaxis más densa, te obliga a declarar tipos y manejar memoria
#include <stdio.h>

int main(void) {
    int total = 0;
    for (int i = 0; i < 10; i++) {
        total += i;
    }
    printf("%d\n", total);
    return 0;
}

Ambos programas hacen exactamente lo mismo (suman 0..9). La diferencia no es de capacidad, sino de nivel de abstracción y de qué control te da sobre la máquina.

Niveles de los lenguajes

Nivel Ejemplos Qué controlas Cerca de la máquina
Bajo nivel Ensamblador, C, C++, Rust Registros, memoria, punteros Muy cerca
Medio Go, Java, C#, TypeScript La mayoría, con runtime que te protege Media
Alto nivel Python, Ruby, JavaScript, PHP Estructuras y lógica; la memoria la gestiona el runtime Lejos

💡 Regla mental: cuanto más bajo el nivel, más control y más responsabilidad (y más fácil meter la pata). Cuanto más alto, más productividad y más distancia del hardware. Los ingenieros serios saben al menos un lenguaje de cada extremo.

Compilados vs interpretados

  • Compilado (C, Go, Rust): un compilador traduce TODO tu código a lenguaje de máquina antes de ejecutarlo. El resultado es un binario (.exe, .out). Rápido en ejecución, lento en desarrollo.
  • Interpretado (Python, Ruby): un intérprete lee y ejecuta tu código línea por línea al correrlo. Sin binario, más lento en ejecución, más rápido en desarrollo.
  • Compilados a bytecode con VM (Java, C#, Python internamente): el compilador produce bytecode (código intermedio) que luego una máquina virtual (VM) ejecuta y optimiza.
C:   código fuente ──compilador──▶ binario (lenguaje máquina) ──▶ CPU
Python: código fuente ──intérprete──▶ ejecuta cada línea
Java: código fuente ──compilador──▶ bytecode ──JVM──▶ máquina

⚠️ Terminología clave: compilador (traduce todo el programa), intérprete (ejecuta línea a línea), bytecode (código intermedio), VM / máquina virtual (ejecuta bytecode), runtime (todo lo que da soporte a tu programa al ejecutarse: gestión de memoria, librerías base).

Cómo elegir tu primer lenguaje

La ruta del plan recomienda aprender C o Python como cimientos. ¿Cuál?

  • C te enseña la máquina de verdad: memoria, punteros, cómo funciona todo por dentro. Es el lenguaje de los sistemas operativos, las bases de datos y los kernels.
  • Python te enseña a pensar en problemas y estructuras sin pelearte con la máquina. Es el lenguaje de la ciencia de datos, la IA y el backend moderno.
📌 Recomendación del plan

Para fundamentos CS, empieza con C (te obliga a entender memoria y tipos) y usa Python como segundo lenguaje para expresar algoritmos con claridad. El plan de backend de este sitio profundiza en ambos: Python y Go.

Variables y tipos de datos

Una variable es un nombre que apunta a un valor guardado en memoria. Un tipo de dato describe qué clase de valor es: un número, un texto, un verdadero/falso.

Tipos básicos (primitivos)

Tipo Qué es Ejemplo Tamaño típico (64 bits)
Entero (integer) Número sin decimales 42, -7, 0 4-8 bytes
Flotante (float/double) Número con decimales 3.14, 2.5e10 4-8 bytes
Booleano (boolean) Verdadero o falso true, false 1 byte
Carácter (char) Un símbolo 'a', 'ñ', '\n' 1-4 bytes
String Cadena de caracteres "hola mundo" variable

En C los tipos son explícitos: le dices a la máquina qué tipo es cada variable y cuántos bytes ocupa:

#include <stdio.h>

int main(void) {
    int    edad = 30;        // entero
    double precio = 19.99;   // decimal de doble precisión
    float  peso = 70.5f;     // decimal simple
    char   inicial = 'A';    // un carácter (comillas simples)
    _Bool activo = 1;        // 0 o 1 (C no tiene true/false nativo hasta C23)

    printf("edad=%d, precio=%.2f, inicial=%c\n", edad, precio, inicial);
    return 0;
}

En Python los tipos son dinámicos: la variable toma el tipo del valor que le asignas y puede cambiar:

edad = 30            # int
precio = 19.99       # float
activo = True        # bool
nombre = "Ana"       # str
print(f"{edad}, {precio}, {nombre}")

⚠️ Terminología clave: fuertemente tipado (no mezclas tipos sin conversión: Python, C, Java) vs débilmente tipado (los convierte por ti: JavaScript, PHP). Estático (el tipo se conoce al compilar: C, Go) vs dinámico (se conoce al ejecutar: Python, JS).

Integer overflow y precisión

Un int no puede guardar cualquier número: tiene un rango finito.

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

int main(void) {
    int max = INT_MAX;        // 2147483647 en 32 bits
    printf("%d\n", max + 1);  // ⚠️ overflow: "da la vuelta" a -2147483648
    return 0;
}

Los flotantes tienen precisión limitada: 0.1 + 0.2 != 0.3 en punto flotante binario:

print(0.1 + 0.2)   # 0.30000000000000004

💡 Por eso el dinero nunca se guarda como float: se guarda como entero (centavos) o como tipo decimal de precisión exacta. Y por eso los enteros sin signo (unsigned) duplican el rango positivo a costa de no poder ser negativos.

Control de flujo

El código no se ejecuta siempre en línea recta: hay decisiones (condicionales) y repeticiones (bucles).

Condicionales

edad = 18
if edad >= 18:
    print("Mayor de edad")
elif edad >= 13:
    print("Adolescente")
else:
    print("Menor")
int edad = 18;
if (edad >= 18) {
    printf("Mayor de edad\n");
} else if (edad >= 13) {
    printf("Adolescente\n");
} else {
    printf("Menor\n");
}

Bucles

# for: repetir un número conocido de veces / recorrer una colección
for i in range(5):
    print(i)          # 0 1 2 3 4

# while: repetir mientras una condición sea verdadera
n = 0
while n < 5:
    n += 1
for (int i = 0; i < 5; i++) {   // inicialización; condición; paso
    printf("%d\n", i);
}

int n = 0;
while (n < 5) {
    n++;
}

⚠️ Terminología: break (salir del bucle), continue (saltar a la siguiente iteración), iteración (una vuelta del bucle), loop infinito (bucle que nunca termina porque la condición nunca es falsa — normalmente un bug).

Switch

Cuando comparas una variable contra muchos valores fijos, switch es más claro que un encadenado de if:

int opcion = 2;
switch (opcion) {
    case 1: printf("Uno\n"); break;
    case 2: printf("Dos\n"); break;
    default: printf("Otro\n"); break;
}

Funciones

Una función es un bloque de código con nombre que recibe parámetros (entradas), hace algo y devuelve un valor de retorno. Es la unidad básica de organización del código.

def suma(a, b):
    """Suma dos números y devuelve el resultado."""
    return a + b

resultado = suma(3, 4)   # 7
#include <stdio.h>

int suma(int a, int b) {   // tipo de retorno, nombre, parámetros
    return a + b;
}

int main(void) {
    int resultado = suma(3, 4);
    printf("%d\n", resultado);   // 7
    return 0;
}

Terminología de funciones

Término Qué significa
Firma Nombre + parámetros + tipo de retorno
Argumento El valor concreto que pasas al llamar
Parámetro La variable que recibe el valor dentro de la función
Retorno El valor que la función devuelve al que la llamó
Efecto secundario Cambio de estado fuera de la función (modificar un archivo, una variable global, imprimir)
Pureza Una función pura solo depende de sus argumentos y no tiene efectos secundarios
Recursión Una función que se llama a sí misma

Recursión

Una función recursiva se llama a sí misma con un caso más pequeño. Toda recursión necesita un caso base (cuándo parar) y un paso recursivo (reducir el problema):

def factorial(n):
    if n <= 1:        # caso base
        return 1
    return n * factorial(n - 1)   # paso recursivo
int factorial(int n) {
    if (n <= 1) return 1;         // caso base
    return n * factorial(n - 1);  // paso recursivo
}

⚠️ Sin caso base, la recursión se desborda: cada llamada reserva un stack frame y el stack se llena → stack overflow. Recuerda: la recursión usa el stack de llamadas.

Alcance (scope) y nombres

El alcance (scope) define dónde existe una variable. Reglas fundamentales:

  • Variable local: existe solo dentro del bloque/función donde se declara.
  • Variable global: declarada fuera de todo, accesible en todo el archivo.
  • Shadowing: una variable local que «tapa» a una global con el mismo nombre.
x = 10            # global

def foo():
    x = 5         # local (hace shadowing de la global)
    print(x)      # 5

foo()
print(x)          # 10 (la global no cambió)
int x = 10;       // global

int main(void) {
    int x = 5;    // local: tapa a la global
    printf("%d\n", x);   // 5
    return 0;
}

💡 Buenas prácticas: menos variables globales = menos sorpresas. Las funciones deben comunicarse por parámetros y retorno, no por estado global.

Estructuras de datos básicas integradas

Antes de las estructuras de datos «de libro» (página siguiente), todos los lenguajes traen colecciones integradas:

Estructura Ordenada Permite duplicados Ejemplo (Python) Ejemplo (C)
Array [1, 2, 3] (lista) int a[3]
Mapa / diccionario No Claves únicas {"a": 1} struct + tabla hash
Conjunto (set) No No {1, 2, 3}
Tupla (1, "a") struct
frutas = ["manzana", "pera", "mango"]
frutas.append("fresa")
print(frutas[0])           # "manzana"
print(len(frutas))         # 4

precios = {"manzana": 2.5, "pera": 3.0}
print(precios["pera"])     # 3.0
precios["mango"] = 5.0     # agregar clave
#include <stdio.h>
#include <string.h>

int main(void) {
    char frutas[4][16];
    strcpy(frutas[0], "manzana");
    strcpy(frutas[1], "pera");
    printf("%s\n", frutas[0]);
    return 0;
}

⚠️ Índices: las colecciones se indexan desde 0 (el primer elemento es el [0]). El último de una colección de tamaño n es [n-1]. Este es un error clásico: el off-by-one.

Modelo mental del programa

Cuando tu código corre, esto es lo que pasa:

  1. El código fuente se compila o interpreta.
  2. El proceso se carga en memoria: el código (text), los datos globales, el stack (variables locales y llamadas) y el heap (memoria dinámica).
  3. La CPU ejecuta instrucciones de una en una, siguiendo el control de flujo: condiciones, bucles, llamadas a funciones.
  4. Los valores viven en registros o memoria; los nombres que usas son referencias a esas ubicaciones.

Modelo de memoria: stack, heap, data, text

Profundizamos en esto en Cómo funciona una computadora y en la wiki de memoria y punteros en C.

Errores y depuración

Todo programa tiene errores. Aprende los tres tipos:

Tipo Qué es Ejemplo
Error de sintaxis Escribiste algo que el lenguaje no entiende if edad = 18 (falta =)
Error en tiempo de ejecución El programa corre pero se rompe dividir entre cero, índice fuera de rango
Error lógico El programa corre pero hace algo distinto a lo que querías usar < en vez de <=

Depurar (debugging) es encontrar y corregir errores. Herramientas básicas:

  • Imprimir valores: print(variable) para ver qué hay en cada paso.
  • Lector de trazas (stack trace): te dice en qué línea se rompió y cómo se llegó ahí.
  • Debugger: ejecuta línea a línea y te deja inspeccionar variables (gdb, pdb, vscode).
def divide(a, b):
    return a / b

print(divide(10, 2))    # 5.0
print(divide(10, 0))    # ZeroDivisionError → stack trace

💡 Regla de oro: reproduce el error mínimo, encuentra la línea, entiende el por qué, corrige, y verifica que no rompiste nada (de ahí los tests, página de testing).

Cheatsheet de terminología

Término Significado
Algoritmo Secuencia de pasos para resolver un problema
Programa Algoritmo escrito en un lenguaje ejecutable
Bug Error en el programa
Compilador Traduce todo el programa a lenguaje máquina
Intérprete Ejecuta el programa línea a línea
Runtime Infraestructura que da soporte al programa al ejecutarse
Variable Nombre que apunta a un valor en memoria
Tipo Clase de valor: entero, flotante, booleano, cadena…
Función Bloque de código reutilizable con nombre
Parámetro / argumento Entrada declarada / entrada concreta
Retorno Valor que devuelve la función
Scope (alcance) Región donde existe una variable
Recursión Función que se llama a sí misma
Iteración Una vuelta de un bucle
Depuración Proceso de encontrar y corregir bugs

Práctica propuesta

  1. Escribe un programa que diga si un número es primo (en C y en Python).
  2. Escribe una función recursiva fibonacci(n) y otra iterativa. Compara.
  3. Escribe un programa que cuente las vocales de una cadena.
  4. Investiga qué pasa en tu lenguaje con 0.1 + 0.2 y explica por qué.
  5. Escribe una función que invierta un array sin crear otro (pista: dos índices, uno por cada extremo).

Compila y ejecuta tus programas de C con:

gcc programa.c -o programa && ./programa

Y ejecuta Python con:

python3 programa.py

Para profundizar

Estudio · Recursos de todo el mundo (inglés, chino, japonés, español, francés, ruso…) curados y traducidos al español.