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 stringmi_strcpy(char *dest, const char *src)→ copiasrcendestmi_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,freey por qué importa