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.
// 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ámetro | Argumento | |
|---|---|---|
| Dónde aparece | En la definición de la función | En la llamada a la función |
| Qué es | Un nombre con su tipo, sin valor todavía | Un valor concreto |
| Cuántas veces | Se escribe una vez | Uno distinto en cada llamada |
| También se llama | Parámetro formal | Parámetro real o efectivo |
function real calcularNota(entero aciertos, entero fallos) { ... }
// ^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^
// estos dos son PARAMETROS
calcularNota(72, 12)
// ^^ ^^
// estos dos son ARGUMENTOSEl 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.
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ón | Procedimiento | |
|---|---|---|
| Devuelve un valor | Sí | No |
| Tipo declarado | El del valor que devuelve | void, o ninguno |
| Cómo se usa | Como una expresión: su resultado se guarda o se opera | Como una instrucción suelta |
| Para qué se usa | Calcular y devolver un resultado | Realizar 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.
// 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 valor | Paso por referencia | |
|---|---|---|
| Qué recibe la función | Una copia del dato | La dirección del dato original |
| Efecto sobre la variable de fuera | Ninguno | Los cambios permanecen |
| Coste en memoria | Duplica el dato | No duplica nada |
| Seguridad | Mayor: la función no puede estropear nada de fuera | Menor: hay que confiar en lo que haga la función |
| Uso típico | Datos simples que solo se consultan | Datos 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.
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