Fundamentos de Programación · Subprogramas

Funciones

Un problema grande se resuelve mejor si lo partimos en problemas chicos.

Pedir 3 notas y mostrar su promediomain()
Leer las notasleerNotas()
Calcular el promediopromedio3()
Mostrar el resultadomostrar()

Cada parte es un subprograma. En C++ se escriben como funciones, y main() es la que las coordina.

¿Por qué conviene?Se lee mejorCada parte se prueba por separadoSe reutiliza: la llamás las veces que quierasSe reparte el trabajo
→ / espacio para avanzar · ← volver · F pantalla completa

¿Qué pasa cuando se llama a una función?

La ejecución salta a la función, la recorre, y al terminar vuelve al punto exacto donde se la llamó.

int main() {
int d1 = 10, d2 = 20, d3 = 45;
float p = promedio3(d1, d2, d3);
cout << "Promedio: " << p;
return 0;
}
float promedio3(int x, int y, int z) {
float w = (x + y + z) / 3.0;
return w;
}
25
memoria de main()
d110
d220
d345
p?
memoria de promedio3()
x10
y20
z45
w—
✕ se destruyen al terminar la función

x, y, z, w son locales: nacen con la llamada y mueren al terminar. Por eso el resultado tiene que salir con return.

Dos tipos de subprograma

Los dos reciben datos y hacen un trabajo. La diferencia está en qué pasa con el resultado.

Función devuelve un valor

Calcula algo y te lo devuelve con return, para que lo guardes o lo sigas usando.

102045
promedio3
25
p25
float promedio3(int x, int y, int z) {
return (x + y + z) / 3.0;
}
// en main:
float p = promedio3(10, 20, 45);

Procedimiento tipo void

Hace una tarea (mostrar, cargar, modificar) pero no devuelve nada. En C++ es una función void.

102045
mostrarPromedio
pantalla
> Promedio: 25
void mostrarPromedio(int x, int y, int z) {
cout << "Promedio: " << (x+y+z)/3.0;
}
// en main:
mostrarPromedio(10, 20, 45);

¿Necesitás el resultado para seguir trabajando con él? → función que devuelve.
¿Solo querés que algo pase? → procedimiento (void).

La firma de una función

Leyendo solo la primera línea ya sabés qué recibe y qué devuelve.

float promedio3(int x, int y, int z)

Tipo de retorno

Qué clase de dato devuelve (int, float, bool…). Si no devuelve nada: void.

Nombre

Cómo la vas a llamar. Conviene un verbo que diga qué hace: calcular, mostrar, esPar…

Parámetros formales

Lo que necesita para trabajar: tipo y nombre de cada uno, separados por coma.

Prototipo

float promedio3(int x, int y, int z);

Va antes de main() y avisa que la función existe. Termina en ;

Definición

float promedio3(int x, int y, int z) {
return (x + y + z) / 3.0;
}

Firma + cuerpo entre llaves: el código que se ejecuta.

Llamada

p = promedio3(d1, d2, d3);
d1 → xd2 → yd3 → z

Parámetros actuales → formales: misma cantidad, orden y tipo.

Tipos de retorno y la sentencia return

El tipo que va antes del nombre dice qué clase de valor sale de la función.

int maximo(int a, int b)→7un entero
float promedio3(int x, int y, int z)→25.0un real
bool esPar(int n)→truesí / no
char letraNota(int nota)→'A'un carácter
void mostrarMenu()→—nada

El valor que acompaña al return tiene que ser del mismo tipo que la función. Una función void no devuelve valor.

Probalo: return termina la función en el acto

n =7
bool esPar(int n) {
if (n % 2 == 0)
return true;
return false;
}
Elegí un número y tocá Ejecutar.
esPar(7) devuelve ?

Pasaje por valor (por copia)

La función recibe una copia del dato. Haga lo que haga con su copia, el original no se entera.

void duplicar(int x) {
x = x * 2;
}
int main() {
int n = 5;
duplicar(n);
cout << n;
}
main()
n5
sigue valiendo 5
duplicar()
x5
es una copia de n
✕ la copia se destruye
5

Imprime 5: la función duplicó su propia copia y después la copia desapareció.

Como prestar una fotocopia de tu apunte: si la rayan, tu original queda limpio.

Pasaje por referencia (alias)

Con & no hay copia: el parámetro pasa a ser otro nombre para la misma variable.

void duplicar(int &x) {
x = x * 2;
}
int main() {
int n = 5;
duplicar(n);
cout << n;
}
main()
n5
¡cambió el original!
duplicar()
x no tiene caja propia:
es un alias de n
✕ el alias x desaparece
x

Imprime 10: lo que la función le hace a x le pasa a n. Sirve para modificar variables de quien llama o para sacar más de un resultado.

Como un apodo: si a Martín le dicen «Tincho» y Tincho se corta el pelo… Martín también.

¿Qué imprime?

Mismo código, una sola diferencia: el &. Pensalo antes de avanzar.

Por copia avanzá para ver la respuesta

void intercambiar(int a, int b) {
int aux = a;
a = b;
b = aux;
}
int main() {
int x = 1, y = 2;
intercambiar(x, y);
cout << x << " " << y;
}
x
y
1
2
intercambiar()
a
b
1
2
✕ a y b se destruyen
Imprime: ?1 2

Se intercambiaron las copias a y b; x e y nunca se tocaron.

Por referencia avanzá para ver la respuesta

void intercambiar(int &a, int &b) {
int aux = a;
a = b;
b = aux;
}
int main() {
int x = 1, y = 2;
intercambiar(x, y);
cout << x << " " << y;
}
x (alias a)
y (alias b)
1
2
Imprime: ?2 1

a y b son otros nombres de x e y: el intercambio pasa en los originales.

Usos prácticos y buenas costumbres

Tres ideas para que tus funciones sirvan en más lugares y den menos sorpresas.

1 · Devolvé el resultado, no lo imprimas adentro

void volumen1(float r, float h) { … cout << vol; }
Consola ✓App con ventanas ✗Otro cálculo ✗
float volumen2(float r, float h) { … return vol; }
Consola ✓App con ventanas ✓Otro cálculo ✓

Si la función hace cout, queda atada a la consola. Si devuelve el valor, quien la llama decide qué hacer con él.

2 · Los arreglos viajan por referencia

void mostrar(const int lista[], int n)
354122🔒 const
lista[0] = 99;
✕ error de compilación

La función trabaja sobre el arreglo original. Si no debe modificarlo, marcalo const.

3 · Variables locales

Lo que se declara dentro de una función (y sus parámetros) solo existe ahí: nace con la llamada y muere al terminar. Preferí pasar los datos por parámetro antes que usar variables globales.

promedio3()xyzw

Dos herramientas más

No son imprescindibles, pero aparecen seguido en ejercicios y parciales.

Parámetros por defecto

float promedio3(int x, int y, int z = 10);
promedio3(4, 7)
4710
promedio3(4, 7, 1)
471

Si no mandás ese parámetro, toma el valor por defecto. Los que tienen valor por defecto van siempre al final de la lista.

Sobrecarga

dividir(5, 2)⇒ 2
dividir(5.0, 2.0)⇒ 2.5
float dividir(float a, float b) { return a / b; }
int dividir(int a, int b) { return a / b; }

Mismo nombre, distintos parámetros: el compilador elige cuál usar según los tipos de la llamada. Si solo cambia el tipo de retorno, no alcanza: es error.

Recursividad

Una función que se llama a sí misma para resolver una versión más chica del mismo problema: 4! = 4 × 3!

long factorial(int x) {
if (x == 0)
return 1; // caso base
return x * factorial(x - 1);
}

1. Se llama a sí misma con un problema más chico (x - 1).

2. Tiene un caso base que corta las llamadas (x == 0).

Sin caso base se llamaría para siempre, hasta llenar la memoria: stack overflow.
Pila de llamadas
factorial(4) = 24