Programación · Lenguajes de programación
Tutorial de programación en C para principiantes
Respuesta corta
C es un lenguaje compilado, pequeño y rápido, que da acceso directo a la memoria mediante punteros. Escribe main en un archivo .c, compílalo con gcc hello.c -o hello y ejecuta ./hello. Las ideas centrales son los tipos de tamaño fijo, las funciones, los arrays y las cadenas terminadas en nulo, los punteros y las direcciones, la gestión manual de memoria con malloc y free, y el manejo de errores mediante valores de retorno.
Qué es C y para qué se usa
C fue creado por Dennis Ritchie en Bell Labs a principios de los años setenta para escribir el sistema operativo Unix. Está estandarizado por ISO (C99, C11, C17 y C23 son las ediciones principales). C compila a código máquina con muy poco entorno de ejecución, por lo que funciona casi en cualquier parte. Se usa en núcleos de sistemas operativos (Linux), firmware embebido, bases de datos (SQLite), entornos de ejecución de lenguajes (CPython está escrito en C) y bibliotecas de alto rendimiento. Aprender C también enseña cómo funcionan realmente la memoria y los ordenadores.
Instalar un compilador y ejecutar Hello, world
En Linux instala gcc (por ejemplo sudo apt install build-essential); en macOS ejecuta xcode-select --install para obtener clang (también disponible como gcc); en Windows usa MSYS2, WSL o Visual Studio.
#include <stdio.h>
int main(void) {
printf("Hello, world\n");
return 0;
}gcc -Wall -Wextra -std=c17 hello.c -o hello
./hello
# Hello, world#include incorpora declaraciones de un archivo de cabecera, aquí la biblioteca estándar de entrada y salida. Devolver 0 desde main significa éxito. Compila siempre con las advertencias activadas (-Wall -Wextra).
Variables y tipos básicos
Cada variable tiene un tipo que fija su tamaño y significado. Los tamaños dependen de la plataforma, salvo donde el estándar diga otra cosa; <stdint.h> ofrece tipos de ancho exacto como int32_t.
| Tipo | Tamaño típico | Ejemplo | Formato de printf |
|---|---|---|---|
char | 1 byte | char c = 'A'; | %c |
int | 4 bytes | int n = 42; | %d |
long long | 8 bytes | long long big = 1LL << 40; | %lld |
double | 8 bytes | double x = 3.14; | %f |
bool | 1 byte | bool ok = true; (C99 <stdbool.h>) | %d |
size_t | 8 bytes en 64 bits | size_t len = sizeof(arr); | %zu |
#include <stdio.h>
int main(void) {
int count = 7;
double avg = count / 2; /* 3.0: integer division first */
double avg2 = count / 2.0; /* 3.5 */
printf("%d %.1f %.1f\n", count, avg, avg2); /* 7 3.0 3.5 */
printf("%zu\n", sizeof(int)); /* 4 on most systems */
return 0;
}Control de flujo y funciones
Una función debe declararse antes de usarse. Los argumentos se pasan por valor: la función recibe una copia.
#include <stdio.h>
int square(int x) {
return x * x;
}
int main(void) {
int score = 72;
if (score >= 90) {
printf("A\n");
} else if (score >= 70) {
printf("B\n"); /* B */
} else {
printf("C\n");
}
for (int i = 0; i < 3; i++) {
printf("%d ", square(i)); /* 0 1 4 */
}
printf("\n");
int n = 10;
while (n > 1) n /= 2;
printf("%d\n", n); /* 1 */
return 0;
}C también tiene do/while, switch con etiquetas case (recuerda el break) y goto, que se usa sobre todo para la limpieza en rutas de error.
Arrays y cadenas
Un array es un número fijo de elementos almacenados contiguos. C no comprueba los límites ni guarda la longitud, así que la longitud se pasa por separado. Una cadena es un array de char que termina con un byte cero '\0'.
#include <stdio.h>
#include <string.h>
int sum(const int *values, size_t n) {
int total = 0;
for (size_t i = 0; i < n; i++) total += values[i];
return total;
}
int main(void) {
int nums[] = {3, 1, 2, 5};
size_t n = sizeof nums / sizeof nums[0];
printf("%zu items, sum %d\n", n, sum(nums, n)); /* 4 items, sum 11 */
char name[16] = "Ada";
strncat(name, " Lovelace", sizeof name - strlen(name) - 1);
printf("%s (%zu chars)\n", name, strlen(name)); /* Ada Lovelace (12 chars) */
return 0;
}Cuando se pasa un array a una función, decae a un puntero a su primer elemento, por eso sum recibe const int * y una longitud.
Característica distintiva: los punteros
Un puntero contiene la dirección de memoria de otro valor. &x da la dirección de x, y *p lee o escribe el valor en la dirección p. Los punteros permiten que las funciones modifiquen las variables de quien las llama, compartan datos grandes sin copiarlos y construyan estructuras de datos enlazadas.
#include <stdio.h>
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
int main(void) {
int x = 1, y = 2;
int *p = &x;
*p = 10; /* changes x */
swap(&x, &y);
printf("x=%d y=%d\n", x, y); /* x=2 y=10 */
int arr[3] = {7, 8, 9};
int *q = arr;
printf("%d %d\n", *(q + 1), q[2]); /* 8 9: pointer arithmetic */
return 0;
}Memoria dinámica con malloc y free
Las variables locales viven en la pila (stack) y desaparecen cuando la función retorna. Para datos cuyo tamaño solo se conoce en tiempo de ejecución, o que deben sobrevivir a la función, reserva memoria en el heap con malloc y libérala con free. Cada malloc que tiene éxito necesita exactamente un free.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t n = 5;
int *squares = malloc(n * sizeof *squares);
if (squares == NULL) {
fprintf(stderr, "out of memory\n");
return 1;
}
for (size_t i = 0; i < n; i++) squares[i] = (int)(i * i);
printf("%d\n", squares[4]); /* 16 */
free(squares);
squares = NULL; /* avoid using a dangling pointer */
return 0;
}Funciones relacionadas son calloc (reserva y pone a cero) y realloc (cambia el tamaño). Agrupa valores relacionados con un struct, por ejemplo struct point { int x; int y; };.
Manejo de errores
C no tiene excepciones. Las funciones informan de los errores mediante valores de retorno (a menudo -1, NULL o un código de estado), y muchas funciones de la biblioteca también establecen la variable global errno. Comprueba todo valor de retorno que pueda fallar.
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main(void) {
FILE *f = fopen("missing.txt", "r");
if (f == NULL) {
fprintf(stderr, "cannot open: %s\n", strerror(errno));
return 1; /* cannot open: No such file or directory */
}
fclose(f);
return 0;
}Un programa completo pequeño: contador de líneas y palabras
Este programa funciona como un pequeño wc: lee un archivo indicado en la línea de comandos e imprime el número de líneas, palabras y bytes.
#include <ctype.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "usage: %s FILE\n", argv[0]);
return 1;
}
FILE *f = fopen(argv[1], "r");
if (f == NULL) {
perror(argv[1]);
return 1;
}
long lines = 0, words = 0, bytes = 0;
int c, in_word = 0;
while ((c = fgetc(f)) != EOF) {
bytes++;
if (c == '\n') lines++;
if (isspace(c)) {
in_word = 0;
} else if (!in_word) {
in_word = 1;
words++;
}
}
fclose(f);
printf("%ld lines, %ld words, %ld bytes\n", lines, words, bytes);
return 0;
}Compila con gcc -Wall -Wextra wc.c -o wc y ejecuta ./wc notes.txt. Observa que c es un int, no un char, para que pueda contener el valor especial EOF.
Errores comunes de principiantes
- Usar
=en lugar de==en una condición. Las advertencias (-Wall) detectan la mayoría de los casos. - Olvidar dejar espacio para el
'\0'final al dimensionar un búfer de cadena. - Usar
getso unscanf("%s", buf)sin límite. Usa en su lugarfgets(buf, sizeof buf, stdin). - Devolver un puntero a una variable local, que ya no existe cuando la función retorna.
- Que los formatos de
printfno coincidan con los tipos, como%dpara unsize_t.
Adónde ir después
Usa la sección de C de cppreference.com como referencia del lenguaje y de la biblioteca estándar, y lee las páginas del grupo de trabajo de ISO (WG14) para el estándar en sí. Practica con structs, listas enlazadas, archivos de cabecera y programas de varios archivos construidos con make. Un depurador (gdb o lldb) y Valgrind o AddressSanitizer te ahorrarán horas. Después explora C++ o Rust, que se basan en las mismas ideas con más seguridad.
Preguntas frecuentes
¿Sigue valiendo la pena aprender C?
Sí. Se sigue usando en sistemas operativos, dispositivos embebidos y bibliotecas críticas para el rendimiento, y enseña memoria, punteros y cómo se ejecuta el código compilado, lo que ayuda en cualquier otro lenguaje.
¿Cuál es la diferencia entre C y C++?
C++ nació como una extensión de C y añade clases, plantillas, referencias, excepciones y una amplia biblioteca estándar. C sigue siendo pequeño y procedimental. La mayor parte del código C se parece mucho a C++ válido, pero son estándares distintos.
¿Por qué mi programa en C falla con un segmentation fault?
Accedió a memoria que no le pertenece, normalmente a través de un puntero nulo, sin inicializar o ya liberado, o indexando más allá del final de un array. Ejecútalo en un depurador o con AddressSanitizer para encontrar la línea exacta.
¿Qué estándar de C debo usar?
C17 está ampliamente soportado por los compiladores actuales, y C23 es el estándar más reciente. Indica la versión de forma explícita, por ejemplo -std=c17, para que el comportamiento sea coherente.