Saltar al contenido
Topos Uranos

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

  1. Predecir el efecto de una función sobre sus argumentos según se pasen por valor o por referencia.
  2. 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.
  3. Devolver varios resultados con std::pair o std::tuple y recibirlos con ligaduras estructuradas.
  4. Determinar el alcance y la duración de cada variable de un programa, incluidas las variables static locales y las que quedan en la sombra de otra.
  5. 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.

Programa en C++
#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);}
Salida
Dentro:  a = 2, b = 1Después: x = 1, y = 2

Dentro 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.

EjemploDos marcos, cuatro casillas

El marco de main contiene x e y; la llamada crea encima el marco de swapValues, con casillas propias.

Demostración

  1. Antes de la llamada, el marco de main tiene dos casillas: x con 1 e y con 2.

  2. La llamada crea el marco de swapValues. Sus parámetros a y b son casillas nuevas, y en ellas se copian los valores de los argumentos: 1 y 2. Desde este momento no queda ningún vínculo entre a y x, salvo que empezaron con el mismo valor.

  3. La variable local saved se crea en el mismo marco y guarda una copia de a.

  4. Las dos asignaciones intercambian a y b. Las casillas que cambian están todas en el marco de arriba; las de main no se tocan.

  5. Al volver, el marco de swapValues desaparece entero, con el intercambio que contenía. x e y siguen 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.

Programa en C++
#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));}
Salida
48210 tiene 5 cifras

La 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.