Estructuras de Datos

0x0042 INICIALIZANDO...
0x7ffe-stack
*p = malloc(sizeof(Node))
0x3b2a - heap
ptr -> next = NULL
Unidad 02

Fundamentos del Lenguaje C

El lenguaje C prioriza el control directo del programador sobre el hardware para lograr un alto rendimiento. A diferencia de lenguajes modernos, C ofrece pocas abstracciones, lo que permite traducir el código a muy pocas instrucciones de máquina, ideal para sistemas operativos y controladores.

2.1 Filosofía de C: Control y Disciplina
Mirar en YouTube

C confía plenamente en el programador. No verifica límites de arreglos ni punteros automáticamente, lo que brinda gran poder pero exige disciplina. Un error simple puede causar fallos graves (segmentation faults), por lo que aprender buenas prácticas desde el inicio es crucial.

C es de tipado estático: el tipo de cada variable (int, float, char) se define al compilar y no cambia. Esto permite al compilador optimizar el código y detectar errores de incompatibilidad antes de ejecutar el programa.

main.cpp
int edad = 20;
float estatura = 1.75;
char inicial = 'J';

// Error detectado al compilar:
// edad = "hola";
int edad = 20;
float estatura = 1.75;
char inicial = 'J';

// Error detectado al compilar:
// edad = "hola";

Como lenguaje estructurado, C utiliza if/else, switch, while y for para controlar el flujo. No existe un tipo booleano nativo clásico; el 0 es falso y cualquier otro valor es verdadero.

main.cpp
int i;
for (i = 1; i <= 5; i++) {
if (i % 2 == 0) {
printf("%d es par\n", i);
} else {
printf("%d es impar\n", i);
}
}
int i;
for (i = 1; i <= 5; i++) {
if (i % 2 == 0) {
printf("%d es par\n", i);
} else {
printf("%d es impar\n", i);
}
}
2.2 Tipado Estático y Structs
Mirar en YouTube

En C, la gestión de memoria es manual. Las variables locales viven en el "Stack" y se limpian solas al salir de la función. La memoria dinámica vive en el "Heap" y el programador debe reservarla y liberarla manualmente.

2.3 (Parte 1) Memoria: El Stack
Mirar en YouTube

Un puntero es una variable que almacena una dirección de memoria. Usamos "&" para obtener la dirección de una variable y "*" para acceder al valor guardado en esa dirección. Es vital inicializarlos (o usar NULL) para evitar errores graves.

2.3 (Parte 2) Punteros y Referencias
Mirar en YouTube

Para datos que deben sobrevivir fuera de una función, usamos "malloc" (reservar) y "free" (liberar). Si no liberamos la memoria con free, creamos fugas de memoria (memory leaks).

main.cpp
int *arreglo = malloc(5 * sizeof(int));

if (arreglo == NULL) {
return 1; // Error de asignación
}

for (int i = 0; i < 5; i++) {
arreglo[i] = i * 2;
}

free(arreglo); // ¡Importante liberar!
int *arreglo = malloc(5 * sizeof(int));

if (arreglo == NULL) {
return 1; // Error de asignación
}

for (int i = 0; i < 5; i++) {
arreglo[i] = i * 2;
}

free(arreglo); // ¡Importante liberar!

C pasa todo por valor (copia). Para modificar una variable original dentro de una función, pasamos su dirección (puntero). Esto simula el "paso por referencia".

main.cpp
void intercambiar(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}

// Uso:
// intercambiar(&x, &y);
void intercambiar(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}

// Uso:
// intercambiar(&x, &y);

Para organizar programas grandes, separamos el código en archivos de cabecera (.h) para declaraciones y archivos fuente (.c) para implementaciones. Esto permite reutilización y compilación separada.

main.cpp
// aritmetica.h
#ifndef ARITMETICA_H
#define ARITMETICA_H

int sumar(int a, int b);
int restar(int a, int b);

#endif
// aritmetica.h
#ifndef ARITMETICA_H
#define ARITMETICA_H

int sumar(int a, int b);
int restar(int a, int b);

#endif
main.cpp
// aritmetica.c
#include "aritmetica.h"

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

int restar(int a, int b) {
return a - b;
}
// aritmetica.c
#include "aritmetica.h"

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

int restar(int a, int b) {
return a - b;
}
main.cpp
// main.c
#include <stdio.h>
#include "aritmetica.h"

int main() {
int x = 10, y = 4;
printf("Suma: %d", sumar(x, y));
return 0;
}
// main.c
#include <stdio.h>
#include "aritmetica.h"

int main() {
int x = 10, y = 4;
printf("Suma: %d", sumar(x, y));
return 0;
}

Cada archivo .c se compila a un objeto (.o) independiente. Luego, el "Linker" une todos los objetos para crear el ejecutable final, resolviendo las referencias entre ellos.

Imagen 10. Flujo Compilación y Linker

Imagen 10. Flujo Compilación y Linker

Diagrama del proceso de compilación separada y enlazado (Linking).

Usamos "#include" para insertar cabeceras y "extern" para compartir variables globales entre archivos. Es fundamental para proyectos complejos.

2.4 Modularidad y Operadores
Mirar en YouTube

Las funciones son bloques de código reutilizables fundamentales en la programación estructurada. Permiten dividir problemas complejos en tareas específicas (como printf o main) y evitar la repetición de código.

Es vital distinguir entre la "Declaración" (prototipo), que indica al compilador el nombre y tipos de la función antes de usarla, y la "Definición", que contiene el código real. Es buena práctica poner prototipos en archivos .h.

main.cpp
// Declaración (Prototipo)
int maximo(int a, int b);

// Definición
int maximo(int a, int b) {
if (a > b) return a;
else return b;
}
// Declaración (Prototipo)
int maximo(int a, int b);

// Definición
int maximo(int a, int b) {
if (a > b) return a;
else return b;
}

Por defecto, C usa "paso por valor". Esto significa que la función recibe una COPIA de las variables. Modificar los parámetros dentro de la función NO afecta a las variables originales fuera de ella.

main.cpp
int x = 10, y = 3;
int mayor = maximo(x, y);

// Aunque dentro de maximo se cambien a o b,
// x e y siguen valiendo 10 y 3 aquí.
int x = 10, y = 3;
int mayor = maximo(x, y);

// Aunque dentro de maximo se cambien a o b,
// x e y siguen valiendo 10 y 3 aquí.

Para que una función modifique variables externas, usamos punteros. Pasamos la dirección de memoria (&variable) y la función opera sobre el valor apuntado (*p). Esto simula el paso por referencia.

main.cpp
void incrementarUno(int *p) {
(*p)++; // Modifica el valor en la dirección p
}

// Uso:
int z = 7;
incrementarUno(&z);
// Ahora z vale 8
void incrementarUno(int *p) {
(*p)++; // Modifica el valor en la dirección p
}

// Uso:
int z = 7;
incrementarUno(&z);
// Ahora z vale 8
2.5 Funciones: Valor vs Referencia
Mirar en YouTube

Las variables locales mueren al terminar la función. C permite la recursividad: una función que se llama a sí misma. Es útil para problemas matemáticos como el factorial.

main.cpp
int factorial(int n) {
if (n <= 1) {
return 1; // Caso base
} else {
return n * factorial(n - 1); // Llamada recursiva
}
}
int factorial(int n) {
if (n <= 1) {
return 1; // Caso base
} else {
return n * factorial(n - 1); // Llamada recursiva
}
}
Fundamentos del Lenguaje C | Estructuras de Datos | Estructuras de Datos | Pensamiento Algorítmico