Resumen
Esta clase estudia por qué caminos entran los datos a una función y salen sus resultados. Por omisión, C++ pasa los argumentos por valor: el parámetro es una copia, y nada de lo que la función le haga alcanza al original. Una referencia, en cambio, es un segundo nombre para una casilla que ya existe, y permite que la función modifique la variable de quien la llama; la referencia constante permite leer un dato grande sin copiarlo y sin poder alterarlo. Para devolver varios resultados, la biblioteca ofrece std::pair y std::tuple, que se reciben con ligaduras estructuradas. Siguen el alcance de un nombre (dónde se ve) y la duración de una variable (cuándo existe), con la sombra de un nombre y las variables static locales, y cierra la referencia que sobrevive a su variable. El concepto que unifica todo es el flujo de datos entre las partes de un programa: copiar frente a compartir, y la visibilidad de los nombres como mecanismo de aislamiento.
Objetivos de aprendizaje
- Predecir el efecto de una función sobre sus argumentos según se pasen por valor o por referencia.
- Elegir el modo de paso de cada parámetro (valor para tipos pequeños, referencia constante para datos grandes, referencia para lo que se modifica) y justificar la elección.
- Devolver varios resultados con
std::pairostd::tupley recibirlos con ligaduras estructuradas. - Determinar el alcance y la duración de cada variable de un programa, incluidas las variables
staticlocales y las que quedan en la sombra de otra. - Reconocer la referencia a una variable local o a un valor temporal que ya no existe, con el aviso del compilador que la anuncia, y corregirla.
Paso por valor
El parámetro es una copia
La clase Funciones mostró que cada llamada crea un marco en la pila, con los parámetros y las variables locales de la función, y que el marco desaparece al volver. Falta precisar qué relación guarda un parámetro con el argumento que lo inicializa. La respuesta por omisión de C++ es la más simple: el parámetro es una variable local nueva, que se inicializa con una copia del valor del argumento. Si esto es así, una función que modifica sus parámetros modifica sus copias, y las variables de quien la llama no cambian. El programa siguiente lo pone a prueba con una función que intenta intercambiar dos enteros.
#include <print>// Intenta intercambiar a y b; solo intercambia sus copias.void swapValues(int a, int b){ const int saved{a}; a = b; b = saved; std::println("Dentro: a = {}, b = {}", a, b);}int main(){ int x{1}; int y{2}; swapValues(x, y); std::println("Después: x = {}, y = {}", x, y);}Dentro: a = 2, b = 1Después: x = 1, y = 2Dentro de la función, el intercambio se hace bien; después de la llamada, x e y conservan sus valores. El compilador no avisa, porque no hay nada que avisar: el código intercambia dos variables locales que desaparecen al terminar. La figura sigue los marcos de la pila durante la llamada.
El marco de main contiene x e y; la llamada crea encima el marco de swapValues, con casillas propias.
Demostración
Antes de la llamada, el marco de
maintiene dos casillas:xcon 1 eycon 2.La llamada crea el marco de
swapValues. Sus parámetrosaybson casillas nuevas, y en ellas se copian los valores de los argumentos: 1 y 2. Desde este momento no queda ningún vínculo entreayx, salvo que empezaron con el mismo valor.La variable local
savedse crea en el mismo marco y guarda una copia dea.Las dos asignaciones intercambian
ayb. Las casillas que cambian están todas en el marco de arriba; las demainno se tocan.Al volver, el marco de
swapValuesdesaparece entero, con el intercambio que contenía.xeysiguen valiendo 1 y 2.
Lo que la copia protege
El paso por valor no es un defecto, sino la regla que hace razonables a las funciones. Primero, el argumento puede ser cualquier expresión, no solo una variable: swapValues(x + 1, 7) es válida, porque el parámetro se inicializa con un valor, venga de donde venga. Segundo, quien llama sabe por la declaración, sin leer el cuerpo, que sus variables no cambiarán. Tercero, dentro de la función el parámetro es una variable local más, que puede servir de variable de trabajo sin consecuencias fuera.
#include <print>// Precondición: n >= 0. Devuelve la cantidad de cifras decimales de n.int digitCount(int n){ int count{1}; while (n >= 10) { n = n / 10; ++count; } return count;}int main(){ const int population{48'210}; std::println("{} tiene {} cifras", population, digitCount(population));}48210 tiene 5 cifrasLa función destruye su parámetro dividiéndolo por 10 hasta que tiene una sola cifra; como es una copia, population queda intacta, y puede ser const. De ello se sigue una regla de lectura que vale para todo el curso: si un parámetro no tiene el signo & que se estudia en la sección siguiente, la función no puede modificar la variable de quien la llama, haga lo que haga con él.
Cargando el contenido…