Saltar al contenido

Funciones

Cómo se le da nombre a un trozo de programa para poder reutilizarlo: definición y llamada, parámetros y argumentos, valor de retorno, la diferencia entre función y procedimiento, y el paso por valor frente al paso por referencia.

Qué es una función y para qué sirve

Cuando un programa crece, hay trozos de código que se repiten en varios sitios: calcular una nota, comprobar si un tema está pendiente, dar formato a una fecha. Copiar y pegar ese trozo funciona hasta el día en que hay que corregirlo, porque entonces hay que acordarse de todas las copias. Una función resuelve eso: es un trozo de programa al que se le da un nombre para poder usarlo desde cualquier sitio nombrándolo.

La imagen que mejor la describe es la de una máquina con una boca de entrada y una de salida. Se le meten unos datos, hace su trabajo por dentro sin que haya que saber cómo, y devuelve un resultado. Quien la usa no necesita entender su interior, igual que para usar una calculadora no hace falta saber electrónica.

  • Reutilización: el código se escribe una vez y se usa desde muchos puntos.
  • Mantenimiento: si hay un error o cambia la regla, se corrige en un solo sitio.
  • Legibilidad: una línea que dice calcularNota(aciertos, fallos) se entiende sin leer el cálculo.
  • División del problema: un problema grande se parte en piezas pequeñas que se pueden resolver y probar por separado. A esto se le llama modularidad.
  • Abstracción: quien llama a la función solo necesita saber qué hace, no cómo lo hace.

Según el lenguaje reciben nombres distintos (función, método, subrutina, subprograma), pero la idea es la misma. En programación orientada a objetos, una función que pertenece a un objeto se llama método.

Una función se escribe una vez y se usa muchas. Ese es su valor principal: no ahorra líneas al escribir, ahorra errores al corregir.

Para el examen

  • Qué aporta: reutilización, mantenimiento en un solo sitio, legibilidad, modularidad y abstracción

  • Cómo se llama en orientación a objetos: método

Definir una función y llamarla

Con una función se hacen dos cosas distintas que conviene no mezclar. Definirla es escribir qué hace, y eso se hace una sola vez; al definirla no se ejecuta nada. Llamarla o invocarla es pedirle que actúe, y eso se hace tantas veces como haga falta.

pseudocodigo
// DEFINICION: aqui no se ejecuta nada todavia
function real calcularNota(entero aciertos, entero fallos) {
    real penalizacion = fallos / 3.0
    real puntos = aciertos - penalizacion
    return (puntos * 10.0) / 100
}

// LLAMADAS: aqui si se ejecuta, una vez por linea
real notaAna  = calcularNota(72, 12)
real notaLuis = calcularNota(55, 9)

escribir(notaAna)    // 6.8
  • Línea 2: es la cabecera o firma de la función. Contiene tres cosas: el tipo que va a devolver (real), el nombre (calcularNota) y entre paréntesis los datos que necesita recibir, cada uno con su tipo.
  • Líneas 3 y 4: el cuerpo de la función, con sus variables locales. «penalizacion» y «puntos» solo existen aquí dentro.
  • Línea 5: return entrega el resultado a quien haya llamado a la función y termina su ejecución.
  • Línea 9: la llamada. El programa salta a la función, ejecuta su cuerpo con aciertos valiendo 72 y fallos valiendo 12, y vuelve trayendo el resultado, que se guarda en «notaAna».
  • Línea 10: la misma función, otra llamada, otros datos. El código de la función no se ha duplicado.

Una llamada a función es, para el resto del programa, una expresión más: donde aparece calcularNota(72, 12) acaba habiendo un número real, exactamente igual que si se hubiera escrito el 6.8 a mano. Por eso una llamada puede ponerse en cualquier sitio donde quepa un valor, incluida la condición de un if o dentro de otra operación.

Definir una función no la ejecuta. Solo se ejecuta cuando alguien la llama, y en ese momento el programa salta al cuerpo, lo recorre y vuelve al punto exacto desde el que se le llamó.

Para el examen

  • Definir: no ejecuta nada: la función solo corre al llamarla

  • La llamada: es una expresión que vale lo que devuelva el return

  • Firma o cabecera: tipo devuelto, nombre y parámetros

Parámetros y argumentos

Estos dos términos se usan a menudo como sinónimos en la conversación corriente, pero en examen se distinguen y conviene tenerlo claro. Los parámetros son los nombres que aparecen en la definición de la función, y son casillas vacías a la espera de recibir un valor. Los argumentos son los valores concretos que se entregan en cada llamada.

ParámetroArgumento
Dónde apareceEn la definición de la funciónEn la llamada a la función
Qué esUn nombre con su tipo, sin valor todavíaUn valor concreto
Cuántas vecesSe escribe una vezUno distinto en cada llamada
También se llamaParámetro formalParámetro real o efectivo
pseudocodigo
function real calcularNota(entero aciertos, entero fallos) { ... }
//                          ^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^
//                          estos dos son PARAMETROS

calcularNota(72, 12)
//           ^^  ^^
//           estos dos son ARGUMENTOS

El emparejamiento entre unos y otros se hace por posición, no por nombre: el primer argumento va al primer parámetro, el segundo al segundo y así sucesivamente. De ahí sale un error clásico y silencioso: invertir el orden. Si se llama calcularNota(12, 72), el programa compila, se ejecuta y devuelve un resultado perfectamente equivocado, porque ha tomado los fallos por aciertos.

El número y el tipo de los argumentos sí tienen que coincidir con los de los parámetros. Llamar a la función con un argumento de menos, o pasar un texto donde se espera un entero, es un error que el traductor detecta en un lenguaje de tipado estático.

Parámetro es lo que aparece en la definición y argumento lo que se pasa en la llamada. Se emparejan por posición, así que cambiar el orden de dos argumentos del mismo tipo produce un fallo que nadie detecta.

Para el examen

  • Parámetro (formal): el de la definición

  • Argumento (real): el de la llamada

  • Cómo se emparejan: por posición, no por nombre

El valor de retorno, y la diferencia entre función y procedimiento

La instrucción return hace dos cosas a la vez, y la segunda se olvida a menudo: entrega un valor a quien llamó a la función y termina inmediatamente su ejecución. Cualquier instrucción que quede por debajo del return dentro de la función no se ejecuta nunca.

pseudocodigo
function cadena clasificar(real nota) {
    if (nota >= 9) {
        return "Dominado"
    }
    if (nota >= 5) {
        return "Superado"
    }
    return "Pendiente"
}

Este ejemplo aprovecha esa segunda propiedad del return. Con una nota de 9.5, el primer return se ejecuta y la función acaba ahí mismo: los dos if siguientes ni se miran, aunque sus condiciones también se cumplirían. Es una alternativa muy usada a la escalera de else if, y funciona precisamente porque return corta la ejecución.

No todo subprograma devuelve algo. Cuando su cometido es hacer una tarea y no calcular un valor (mostrar un aviso por pantalla, guardar un dato en un archivo), no hay nada que retornar. A esa variante se la llama procedimiento, y en los lenguajes de la familia de C se declara con el tipo void, que significa «vacío».

FunciónProcedimiento
Devuelve un valorNo
Tipo declaradoEl del valor que devuelvevoid, o ninguno
Cómo se usaComo una expresión: su resultado se guarda o se operaComo una instrucción suelta
Para qué se usaCalcular y devolver un resultadoRealizar una acción o producir un efecto

return devuelve el valor y además termina la función en ese punto. Un procedimiento no devuelve nada y se invoca como una instrucción, no como parte de una expresión.

Para el examen

  • Qué hace return: entrega el valor Y termina la función

  • Lo que quede debajo: no se ejecuta

  • Procedimiento: no devuelve nada (void) y se usa como instrucción, no como expresión

Paso por valor y paso por referencia

Esta es la pregunta clásica de todo el apartado de funciones, y la duda es siempre la misma: si una función modifica un parámetro que ha recibido, ¿queda modificada también la variable original de quien la llamó? La respuesta depende de cómo se haya pasado el dato, y hay dos formas.

En el paso por valor se entrega a la función una COPIA del dato. La función trabaja con su copia, y lo que le haga se pierde al terminar: la variable original de fuera no se entera de nada. Es la analogía de dar a alguien una fotocopia de un documento; puede escribir encima lo que quiera, que el original sigue intacto.

En el paso por referencia no se entrega una copia sino la dirección de la variable original, es decir, dónde vive en memoria. La función trabaja sobre la variable de fuera, y los cambios que haga permanecen después de terminar. La analogía sería dar la llave del archivador en vez de una fotocopia.

pseudocodigo
// PASO POR VALOR
function void sumarDiez(entero n) {
    n = n + 10
    escribir(n)          // escribe 60
}

entero aciertos = 50
sumarDiez(aciertos)
escribir(aciertos)       // escribe 50: la original NO ha cambiado


// PASO POR REFERENCIA  (se marca con & en este pseudocodigo)
function void sumarDiezRef(entero &n) {
    n = n + 10
    escribir(n)          // escribe 60
}

entero fallos = 50
sumarDiezRef(fallos)
escribir(fallos)         // escribe 60: la original SI ha cambiado
  • En el primer bloque, «n» es una copia de «aciertos». Sumarle diez cambia la copia y nada más, así que al volver, «aciertos» sigue valiendo 50.
  • En el segundo bloque, «n» no es una copia: es otro nombre para la misma casilla de memoria que «fallos». Sumarle diez modifica la variable original, y al volver «fallos» vale 60.
  • Las dos funciones tienen el cuerpo idéntico. Lo único que cambia es cómo se declara el parámetro, y eso basta para que el efecto sobre el exterior sea completamente distinto.
Paso por valorPaso por referencia
Qué recibe la funciónUna copia del datoLa dirección del dato original
Efecto sobre la variable de fueraNingunoLos cambios permanecen
Coste en memoriaDuplica el datoNo duplica nada
SeguridadMayor: la función no puede estropear nada de fueraMenor: hay que confiar en lo que haga la función
Uso típicoDatos simples que solo se consultanDatos grandes, o cuando se quiere devolver más de un resultado

Por valor se pasa una copia y el original no cambia; por referencia se pasa la dirección y el original sí cambia. Es la pregunta más repetida sobre funciones.

Para el examen

  • Por valor: la función recibe una copia; la variable original no cambia

  • Por referencia: recibe la dirección y los cambios permanecen

  • Importancia: es la pregunta más repetida sobre funciones

Sobrecarga, valores por defecto y efectos laterales

La sobrecarga consiste en definir varias funciones con el mismo nombre pero con listas de parámetros distintas, en número o en tipo. El lenguaje decide cuál usar según los argumentos de cada llamada, y esa decisión se toma al compilar. Sirve para que operaciones conceptualmente iguales no necesiten nombres artificiales distintos.

pseudocodigo
function real calcularNota(entero aciertos, entero fallos) { ... }
function real calcularNota(entero aciertos) { ... }        // sin penalizacion

calcularNota(72, 12)   // usa la primera
calcularNota(72)       // usa la segunda

// El tipo devuelto NO sirve para distinguirlas: dos funciones que solo
// se diferencien en lo que retornan no son una sobrecarga valida.

Los parámetros por defecto resuelven el mismo problema de otra manera: se le da a un parámetro un valor que se usará si en la llamada no se indica ninguno. Con ello una sola definición cubre varias formas de llamada, sin duplicar la función.

Un efecto lateral es cualquier cosa que una función hace más allá de devolver su resultado: modificar una variable global, cambiar un parámetro pasado por referencia, escribir en un archivo. Una función sin efectos laterales, que ante los mismos argumentos devuelve siempre lo mismo y no toca nada de fuera, se llama función pura, y es el ideal del paradigma funcional.

Las funciones puras son mucho más fáciles de probar y de razonar, porque para saber qué hacen basta con mirar su cuerpo: no dependen de nada externo ni alteran nada externo. Cuando una función mezcla cálculo y efectos laterales, un fallo puede venir de cualquier punto anterior del programa.

Dos funciones sobrecargadas se distinguen por su lista de parámetros, nunca por el tipo que devuelven. Cambiar solo el tipo de retorno no es una sobrecarga válida.

Para el examen

  • Sobrecarga: mismo nombre, distinta lista de parámetros

  • Lo que NO distingue una sobrecarga: el tipo de retorno

  • Función pura: mismo resultado ante los mismos argumentos y sin efectos laterales