Estructuras de Controlc++funcionesparámetrosreturnmodularidad

Funciones

Aprende a crear y usar funciones para organizar y reutilizar tu código

OOI Oaxaca9 de febrero de 20269 min read

¿Qué es una función?

Imagina que tienes una receta para hacer un pastel. Cada vez que quieres un pastel, no necesitas reinventar cómo hacerlo: simplemente sigues la receta. Una función es exactamente eso: una receta de código que puedes usar una y otra vez.

Una función es un bloque de código con un nombre que realiza una tarea específica. La defines una vez y la puedes llamar (usar) las veces que quieras.

Sin funciones (código repetido):

// Calcular si 7 es primo
bool esPrimo7 = true;
for (int j = 2; j * j <= 7; j++)
    if (7 % j == 0) esPrimo7 = false;

// Calcular si 13 es primo (¡el mismo código otra vez!)
bool esPrimo13 = true;
for (int j = 2; j * j <= 13; j++)
    if (13 % j == 0) esPrimo13 = false;

Con funciones (código limpio):

bool esPrimo(int n) {
    for (int j = 2; j * j <= n; j++)
        if (n % j == 0) return false;
    return n > 1;
}

// Usar la función es una sola línea
bool resultado1 = esPrimo(7);   // true
bool resultado2 = esPrimo(13);  // true
bool resultado3 = esPrimo(15);  // false

Anatomía de una función

tipo_de_retorno nombre(tipo param1, tipo param2, ...) {
    // Cuerpo de la función
    return valor;
}

Veamos cada parte:

int sumar(int a, int b) {
    int resultado = a + b;
    return resultado;
}
  • int (tipo de retorno): El tipo de dato que la función devuelve. Esta función devuelve un entero.
  • sumar (nombre): El nombre que le damos para poder llamarla.
  • int a, int b (parámetros): Los datos que la función necesita para trabajar. Son como los "ingredientes" de la receta.
  • return resultado: Devuelve el resultado al lugar donde se llamó la función.

Llamar una función

int main() {
    int x = sumar(3, 5);      // x = 8
    cout << sumar(10, 20);     // Imprime 30
    cout << sumar(sumar(1, 2), 3);  // sumar(3, 3) = 6
    return 0;
}

Funciones que no devuelven nada: void

A veces una función solo hace algo (imprimir, modificar) sin devolver un valor. Para eso usamos void:

void saludar(string nombre) {
    cout << "¡Hola, " << nombre << "!" << endl;
    // No necesita return (o puedes poner "return;" sin valor)
}

int main() {
    saludar("Carlos");   // Imprime: ¡Hola, Carlos!
    saludar("María");    // Imprime: ¡Hola, María!
    return 0;
}

Funciones que no reciben nada

void imprimirLinea() {
    cout << "========================" << endl;
}

int main() {
    imprimirLinea();
    cout << "Mi Programa" << endl;
    imprimirLinea();
    return 0;
}

Salida:

========================
Mi Programa
========================

El orden importa

En C++, una función debe estar definida antes de ser usada. Si main llama a sumar, sumar debe estar arriba de main:

// ✅ Correcto: la función está definida ANTES de main
int sumar(int a, int b) {
    return a + b;
}

int main() {
    cout << sumar(3, 5) << endl;
    return 0;
}
// ❌ Error: main usa sumar, pero sumar está después
int main() {
    cout << sumar(3, 5) << endl;  // Error: ¿qué es "sumar"?
    return 0;
}

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

Prototipos de función

Alternativamente, puedes declarar un prototipo (la firma de la función sin el cuerpo) al inicio:

// Prototipo (declaración)
int sumar(int a, int b);

int main() {
    cout << sumar(3, 5) << endl;  // Funciona porque el prototipo está arriba
    return 0;
}

// Definición completa
int sumar(int a, int b) {
    return a + b;
}

En competencias, normalmente ponemos todas las funciones arriba de main, así que no necesitamos prototipos.

Parámetros: paso por valor vs paso por referencia

Paso por valor (copia)

Por defecto, cuando pasas una variable a una función, se pasa una copia. La función trabaja con su propia copia y la variable original no cambia:

void duplicar(int x) {
    x = x * 2;
    cout << "Dentro de la función: " << x << endl;  // 10
}

int main() {
    int num = 5;
    duplicar(num);
    cout << "En main: " << num << endl;  // 5 (¡no cambió!)
    return 0;
}

Analogía: Le das una fotocopia de tu documento a alguien. Si escribe sobre la fotocopia, tu documento original no se afecta.

Paso por referencia (&)

Si quieres que la función modifique la variable original, usa &:

void duplicar(int &x) {    // Note el & antes de x
    x = x * 2;
    cout << "Dentro de la función: " << x << endl;  // 10
}

int main() {
    int num = 5;
    duplicar(num);
    cout << "En main: " << num << endl;  // 10 (¡SÍ cambió!)
    return 0;
}

Analogía: Le das tu documento original a alguien. Si escribe sobre él, tus cambios se reflejan.

¿Cuándo usar referencia?

  1. Cuando necesitas modificar la variable original:

    void intercambiar(int &a, int &b) {
        int temp = a;
        a = b;
        b = temp;
    }
    
  2. Cuando pasas algo grande (como un vector) y no quieres copiarlo (es más eficiente):

    // ❌ Lento: copia TODO el vector cada vez que llamas la función
    int sumaVector(vector<int> v) { ... }
    
    // ✅ Rápido: pasa una referencia (no copia nada)
    int sumaVector(vector<int> &v) { ... }
    
    // ✅ Aún mejor: const reference (rápido Y no lo modifica)
    int sumaVector(const vector<int> &v) { ... }
    
⚠️

En competencias, siempre pasa vectores y strings por referencia (&). Pasarlos por valor (sin &) crea una copia cada vez, lo cual puede hacer tu programa mucho más lento.

Variables locales y globales

Variables locales

Una variable declarada dentro de una función solo existe dentro de esa función:

void miFuncion() {
    int x = 10;  // x solo existe aquí
    cout << x << endl;
}

int main() {
    miFuncion();
    // cout << x;  // ❌ Error: x no existe aquí
    return 0;
}

Variables globales

Una variable declarada fuera de todas las funciones es global y accesible desde cualquier función:

int contador = 0;  // Variable global

void incrementar() {
    contador++;  // Puede acceder a la variable global
}

int main() {
    incrementar();
    incrementar();
    incrementar();
    cout << contador << endl;  // 3
    return 0;
}
💡

En competencias, es común usar variables globales para arreglos grandes (como int arr[100001]), porque las variables globales se inicializan a cero automáticamente y no tienen el límite de tamaño de las variables locales (el stack tiene límite, el segmento de datos estáticos no).

return termina la función

return no solo devuelve un valor; también termina la ejecución de la función inmediatamente:

int buscar(int arr[], int n, int objetivo) {
    for (int i = 0; i < n; i++) {
        if (arr[i] == objetivo) {
            return i;  // Encontrado: retorna la posición y TERMINA
        }
    }
    return -1;  // No encontrado
}

En funciones void, puedes usar return; (sin valor) para salir temprano:

void procesar(int x) {
    if (x < 0) {
        cout << "Número negativo, no procesar" << endl;
        return;  // Sale de la función
    }
    cout << "Procesando: " << x << endl;
}

Funciones útiles en competencias

Verificar si es primo

bool esPrimo(int n) {
    if (n < 2) return false;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) return false;
    }
    return true;
}

Potencia modular

long long potMod(long long base, long long exp, long long mod) {
    long long result = 1;
    base %= mod;
    while (exp > 0) {
        if (exp % 2 == 1) {
            result = result * base % mod;
        }
        exp /= 2;
        base = base * base % mod;
    }
    return result;
}

Máximo Común Divisor (GCD)

int gcd(int a, int b) {
    while (b != 0) {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

// O simplemente usa __gcd(a, b) que ya viene en C++

Mínimo Común Múltiplo (LCM)

int lcm(int a, int b) {
    return a / gcd(a, b) * b;  // Dividimos primero para evitar overflow
}

El template de competencia usa funciones

¿Recuerdas el template?

#include <bits/stdc++.h>
using namespace std;

void solve() {
    // Tu solución va aquí
}

signed main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int t = 1;
    // cin >> t;
    while (t--) {
        solve();
    }
    return 0;
}

La función solve() contiene tu solución. Esto es útil porque:

  1. Si hay múltiples casos, solo descomentas cin >> t.
  2. Puedes usar return dentro de solve() para terminar un caso temprano sin terminar todo el programa.
  3. Las variables locales de solve() se reinician automáticamente en cada caso.

Ejercicio de práctica

Escribe una función int factorial(int n) que calcule el factorial de n. Luego lee un número y muestra su factorial.

Entrada: 5 Salida: 120 (porque 5! = 5×4×3×2×1 = 120)

Ver solución
#include <iostream>
using namespace std;

long long factorial(int n) {
    long long resultado = 1;
    for (int i = 2; i <= n; i++) {
        resultado *= i;
    }
    return resultado;
}

int main() {
    int n;
    cin >> n;
    cout << factorial(n) << endl;
    return 0;
}

Nota: Usamos long long porque los factoriales crecen muy rápido. 20! ya es más de 2.4×10182.4 \times 10^{18}, que apenas cabe en un long long. ¡21! ya no cabe!

Siguiente paso

Con funciones dominadas, estás listo para aprender las Estructuras de Datos fundamentales: arreglos, vectores, strings y más.