</>waridocu

C++

Funciones: parámetros, retorno y referencias

Cómo declarar funciones, qué pasa con los argumentos al pasarlos, y por qué existen las referencias.

cppfuncionesparametrosreferenciassobrecarga

De dónde venimos

En Control de flujo el main() ya empezaba a acumular responsabilidades: pedir datos, validarlos, decidir y mostrar el resultado, todo en el mismo bloque. Esta página parte de ese problema concreto.

También retoma dos ideas anteriores que aquí se vuelven centrales:

  • El ámbito de las variables que viste en Bloques y ámbito: el cuerpo de una función es un bloque, y sus variables locales mueren al terminar la función.
  • El const de Variables y tipos, que combinado con referencias se convierte en la forma estándar de pasar datos grandes en C++.

Y, sobre todo, esta página es el puente hacia punteros: el operador & que vas a ver aquí es el mismo símbolo (con otro significado) que aparecerá en Punteros.

Qué es una función

Una función es un bloque de código con nombre que recibe datos de entrada (parámetros), hace algo con ellos y opcionalmente devuelve un resultado. Ya vienes usando una desde el primer programa: main() es una función, y el sistema operativo la llama por ti al ejecutar el programa.

La forma general es:

Código
tipo_de_retorno nombre(tipo parametro1, tipo parametro2) {
    // cuerpo
    return valor;
}
Ejemplo: función que calcula el área de un rectángulocpp
#include <iostream>

double areaRectangulo(double ancho, double alto) {
    return ancho * alto;
}

int main() {
    double a = 0.0;
    double b = 0.0;

    std::cout << "Ancho y alto: ";
    std::cin >> a >> b;

    std::cout << "Área: " << areaRectangulo(a, b) << "\n";
    return 0;
}
bashbash
g++ -std=c++17 -Wall -o area area.cpp
./area
texttext
Ancho y alto: 3 4.5
Área: 13.5

Tres cosas que este ejemplo muestra de paso:

  1. std::cin >> a >> b; encadena dos lecturas, igual que std::cout encadena escrituras (misma idea de la página de la Standard Library).
  2. Los parámetros ancho y alto son variables locales de la función: existen solo mientras ella se ejecuta.
  3. Los nombres de los argumentos en la llamada (a, b) no tienen por qué coincidir con los de los parámetros. Lo que importa es el orden y el tipo.

Funciones que no devuelven nada: void

Si una función solo hace algo (imprimir, modificar) y no tiene un resultado que entregar, su tipo de retorno es void y no lleva return (o lleva un return; seco para salir antes de tiempo).

Ejemplo: función void que solo imprimecpp
void mostrarSeparador() {
    std::cout << "--------------------\n";
}

Declaración y definición

El compilador de C++ lee el archivo de arriba a abajo y necesita conocer una función antes de la línea donde la llamas. Por eso, si prefieres poner main() primero, debes anticipar la función con un prototipo (la firma seguida de ;, sin cuerpo):

Ejemplo: prototipo antes de main, definición despuéscpp
#include <iostream>

double areaRectangulo(double ancho, double alto);   // declaración (prototipo)

int main() {
    std::cout << areaRectangulo(3.0, 4.5) << "\n";
    return 0;
}

double areaRectangulo(double ancho, double alto) {  // definición
    return ancho * alto;
}
Nota

En un programa de un solo archivo como este, el prototipo es opcional: basta con definir la función antes de usarla. En proyectos reales, en cambio, los prototipos son la norma y viven en archivos de cabecera (.h / .hpp) mientras las definiciones van en .cpp. Esa separación es justamente lo que hace #include <iostream>: te trae las declaraciones de std::cout, no su código. Este ejemplo de un archivo no es representativo de cómo se organiza un proyecto de verdad.

Paso por valor vs. paso por referencia

Aquí está la parte que de verdad importa, porque define cómo se comporta C++ frente a otros lenguajes.

Por defecto, C++ pasa los argumentos por valor: la función recibe una copia del dato. Modificar el parámetro dentro de la función no afecta a la variable original de quien llamó.

Ejemplo: paso por valor (NO modifica el original)cpp
#include <iostream>

void intentarDuplicar(int numero) {
    numero = numero * 2;      // modifica solo la copia local
}

int main() {
    int x = 10;
    intentarDuplicar(x);
    std::cout << x << "\n";   // imprime 10, no 20
    return 0;
}

Para que la función trabaje sobre la variable original, se declara el parámetro como referencia, añadiendo & al tipo. Una referencia es un alias: otro nombre para la misma variable, no una copia.

Ejemplo: paso por referencia (SÍ modifica el original)cpp
#include <iostream>

void duplicar(int& numero) {   // el & convierte el parámetro en referencia
    numero = numero * 2;       // modifica la variable original
}

int main() {
    int x = 10;
    duplicar(x);
    std::cout << x << "\n";    // imprime 20
    return 0;
}
bashbash
g++ -std=c++17 -Wall -o referencias referencias.cpp
./referencias
texttext
20

La diferencia entre los dos programas es un único carácter: &. Por eso conviene detenerse aquí y no seguir hasta tenerlo claro.

FormaSintaxis¿Copia el dato?¿Puede modificar el original?
Por valorvoid f(int n)No
Por referenciavoid f(int& n)No
Referencia constvoid f(const std::string& s)NoNo (el compilador lo impide)

La tercera fila es la combinación que más vas a usar en C++ real: pasar objetos grandes por referencia constante. Copiar un int es gratis, pero copiar un std::string con miles de caracteres o un vector con un millón de elementos no lo es. La referencia evita la copia, y el const deja claro (al compilador y al lector) que la función solo va a leer el dato.

Ejemplo: pasar un string grande sin copiarlocpp
#include <iostream>
#include <string>

void saludar(const std::string& nombre) {   // sin copia, sin permiso de modificar
    std::cout << "Hola, " << nombre << "\n";
}

int main() {
    std::string usuario = "Camilo";
    saludar(usuario);
    return 0;
}
Advertencia

Nunca devuelvas una referencia a una variable local de la función. La variable muere cuando la función termina (por el ámbito que viste en Bloques y ámbito), y quien reciba esa referencia estará apuntando a memoria que ya no le pertenece a nadie. El código compila —a veces con una advertencia si usas -Wall— y puede parecer que funciona en pruebas pequeñas, pero es comportamiento indefinido. La versión con punteros de este mismo error se llama puntero colgante y la verás en Punteros.

cppcpp
int& mal() {
    int local = 42;
    return local;      // ERROR conceptual: local deja de existir aquí
}
Importante

Que el paso por valor haga una copia significa que la referencia es la única forma que tiene una función de modificar algo de quien la llamó (junto con los punteros, que veremos después). Si vienes de Python o JavaScript, donde las listas y objetos se comportan como si se pasaran por referencia automáticamente, este es uno de los cambios mentales más importantes: en C++ decides tú, explícitamente, en cada parámetro.

Sobrecarga de funciones

C++ permite tener varias funciones con el mismo nombre siempre que se diferencien en el número o el tipo de sus parámetros. El compilador elige cuál llamar según los argumentos. Esto se llama sobrecarga (overloading), y es una de las diferencias con C que ya se mencionaba en la tabla comparativa C vs C++.

Ejemplo: tres versiones sobrecargadas de area()cpp
#include <iostream>

double area(double lado) {                    // cuadrado
    return lado * lado;
}

double area(double ancho, double alto) {      // rectángulo
    return ancho * alto;
}

double area(int radio) {                      // círculo (radio entero)
    return 3.14159 * radio * radio;
}

int main() {
    std::cout << area(4.0)        << "\n";    // 16      -> cuadrado
    std::cout << area(3.0, 4.0)   << "\n";    // 12      -> rectángulo
    std::cout << area(2)          << "\n";    // 12.5664 -> círculo
    return 0;
}
bashbash
g++ -std=c++17 -Wall -o sobrecarga sobrecarga.cpp
./sobrecarga
texttext
16
12
12.5664
Advertencia

Este ejemplo es didáctico, no un buen diseño. Distinguir “cuadrado” de “círculo” solo porque uno recibe double y el otro int es frágil y confuso: area(2) da el área de un círculo, pero area(2.0) da la de un cuadrado. En código real, la sobrecarga se usa cuando las versiones hacen conceptualmente lo mismo con tipos distintos (por ejemplo, imprimir(int) e imprimir(const std::string&)). Si las operaciones son distintas, dales nombres distintos: areaCuadrado, areaCirculo.

Advertencia

El tipo de retorno no participa en la sobrecarga. Estas dos no pueden coexistir, porque en la llamada f(1) el compilador no tendría forma de saber cuál quieres:

cppcpp
int    f(int x);
double f(int x);   // ERROR: redefinición, difiere solo en el retorno

Siguiente paso

Con funciones ya puedes organizar el código, pero sigues manejando valores sueltos de uno en uno. Para trabajar con colecciones de datos del mismo tipo, continúa con Arrays y cadenas de estilo C — que además es el escalón directo hacia los punteros.