Parámetros frente a argumentos
Los parámetros de una función son las variables con nombre de su definición; los argumentos son los valores reales que le pasas cuando la llamas. La página anterior mostró cómo definir y llamar funciones; esta página trata de cómo entran realmente esos valores, porque C++ te ofrece varias formas y la elección afecta tanto a la corrección como a la velocidad.
Lo predeterminado en C++ es el paso por valor: la función recibe una copia.
Dentro de addTen, n es una variable independiente inicializada a partir de score. Reasignar n toca solo esa copia, así que score queda intacto de vuelta en main. Esto es seguro y predecible: la función no puede pisar tus datos por accidente, y por eso precisamente es lo predeterminado.
Paso por referencia: dejar que una función cambie a quien la llama
A veces quieres que la función modifique la variable de quien la llama. Añade un & al tipo del parámetro y se convierte en una referencia: un alias del original, no una copia:
La única diferencia respecto al primer ejemplo es el &, pero ahora n y score son el mismo objeto. Esta es la manera estándar de "devolver" más de un valor o de actualizar algo en el sitio. Un uso clásico es intercambiar dos variables:
Sin el &, swapValues barajaría dos copias y main no vería ningún cambio: un error de principiante muy habitual.
Referencias const: acceso barato y de solo lectura
El paso por valor copia el argumento. Para un int eso no cuesta nada, pero copiar un string o un vector grande en cada llamada es trabajo real y desperdiciado. La solución es una referencia const (const T&): obtienes la velocidad de la referencia (sin copia) más una promesa garantizada por el compilador de no modificar el argumento.
Una regla práctica útil: pasa los tipos integrados pequeños (int, double, char, bool, punteros) por valor, y pasa los objetos grandes que solo necesitas leer por referencia const. Reserva una T& no const para los casos en los que de verdad pretendes modificar el objeto de quien llama.
Un detalle sutil: una int& n normal no puede enlazar a un temporal ni a un literal. addTen(5) del primer ejemplo no compilaría si el parámetro fuera int&, porque 5 no es una variable a la que puedas asignar un alias. Una const int& sí puede enlazar a 5, lo cual es una razón más por la que las referencias const se usan tanto.
Argumentos por defecto
Puedes dar a un parámetro un valor de reserva para que quien llama pueda omitirlo. Si falta el argumento, se usa el valor por defecto:
Hay dos reglas con las que la gente tropieza. Primera, los valores por defecto deben ir al final: una vez que un parámetro tiene un valor por defecto, todos los que vienen después también deben tenerlo. No puedes escribir void f(int a = 1, int b) porque no habría forma de proporcionar b saltándote a. Segunda, cuando una función se declara en una cabecera y se define en otro sitio, pon el valor por defecto solo en la declaración, nunca lo repitas en la definición: repetirlo es un error de compilación.
Pasar arrays y vectores
Un array nativo se degrada a un puntero cuando se pasa, así que la función pierde el rastro de su tamaño; casi siempre pasas la longitud junto a él:
Como el array se convirtió en un puntero, sizeof(arr) dentro de sum daría el tamaño de un puntero, no del array: un error famoso. En C++ moderno prefiere un std::vector (o un std::span en C++20), pasado por referencia const, que lleva consigo su propio tamaño:
Fíjate en el const&: quítalo y cada llamada copia el vector entero. Para un vector de cuatro elementos eso es inofensivo, pero para un millón de elementos es un derroche silencioso de rendimiento.
Parámetros de tipo puntero
También puedes pasar un puntero (T*). Igual que una referencia, esto permite a la función alcanzar los datos de quien la llama, pero un puntero puede reapuntarse o ser nulo, así que es la herramienta adecuada cuando "ningún valor" es una opción legítima:
Quien llama pasa &value para compartir su dirección, y la función escribe a través de *out. La diferencia clave respecto a las referencias: un puntero podría ser nullptr, así que una función que reciba uno debería comprobarlo antes de desreferenciarlo; saltarse esa protección y desreferenciar un puntero nulo es comportamiento indefinido, normalmente un cuelgue. Si "ningún valor" nunca tiene sentido, una referencia es más limpia porque, para empezar, no puede ser nula.
Siguiente: Referencias
Los parámetros son donde las referencias se ganan el sueldo, pero las referencias son una característica por derecho propio: alias que puedes crear para cualquier variable, no solo dentro de la firma de una función. La siguiente página profundiza en cómo funcionan las referencias por sí solas: cómo declararlas, por qué deben inicializarse de inmediato, la diferencia entre una referencia lvalue y una referencia const, y las formas sutiles en que una referencia puede acabar colgando.
Preguntas frecuentes
¿Cuál es la diferencia entre paso por valor y paso por referencia en C++?
El paso por valor copia el argumento en el parámetro, así que los cambios dentro de la función no afectan a quien la llama. El paso por referencia (int&) hace que el parámetro sea un alias de la variable de quien llama, así que los cambios se ven desde fuera. Usa void f(int x) para copiar y void f(int& x) para modificar el original.
¿Cuándo deberías usar un parámetro de referencia const en C++?
Usa const T& cuando quieras leer un objeto grande sin copiarlo y sin permitir que la función lo modifique; por ejemplo void print(const string& s). Te da la velocidad del paso por referencia con la seguridad del paso por valor. Para tipos pequeños como int o char, el paso por valor normal es igual de rápido.
¿Qué son los argumentos por defecto en C++?
Los argumentos por defecto permiten que un parámetro tome un valor de reserva cuando quien llama lo omite, p. ej. void greet(string name = "there"). Los valores por defecto deben ser los parámetros finales (los más a la derecha), y se especifican solo en la declaración, no en la definición si ambas están separadas.