Fundamentos
Funciones en C++: la imagen completa
Respuesta corta: una función es un bloque con nombre, con entradas tipadas (parámetros) y una salida tipada (el valor de retorno). Llamarla ejecuta el bloque con unas entradas concretas y devuelve el resultado.
Anatomía
int sumar(int a, int b) { // tipo de retorno, nombre, lista de parámetros
return a + b; // cuerpo, termina devolviendo un int
}
Hay cuatro cosas que leer: int es lo que vuelve, sumar es el nombre, int a, int b son las entradas y return produce la salida. Una función sin valor de retorno se declara void.
Llamar es sustituir
#include <iostream>
using namespace std;
int sumar(int a, int b) {
return a + b;
}
int cuadrado(int x) {
return x * x;
}
int main() {
cout << sumar(2, 3) << endl; // 5
cout << cuadrado(sumar(1, 1)) << endl; // (2)^2 = 4 — una llamada dentro de otra
return 0;
}
La segunda línea es la clave: una llamada a función es un valor, así que puede ir donde iría un literal. Eso es lo que hace componibles a las funciones.
Los parámetros son copias
Este es el hecho más importante sobre las funciones en C++:
#include <iostream>
using namespace std;
void intentarCambiar(int n) {
n = 999; // cambia solo la copia local
}
int main() {
int x = 5;
intentarCambiar(x);
cout << x << endl; // sigue siendo 5
return 0;
}
Para cambiar de verdad la variable del llamador hay que pasar una dirección (puntero) o una referencia. La comparación completa, con diagramas, está en paso por valor, puntero o referencia.
Devolver frente a imprimir
Una función que devuelve se puede usar en expresiones; una que imprime, no:
#include <iostream>
using namespace std;
int doble(int n) { return n * 2; }
void imprime(int n) { cout << n * 2 << endl; }
int main() {
int total = doble(10) + 1; // funciona: doble() es un valor
cout << total << endl;
imprime(10); // funciona, pero no se puede reutilizar
return 0;
}
Regla de la casa: calcula en las funciones (return), imprime en main. Mantiene la función testeable y reutilizable.
Ámbito: dónde viven los nombres
#include <iostream>
using namespace std;
int contador = 0; // global: visible en todas partes
int bump() {
int local = 5; // local: existe solo durante esta llamada
contador++;
return contador + local;
}
int main() {
cout << bump() << endl; // 6
cout << bump() << endl; // 7 — el global sobrevivió
// cout << local; // error: local no está en este ámbito
return 0;
}
Prefiere variables locales: pasa como parámetro lo que la función necesite y devuelve lo que produzca. Los globales son cómodos y hacen el código difícil de razonar en silencio.
Para qué dividir el código
- Una definición, muchos usos. Cambia la regla en un sitio y todos los puntos de llamada la siguen.
- Los nombres se vuelven frases.
esPrimo(n)se lee mejor que doce líneas de división por tentativa. - Testeabilidad. Una función con parámetros y retorno se comprueba mirando su salida; un bloque que imprime y muta globales no.
- La recursión se vuelve posible. Una función puede llamarse a sí misma — la base de la recursión.
Los tres bugs
- Caerse por el final. Declarada para devolver
intpero alguna ruta no tienereturn— comportamiento indefinido, no error de compilación. - Esperar que el paso por valor mute. La confusión principiante más común: la función cambia una copia.
- Declarar después de usar. C++ necesita conocer la firma antes de la llamada — eso lo da una declaración (o una definición encima de
main).
Practica: ¿Es primo? y MCD quedan más limpios con una función auxiliar. Lee después: recursión y luego formas de pasar.