Volver a Labs
Intermedio

Fundamentos de Programación en C

3–4 horas Actualizado Junio 2026
C Fundamentos Sistemas Bajo Nivel

Introducción

C es el lenguaje que define cómo funciona la computación moderna. Linux, Windows, el intérprete de Python, los compiladores de Java y casi cualquier sistema operativo están escritos (en parte) en C. Es un lenguaje de bajo nivel — tienes acceso directo a la memoria — lo que lo hace extremadamente eficiente y a la vez exigente.

Aprender C te da una comprensión profunda de lo que sucede debajo de lenguajes como Python o Java: gestión de memoria, punteros, estructuras de datos y cómo el hardware ejecuta tu código.

¿Qué vas a aprender en este lab?

  • Compilar y ejecutar un programa en C
  • Usar variables, tipos de datos y operadores
  • Controlar el flujo con condicionales y bucles
  • Definir y llamar funciones
  • Trabajar con arrays y strings
  • Entender la entrada/salida estándar

Requisitos previos: conocimiento básico de algún lenguaje de programación (variables, bucles, funciones). No es un lab para quien empieza desde cero en programación.


0. Compilar y ejecutar

C es un lenguaje compilado: el código fuente (.c) se convierte a código máquina (ejecutable) antes de correr.

/* hola.c */
#include <stdio.h>

int main(void) {
    printf("¡Hola, mundo!\n");
    return 0;
}

Compilar con GCC:

gcc hola.c -o hola
./hola

Salida:

¡Hola, mundo!

Anatomía del programa:

Parte Qué hace
#include <stdio.h> Incluye la biblioteca de entrada/salida estándar
int main(void) Punto de entrada. Devuelve int (0 = éxito)
printf(...) Imprime texto con formato en stdout
\n Salto de línea (no hay println, el salto va dentro del string)
return 0 Indica al sistema operativo que el programa terminó sin error

Compilar con warnings (siempre)

gcc -Wall -Wextra -std=c11 hola.c -o hola

-Wall -Wextra activan todos los avisos. Corrígelos siempre — en C, ignorar un warning puede ser un bug serio.


1. Variables y Tipos de Datos

C tiene tipos de tamaño fijo. A diferencia de Python, debes declarar el tipo antes de usar la variable.

int    edad      = 25;       /* entero — 4 bytes */
double precio    = 19.99;    /* decimal — 8 bytes */
float  temperatura = 36.6f;  /* decimal — 4 bytes */
char   inicial   = 'J';      /* un carácter — 1 byte */
int    activo    = 1;        /* bool: 0 = false, distinto de 0 = true */

C no tiene un tipo String. Las cadenas son arrays de char terminados en \0.

char nombre[] = "Julian";  /* equivale a {'J','u','l','i','a','n','\0'} */

Tamaños de tipos

#include <stdio.h>

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

En sistemas de 64 bits: int = 4, long = 8, double = 8, char = 1.

Tipos con tamaño garantizado (preferidos en código moderno)

#include <stdint.h>

int8_t   a = 127;          /* entero con signo de exactamente 8 bits */
uint32_t b = 4294967295U;  /* entero sin signo de exactamente 32 bits */
int64_t  c = -9000000000LL;

Constantes

#define PI 3.14159265358979  /* macro — reemplaza texto antes de compilar */
const double GRAVEDAD = 9.81; /* constante tipada — preferida */

Ejercicio 1

Escribe un programa que declare variables de cada tipo básico (int, double, char), las inicialice con valores de tu elección y las imprima usando printf con el especificador correcto (%d, %f, %c, %s).


2. Entrada y Salida

#include <stdio.h>

int main(void) {
    int edad;
    char nombre[50];

    printf("¿Tu nombre? ");
    scanf("%49s", nombre);        /* lee string (hasta espacio) */

    printf("¿Tu edad? ");
    scanf("%d", &edad);           /* & necesario para int, no para arrays */

    printf("Hola, %s! Tienes %d años.\n", nombre, edad);
    return 0;
}

Especificadores de formato más usados

Tipo Escritura (printf) Lectura (scanf)
int %d %d
long %ld %ld
double %f o %.2f %lf
float %f %f
char %c %c
char[] %s %49s (con límite)

Nunca uses scanf("%s", buffer) sin límite de tamaño — es el origen de incontables vulnerabilidades de buffer overflow.


3. Operadores

int a = 10, b = 3;

printf("%d\n", a + b);   /* 13 */
printf("%d\n", a - b);   /* 7  */
printf("%d\n", a * b);   /* 30 */
printf("%d\n", a / b);   /* 3  — división entera */
printf("%d\n", a % b);   /* 1  — módulo */

/* Incremento / decremento */
a++;    /* post-incremento: usa a, luego incrementa */
++a;    /* pre-incremento: incrementa, luego usa */
a--;
--a;

/* Asignación compuesta */
a += 5;   /* a = a + 5  */
a *= 2;   /* a = a * 2  */

Operadores de bits

C permite manipular bits individuales — algo muy usado en sistemas embebidos:

unsigned int flags = 0b00001010;  /* 10 en binario */

flags |= (1 << 2);   /* activa el bit 2:  00001110 */
flags &= ~(1 << 1);  /* desactiva bit 1:  00001100 */
flags ^= (1 << 3);   /* invierte bit 3:   00000100 */

printf("Bit 2 está activo: %d\n", (flags >> 2) & 1);  /* 1 */

4. Condicionales

int nota = 75;

if (nota >= 90) {
    printf("Excelente\n");
} else if (nota >= 70) {
    printf("Aprobado\n");
} else if (nota >= 50) {
    printf("Recuperación\n");
} else {
    printf("Reprobado\n");
}
/* Aprobado */

switch

int dia = 3;

switch (dia) {
    case 1: printf("Lunes\n");     break;
    case 2: printf("Martes\n");    break;
    case 3: printf("Miércoles\n"); break;
    case 4: printf("Jueves\n");    break;
    case 5: printf("Viernes\n");   break;
    default: printf("Fin de semana\n");
}

Diferencia importante con Java/Python: en C, el switch solo funciona con valores enteros y caracteres — no con strings.


5. Bucles

/* for */
for (int i = 1; i <= 5; i++) {
    printf("Iteración %d\n", i);
}

/* while */
int numero = 1;
while (numero <= 100) {
    numero *= 2;
}
printf("Primer número mayor a 100: %d\n", numero); /* 128 */

/* do-while — ejecuta al menos una vez */
int intentos = 0;
do {
    printf("Intento %d\n", intentos + 1);
    intentos++;
} while (intentos < 3);

/* break y continue */
for (int i = 0; i < 10; i++) {
    if (i == 5) break;      /* sale del bucle */
    if (i % 2 == 0) continue; /* salta iteración */
    printf("%d ", i);       /* imprime 1 3 */
}

Ejercicio 2

Escribe un programa que lea enteros del usuario hasta que ingrese 0. Al terminar, imprime: cantidad de números positivos, cantidad de negativos, la suma total y el promedio (excluye el 0 del cálculo).


6. Funciones

C requiere declarar o definir la función antes de usarla. La declaración (prototipo) le dice al compilador el tipo de retorno y los parámetros.

#include <stdio.h>

/* Prototipo — declaración antes de main */
int sumar(int a, int b);
double promedio(int arr[], int n);
void imprimir_separador(char c, int veces);

int main(void) {
    printf("5 + 3 = %d\n", sumar(5, 3));

    int notas[] = {8, 7, 9, 6, 10};
    printf("Promedio: %.2f\n", promedio(notas, 5));

    imprimir_separador('-', 20);
    return 0;
}

/* Definiciones */
int sumar(int a, int b) {
    return a + b;
}

double promedio(int arr[], int n) {
    int suma = 0;
    for (int i = 0; i < n; i++) suma += arr[i];
    return (double)suma / n;
}

void imprimir_separador(char c, int veces) {
    for (int i = 0; i < veces; i++) putchar(c);
    putchar('\n');
}

Paso por valor vs por referencia

En C todo se pasa por valor — la función recibe una copia. Para modificar el original, debes pasar un puntero.

/* No funciona: modifica la copia */
void duplicar_mal(int n) {
    n *= 2;  /* no afecta a quien llama */
}

/* Funciona: recibe dirección, modifica el original */
void duplicar_bien(int *n) {
    *n *= 2;
}

int x = 10;
duplicar_mal(x);
printf("%d\n", x);  /* 10 — sin cambios */

duplicar_bien(&x);
printf("%d\n", x);  /* 20 — modificado */

7. Arrays

#include <stdio.h>

int main(void) {
    /* Declaración e inicialización */
    int numeros[5] = {10, 20, 30, 40, 50};
    double notas[] = {8.5, 7.0, 9.5};  /* tamaño inferido */

    /* Acceso — índice 0 a n-1 */
    printf("%d\n", numeros[0]);  /* 10 */
    printf("%d\n", numeros[4]);  /* 50 */

    /* Recorrer */
    int n = sizeof(numeros) / sizeof(numeros[0]);  /* 5 */
    for (int i = 0; i < n; i++) {
        printf("%d ", numeros[i]);
    }
    printf("\n");

    /* Modificar */
    numeros[2] = 99;

    return 0;
}

En C no hay comprobación de límites. Acceder a numeros[10] cuando el array tiene 5 elementos no da error inmediato — lee memoria aleatoria. Este es uno de los errores más peligrosos en C.

Arrays multidimensionales

int matriz[3][3] = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

/* Acceso */
printf("%d\n", matriz[1][2]);  /* 6 */

/* Recorrer */
for (int i = 0; i < 3; i++) {
    for (int j = 0; j < 3; j++) {
        printf("%3d", matriz[i][j]);
    }
    printf("\n");
}

8. Strings en C

Un string en C es un array de char terminado en el carácter nulo \0.

#include <stdio.h>
#include <string.h>   /* strlen, strcpy, strcat, strcmp, etc. */

int main(void) {
    char saludo[20] = "Hola";
    char nombre[]   = "Julian";

    /* strlen — longitud sin contar \0 */
    printf("Longitud: %zu\n", strlen(saludo));  /* 4 */

    /* strcat — concatenar (el destino debe tener espacio) */
    strcat(saludo, ", ");
    strcat(saludo, nombre);
    printf("%s\n", saludo);  /* Hola, Julian */

    /* strcmp — comparar (0 = iguales) */
    if (strcmp(nombre, "Julian") == 0) {
        printf("¡Hola, Julian!\n");
    }

    /* strcpy — copiar */
    char copia[20];
    strcpy(copia, saludo);

    /* sprintf — formatear a string */
    char resultado[50];
    sprintf(resultado, "Edad: %d", 25);
    printf("%s\n", resultado);

    return 0;
}

Ejercicio 3

Escribe las siguientes funciones de manipulación de strings sin usar <string.h>:

  • mi_strlen(const char *s) → longitud del string
  • mi_strcpy(char *dest, const char *src) → copia src en dest
  • mi_strcmp(const char *a, const char *b) → 0 si iguales, < 0 o > 0 si no

9. Errores Comunes

Error 1 — Buffer overflow con scanf

char nombre[10];

/* Peligroso: si el usuario escribe más de 9 caracteres, desbordamiento */
scanf("%s", nombre);

/* Siempre limita el tamaño */
scanf("%9s", nombre);  /* máximo 9 caracteres + \0 */

Error 2 — Usar una variable sin inicializar

int suma;
/* suma contiene basura — lo que había en memoria */
for (int i = 0; i < 5; i++) suma += i;  /* resultado impredecible */

/* Siempre inicializa */
int suma = 0;

Error 3 — División entera inesperada

int total = 7, personas = 2;

/* Resultado: 3 (no 3.5) */
double por_persona = total / personas;

/* Castea antes de dividir */
double por_persona = (double)total / personas;  /* 3.5 */

Error 4 — Confundir = con == en condicionales

int x = 5;

/* Asigna 0 a x y evalúa como false — siempre */
if (x = 0) { ... }

/* Comparación */
if (x == 0) { ... }

/* Truco defensivo: poner la constante primero (Yoda conditions) */
if (0 == x) { ... }  /* si escribes = en vez de ==, el compilador da error */

Resumen

Concepto C
Tipos principales int, double, float, char
String char nombre[N] terminado en \0
Entrada scanf("%d", &var)
Salida printf("%d\n", var)
Función tipo nombre(params) { return val; }
Array int arr[N] con índices 0..N-1
Sin retorno void nombre(...)

Próximos pasos

  • Punteros: el concepto más poderoso (y peligroso) de C
  • Estructuras: crear tus propios tipos de datos con struct
  • Gestión de memoria: malloc, free y por qué importa