Estructuras de Control y Modularidad

2.10 · Modularidad: Scope, Paso por Valor/Referencia y Multi-archivo

PF-210 ⏱ 90 min ⭐ 120 XP intermedio

🎯 Objetivo de aprendizaje

Al terminar este tema vas a poder:

  1. Explicar qué es el scope (alcance) de variables en C++: local, global, de bloque.
  2. Pasar argumentos por valor y entender que se hace una copia.
  3. Pasar argumentos por referencia con & y modificar el valor original.
  4. Pasar argumentos por puntero con * (introducción).
  5. Organizar un programa en múltiples archivos .cpp y .h.
  6. Usar prototipos en archivos de cabecera.
  7. Compilar proyectos multi-archivo con g++ archivo1.cpp archivo2.cpp -o programa.

La modularidad es lo que diferencia a un programa de "tarea" de un programa "profesional". Con módulos, tu código se vuelve mantenible, testeable y reutilizable. Es la base de cualquier proyecto serio.

🧠 Teoría y conceptos — 2.10.1 — El scope de variables: local, global, de bloque

El scope de variables: local, global, de bloque
El scope de variables: local, global, de bloque

Los 3 tipos de scope en C++

Ejemplo: los 3 scopes

#include <iostream>
using namespace std;

int globalVar = 100; // GLOBAL

void miFuncion() {

int localVar = 50; // LOCAL

cout << "Global: " << globalVar << endl;  // OK
cout << "Local: " << localVar << endl;    // OK
// cout << bloqueVar;  // ❌ No existe aquí

}

int main() {

int mainVar = 10; // LOCAL a main

if (true) {

int bloqueVar = 5; // DE BLOQUE

cout << "Global: " << globalVar << endl;  // OK
cout << "Main: " << mainVar << endl;      // OK
cout << "Bloque: " << bloqueVar << endl;   // OK

}

// cout << bloqueVar;  // ❌ No existe aquí
cout << "Main: " << mainVar << endl;          // OK

miFuncion();

return 0;

}

Diagrama de scopes anidados

┌────────────────────────┐

│ GLOBAL │

│ (todo el programa) │

│ globalVar = 100 │

│ │

│ ┌──────────────┐ │

│ │ MAIN │ │

│ │ mainVar=10 │ │

│ │ │ │

│ │ ┌────────┐ │ │

│   │  │ if {}  │  │     │

│ │ │bloqueV │ │ │

│ │ │=5 │ │ │

│ │ └────────┘ │ │

│ │ │ │

│ │ ┌──────────┐│ │

│ │ │miFuncion ││ │

│ │ │localV=50 ││ │

│ │ └──────────┘│ │

│ └──────────────┘ │

└────────────────────────┘

Lo que muestra: cada bloque "contiene" a los más internos. Un bloque interno puede ver las variables de los bloques externos, pero NO al revés. Esto se llama regla del scope anidado.

Variables con el mismo nombre en scopes diferentes

int x = 10; // Global

void foo() {

int x = 5; // Local, OCULTA a la global

cout << x;  // 5

}

int main() {
cout << x;  // 10 (la global)

foo();

return 0;

}

La regla: la variable local "tapa" a la global. Para acceder a la global explícitamente, usa el operador :::

int x = 10; // Global

void foo() {

int x = 5; // Local

cout << ::x;  // 10 (la global, accedida con ::)
cout << x;   // 5 (la local)

}

Cuaderno físico (obligatorio)

los 3 tipos de scope con un ejemplo propio. Memorízalos: local, global, de bloque.

2.10.2 — Paso por valor: la copia segura

Paso por valor: la copia segura
Paso por valor: la copia segura

Recordatorio

Ya vimos esto en el Tema 2.9. Por defecto, en C++ los argumentos se pasan por valor: la función recibe una COPIA, modificar el parámetro NO afecta al original.

void incrementar(int x) {

x++; // Solo modifica la copia

}

int main() {

int n = 5;

incrementar(n);

cout << n;  // 5, NO 6

}

Cuándo usar paso por valor

✅ Cuando la función no necesita modificar el argumento. ✅ Para tipos pequeños (int, double, char, bool). ✅ Por defecto, si no estás seguro.

Cuaderno físico (obligatorio)

por qué el paso por valor es "seguro". Da un ejemplo de cuándo es problemático.

2.10.3 — Paso por referencia con &: modificar el original

Paso por referencia con &: modificar el original
Paso por referencia con &: modificar el original

El concepto

Si quieres que la función modifique el valor original, pasas el argumento por referencia con &. La función trabaja directamente sobre la variable original, no una copia.

void incrementar(int& x) { // & indica paso por referencia

x++; // Modifica la variable original

}

int main() {

int n = 5;

incrementar(n);

cout << n;  // 6, NO 5

}

Ejemplo: intercambiar dos valores

void intercambiar(int& a, int& b) {

int temp = a;

a = b;

b = temp;

}

int main() {

int x = 5, y = 3;

cout << "Antes: x=" << x << ", y=" << y << endl;  // x=5, y=3

intercambiar(x, y);

cout << "Después: x=" << x << ", y=" << y << endl;  // x=3, y=5

}

Sin referencias, esto sería imposible.

Ejemplo: leer múltiples valores con validación

void leerEdad(int& edad) {

do {

cout << "Edad (0-150): ";
cin >> edad;
} while (edad < 0 || edad > 150);

}

int main() {

int edad;

leerEdad(edad); // Por referencia: modifica `edad` directamente

cout << "Edad válida: " << edad << endl;

}

Paso por referencia const (solo lectura, sin copia)

A veces quieres eficiencia de referencia (no copia) pero no necesitas modificar. Usa const:

void imprimir(const string& texto) { // No copia, no modifica

cout << texto << endl;

}

int main() {

string s = "Hola mundo";

imprimir(s); // Eficiente (sin copiar el string completo)

}

const le dice al compilador "prometo no modificar esto", y si intentas, da error.

Cuaderno físico (obligatorio)

la diferencia entre paso por valor, por referencia (`&`) y por referencia constante (`const &`). Da un ejemplo de cada uno.

2.10.4 — Paso por puntero con *: alternativa a la referencia

Paso por puntero con *: alternativa a la referencia
Paso por puntero con *: alternativa a la referencia

El concepto

Un puntero es una variable que almacena la dirección de memoria de otra variable. En lugar de pasar la variable, pasas su dirección.

void incrementar(int* x) { // * indica que x es un puntero a int

(*x)++; // Desreferenciar: acceder al valor en esa dirección

}

int main() {

int n = 5;

incrementar(&n); // &n es la dirección de n

cout << n;  // 6

}

Comparación: puntero vs referencia

En C++ moderno, prefiere referencias sobre punteros (más seguro). Pero en el Capítulo 3 veremos punteros porque son la base de los arreglos dinámicos y la memoria.

Cuándo usar punteros

✅ Cuando necesitas una variable que puede no apuntar a nada (nullptr). ✅ Cuando interactúas con código C legacy. ✅ Cuando trabajas con memoria dinámica (lo veremos en el Cap 3).

Cuaderno físico (obligatorio)

las 3 formas de paso (valor, referencia, puntero) y cuándo usar cada una.

2.10.5 — Cuándo usar cada tipo de paso

Cuándo usar cada tipo de paso
Cuándo usar cada tipo de paso

Regla de decisión

¿La función necesita MODIFICAR el argumento?

├── NO

│ ├── ¿El argumento es un tipo grande (string, struct)?

│ │ ├── SÍ → const referencia (eficiencia)

│ │ └── NO → valor (simpleza)

│ └──

└── SÍ

├── ¿El argumento PUEDE ser null?

│ ├── SÍ → puntero

│ └── NO → referencia

Tabla resumen

2.10.6 — Archivos de cabecera (.h): declarar funciones

Archivos de cabecera (.h): declarar funciones
Archivos de cabecera (.h): declarar funciones

El problema

En el Tema 2.9 vimos los prototipos. En programas con muchas funciones, tener todos los prototipos en el mismo archivo .cpp se vuelve inmanejable.

La solución: archivos .h

Un archivo .h (header) contiene declaraciones (prototipos, constantes, tipos) que otros archivos pueden incluir.

// math_utils.h — Declaraciones

#ifndef MATH_UTILS_H  // Guardas de inclusión

#define MATH_UTILS_H

int sumar(int a, int b);

int restar(int a, int b);

const double PI = 3.14159;

#endif

Las guardas de inclusión (#ifndef, #define, #endif) evitan que el archivo se incluya múltiples veces, lo que causaría errores de "declaración duplicada".

Uso en otro archivo

// main.cpp — Implementación + uso

#include <iostream>
#include "math_utils.h"  // Incluir nuestro header
using namespace std;

// Implementaciones de las funciones declaradas en math_utils.h

int sumar(int a, int b) { return a + b; }
int restar(int a, int b) { return a - b; }
int main() {
cout << sumar(3, 5) << endl;  // 8
cout << restar(10, 3) << endl;  // 7
return 0;

}

Cuaderno físico (obligatorio)

la estructura de un archivo `.h` con guardas de inclusión. Memorízala.

2.10.7 — Multi-archivo: separar .h y .cpp

Estructura profesional

mi_proyecto/

├── main.cpp

├── math_utils.h

├── math_utils.cpp

├── string_utils.h

└── string_utils.cpp

Ejemplo: proyecto "Calculadora" multi-archivo

math_utils.h:

#ifndef MATH_UTILS_H

#define MATH_UTILS_H

int sumar(int a, int b);

int restar(int a, int b);

int multiplicar(int a, int b);

double dividir(double a, double b);

#endif

math_utils.cpp:

#include "math_utils.h"
int sumar(int a, int b) { return a + b; }
int restar(int a, int b) { return a - b; }
int multiplicar(int a, int b) { return a * b; }

double dividir(double a, double b) {

if (b == 0) return 0;  // O lanzar error
return a / b;

}

main.cpp:

#include <iostream>
#include "math_utils.h"
using namespace std;
int main() {
cout << "5 + 3 = " << sumar(5, 3) << endl;
cout << "10 - 4 = " << restar(10, 4) << endl;
cout << "6 * 7 = " << multiplicar(6, 7) << endl;
cout << "15 / 4 = " << dividir(15, 4) << endl;
return 0;

}

Ventajas del multi-archivo

Cuaderno físico (obligatorio)

la estructura de un proyecto multi-archivo. Dibuja el diagrama de dependencias.

2.10.8 — Compilar proyectos multi-archivo

Compilar con g++

g++ main.cpp math_utils.cpp -o programa.exe

El comando le dice a g++: "compila main.cpp y math_utils.cpp, vincúlalos en un solo ejecutable llamado programa.exe".

El proceso

  1. g++ compila cada .cpp por separado a un archivo objeto (.o).
  2. El linker une los archivos objeto y las librerías.
  3. Genera el ejecutable final.

Errores comunes

Error: "undefined reference to sumar"

Si declaraste sumar en math_utils.h pero no la implementaste en math_utils.cpp (o no incluiste math_utils.cpp en la compilación), el linker no encuentra la definición.

Solución: asegúrate de:

  • Implementar la función en el .cpp.
  • Incluir el .cpp en la línea de compilación.

Error: "redefinition of sumar"

Si implementaste sumar en math_utils.cpp Y en main.cpp (olvidaste las guardas de inclusión).

Solución: implementa cada función en un solo .cpp, y en los demás solo pon el prototipo (en el .h).

Script de compilación (Makefile básico)

# Makefile

programa: main.o math_utils.o

g++ main.o math_utils.o -o programa

main.o: main.cpp math_utils.h

g++ -c main.cpp

math_utils.o: math_utils.cpp math_utils.h

g++ -c math_utils.cpp

clean:

rm *.o programa

Para compilar: make. Para limpiar: make clean.
Cuaderno físico (obligatorio)

el comando de compilación multi-archivo. Memorízalo: `g++ archivo1.cpp archivo2.cpp -o programa`.

🤖 IA como copiloto — 🤖 AI Mission 2.10

Prompt 1: "Te voy a pegar un programa multi-archivo en C++ que tiene errores de compilación. NO me los corrijas. Solo dime: (1) qué errores ves, (2) qué archivos faltan o están mal, (3) cómo solucionarlo. Máximo 150 palabras."

Prompt 2: "Tengo un programa con 3 archivos: main.cpp, utils.h, utils.cpp. Dame el comando exacto para compilarlos todos juntos en un ejecutable llamado app.exe. Explícame qué hace cada parte del comando. Máximo 100 palabras."

🧪 Laboratorio — 🧪 LAB 2.10 — 6 ejercicios de modularidad

🛠️ Herramienta recomendada para los diagramas de flujo de este LAB: Para los ejercicios que pidan un diagrama de flujo, usa [draw.io (diagrams.net)](https://app.diagrams.net/) — herramienta gratis, sin registro, que funciona en el navegador. Tiene plantillas con la simbología ANSI/ISO lista (en `General → Flowchart`). Exporta tus diagramas como PNG e insértalos en tu cuaderno digital o entrégalos junto con el código.

Ejercicio 1 — Intercambiar dos valores (básico, 15 min)

Enunciado. Crea una función void intercambiar(int& a, int& b) que intercambie dos valores. Llámala desde main y muestra el antes y el después.

Rúbrica (10 pts): función (5 pts) + main (3 pts) + verificación (2 pts).

Ejercicio 2 — Incrementar un contador (básico, 15 min)

Enunciado. Crea una función void incrementar(int& contador, int cantidad) que incremente el contador por la cantidad. Verifica con 5 llamadas que el contador suma correctamente.

Rúbrica (10 pts): función (4 pts) + 5 tests (4 pts) + verificación (2 pts).

Ejercicio 3 — Encontrar mayor y menor (básico, 20 min)

Enunciado. Crea una función void encontrarExtremos(const int arr[], int n, int& mayor, int& menor) que encuentre el mayor y el menor de un arreglo. El arreglo se pasa por referencia constante (no se copia), los resultados por referencia.

Rúbrica (10 pts): función (5 pts) + main con arreglo de prueba (3 pts) + output correcto (2 pts).

Ejercicio 4 — Proyecto multi-archivo "Calculadora" (intermedio, 30 min)

Enunciado. Crea un proyecto con 3 archivos:

  • math_utils.h: prototipos de sumar, restar, multiplicar, dividir.
  • math_utils.cpp: implementaciones.
  • main.cpp: menú con las 4 operaciones.

Compila con g++ main.cpp math_utils.cpp -o calculadora.exe.

Rúbrica (10 pts): 3 archivos correctos (5 pts) + menú funcional (3 pts) + compilación (2 pts).

Ejercicio 5 — Proyecto multi-archivo "Estudiante" (avanzado, 30 min)

Enunciado. Crea un proyecto con 4 archivos:

  • estudiante.h: struct Estudiante (nombre, edad, promedio) + prototipos.
  • estudiante.cpp: funciones para crear, imprimir, calcular promedio.
  • main.cpp: crea 3 estudiantes, calcula e imprime sus promedios.
  • Compila y ejecuta.

Rúbrica (10 pts): 3 archivos correctos (5 pts) + struct bien definido (2 pts) + compilación (2 pts) + output (1 pt).

Ejercicio 6 — Validación por referencia (avanzado, 30 min)

Enunciado. Crea una función bool validarRango(int& valor, int min, int max) que pida al usuario un valor hasta que esté en el rango. Retorna true si el valor final es válido.

Rúbrica (10 pts): función (5 pts) + uso en main (3 pts) + tests (2 pts).

Ejercicio bonus — Proyecto con 5 archivos (opcional, 30 min)

Enunciado. Crea un proyecto con 5 archivos: main.cpp, menu.h/cpp, operaciones.h/cpp. El menú llama a operaciones que están en archivos separados. Compila todo en un solo ejecutable.

Rúbrica bonus (5 pts): 5 archivos (2 pts) + compilación exitosa (2 pts) + funcionalidad (1 pt).

🎯 Cierre del tema

Lo que aprendiste hoy:

  • Los 3 tipos de scope: local, global, de bloque.
  • Las 3 formas de paso: por valor, por referencia, por puntero.
  • Cuándo usar cada tipo de paso (regla de decisión).
  • Archivos de cabecera .h con guardas de inclusión.
  • Estructura de proyectos multi-archivo profesionales.
  • Comando para compilar multi-archivo con g++.
  • Comparación con Java y Python.

Lo que sigue: el Boss Final 2 — Sistema de Gestión de Biblioteca (8h), el proyecto integrador del capítulo. Vas a aplicar TODO lo aprendido: condicionales, bucles, patrones, funciones, modularidad, testing. Es el examen final del capítulo y tu segundo proyecto para tu portafolio.

XP obtenida al completar este tema: 100 base + 30 por LAB (6 ejercicios) + 20 por Quest = 150 XP.

*"Un programa sin modularidad es un cuarto desordenado. Un programa con módulos es una casa bien organizada: cada cosa en su lugar, y cuando necesitas algo, sabes dónde encontrarlo."* — De la introducción de este manual.

Fin del Tema 2.10

📝 Quest · Cuestionario — 📝 Quest — Reactivos para autoevaluación

Las siguientes preguntas te sirven para autoevaluarte después de leer el tema. Responde en tu cuaderno o mentalmente, y luego revisa las Respuestas modelo (disponibles en libre acceso, sin iniciar sesión).

Sección A · Opción múltiple

Elige la opción correcta (A, B, C o D). Las respuestas están en la sección Respuestas modelo.

  1. ¿Qué es el scope de una variable?
    • Su tipo
    • La región del código donde la variable es visible
    • Su valor
    • Su nombre
  2. Una variable local existe:
    • En todo el programa
    • Solo dentro de la función donde se declara
    • En otros archivos
    • Siempre
  3. Una variable global existe:
    • Solo en una función
    • En todo el archivo (o más si es extern)
    • Solo en main
    • Solo en clases
  4. Paso por valor significa:
    • Se pasa la variable original
    • Se pasa una copia; cambios no afectan al original
    • Se pasa un puntero
    • Se pasa la referencia
  5. Paso por referencia significa:
    • Copia
    • Se pasa el original; cambios SÍ afectan al original
    • Constante
    • Puntero null
  6. En C++, ¿cómo se declara paso por referencia?
    • tipo funcion(tipo x)
    • tipo funcion(tipo& x)
    • tipo funcion(tipo* x)
    • tipo funcion(const tipo x)
  7. ¿Qué es multi-archivo en C++?
    • Un solo archivo
    • Dividir el código en varios .cpp/.h para mejor organización
    • Compilar varias veces
    • Comprimir archivos
  8. ¿Para qué sirve un header (.h)?
    • Compilar
    • Declarar funciones/clases que otros archivos usarán
    • Ejecutar
    • Es un comentario
  9. ¿Qué hace #ifndef X / #define X / #endif?
    • Compila
    • Evita que un header se incluya múltiples veces
    • Ejecuta
    • Renombra
  10. En C++, ¿qué archivo implementa una función declarada en funciones.h?
    • funciones.h
    • funciones.cpp
    • main.cpp solamente
    • Cualquiera

Sección B · Preguntas abiertas

Desarrolla tu respuesta en al menos 3 líneas. Compara con la respuesta modelo después de escribir.

  1. Explica la diferencia entre variable local, global y estática. Da un ejemplo de cada una.
    → Escribe tu respuesta aquí (en tu cuaderno o mentalmente)…
  2. ¿Por qué las variables globales son generalmente una mala práctica?
    → Escribe tu respuesta aquí (en tu cuaderno o mentalmente)…
  3. Diseña un sistema multi-archivo en C++ para una calculadora: calculadora.h (interfaz), calculadora.cpp (implementación), main.cpp (uso). Explica qué va en cada archivo.
    → Escribe tu respuesta aquí (en tu cuaderno o mentalmente)…

🟢 Cuando termines, revisa las Respuestas modelo y compáralas con las tuyas. La mejor forma de aprender es discutir cada respuesta contigo mismo o con un compañero.

← Volver a temas del capítulo 📚 Ver todos los temas 🔑 Inicia sesión para hacer el Test