2

Este es un código de agregar pilas, lo que no logro entender bien es que en el prototipo de la función le estoy pasando el *& mi pregunta es ¿por que si solo uso el * el código sigue funcionando sin necesidad de usar *& , hay alguna diferencia entre usar * y *& ?Gracias.


#include<iostream>

struct Nodo{
    int dato;
    Nodo *siguiente;
};

void agregar_pila(Nodo *&, int);

using namespace std;

int main(){

    Nodo *pila = NULL;
    
    agregar_pila(pila, 6);
    agregar_pila(pila, 12);

    return 0;
}

void agregar_pila(Nodo *&pila, int n){
    Nodo *nuevo_nodo = new Nodo();
    nuevo_nodo->dato = n;
    nuevo_nodo->siguiente = pila;
    pila = nuevo_nodo;

    cout<<"Elemento "<<n<<" agregado correctamente a la pila"<<endl;
}
DanshX
  • 47
  • 3
  • Mira [ask] para que tu pregunta sea mejor recibida. También, aprovecha y haz el [tour] para entender mejor cómo funcionamos y de paso obtener tu primera [medalla](https://es.stackoverflow.com/help/badges)! – gbianchi May 09 '21 at 04:21
  • ¿Responde esto a tu pregunta? [¿Cual es la diferencia entre \`int \*\` e \`int &\`?](https://es.stackoverflow.com/questions/46909/cual-es-la-diferencia-entre-int-e-int) – Trauma May 09 '21 at 04:51

2 Answers2

1

TLDR: Si quitas ese &, el programa, aunque no de errores, dejará de funcionar correctamente, como demostraré luego.

Qué hace el &?

Hay que comenzar diciendo que el código es C++. No compilaría correctamente con un compilador de C. En C no se admite el poner un & como parte de la declaración de un parámetro.

En C++ el poner & delante de un parámetro indica que éste se pasa por referencia. Así pues, en tu función:

void agregar_pila(Nodo *&pila, int n){
   ...
}

el parámetro pila es de tipo "referencia a puntero a Nodo". Esto quiere decir que la variable que le vayas a pasar como parámetro ha de ser de tipo "puntero a nodo", pero la funcion lo que recibirá será una referencia al puntero que le pases.

Eso es lo que permite que dentro de la función, cuando haces:

    pila = nuevo_nodo;

estés modificando la variable que habías recibido como parámetro, en vez de una copia de esa variable que sería lo que ocurriría si no fuera de tipo referencia.

En definitiva, cuando el programa principal llama a esa función por ejemplo aquí:

    agregar_pila(pila, 6);

la variable pila de la función main() es pasada como referencia a agregar_pila(), por lo que la variable pila dentro de esa función alterará directamente la variable pila de main().

Como consecuencia, una vez que la función retorne, la variable pila de main() habrá cambiado de valor. Esto podemos verificarlo imprimiendo esa variable:

int main(){

    Nodo *pila = NULL;
    
    cout << pila << endl;

    agregar_pila(pila, 6);
    cout << pila << endl;

    agregar_pila(pila, 12);
    cout << pila << endl;

    return 0;
}

Al ejecutarlo veremos:

0
Elemento 6 agregado correctamente a la pila
0x2383280
Elemento 12 agregado correctamente a la pila
0x23832a0

Como ves, cada vez que se llama a la función, la variable pila cambia de valor, como debe ser para apuntar al nuevo elemento insertado en la cabeza de la pila.

¿Y si no ponemos &?

Si no se pone & el programa compila y ejecuta sin errores, pero no funciona correctamente. Al no poner & la variable no se pasa por referencia, sino por copia. Esto implica que la variable pila de la función agregar_pila() es una variable independiente de la variable pila del programa principal.

Así cuando en la función haces pila=nuevo_nodo; estás modificando sólo la variable local de la función agregar_pila(), pero la variable pila de main() permanecerá inalterada.

En efecto, si pruebas a ejecutar el mismo experimento de antes pero quitando el & en el parámetro (no hace falta ni siquiera modificar main()), verás que ahora imprime:

0
Elemento 6 agregado correctamente a la pila
0
Elemento 12 agregado correctamente a la pila
0

O sea, la variable pila de main() sigue valiendo siempre NULL. Por ello, aunque el dato se inserta, no se actualiza el puntero a la cabeza de la cola, con lo que la implementación no es correcta.

Bonus: implementación en C (sin &)

Ya que C no tiene la posibilidad de marcar como "referencia" un parámetro ¿cómo se podría lograr esto mismo? (es decir, que la función modifique una variable que en realidad pertenece a main())

La solución es que en lugar de pasar el puntero pila a la función, le pasemos la dirección en que se halla el puntero pila, es decir &pila. De ese modo la función no recibiría ya un puntero a Nodo, sino la dirección de un puntero a Nodo, es decir, un puntero a puntero a Nodo. A través de esa dirección podrá modificar el puntero original.

La cosa sería entonces así:

#include<stdio.h>

struct Nodo{
    int dato;
    Nodo *siguiente;
};

void agregar_pila(Nodo **, int);

int main(){

    Nodo *pila = NULL;
    
    printf("%p\n", pila);
    agregar_pila(&pila, 6);
    printf("%p\n", pila);

    agregar_pila(&pila, 12);
    printf("%p\n", pila);

    return 0;
}

void agregar_pila(Nodo **pila, int n){ // <--- Atención al doble asterisco
    Nodo *nuevo_nodo = new Nodo();
    nuevo_nodo->dato = n;
    nuevo_nodo->siguiente = *pila;   // <--- Atención al asterisco aqui
    *pila = nuevo_nodo;              // <--- y aquí

    printf("Elemento %d agregado correctamente a la pila\n", n);
}

Y vemos al ejecutar que efectivamente la variable pila de main() resulta modificada como debe ser:

(nil)
Elemento 6 agregado correctamente a la pila
0x1761280
Elemento 12 agregado correctamente a la pila
0x17612a0
abulafia
  • 53,696
  • 3
  • 45
  • 80
1

¿hay alguna diferencia entre usar * y *& ?

Para saber la diferencia entre puntero (*) y referencia (&) te conviene leer ¿Cual es la diferencia entre int * e int &?.

No logro entender bien es que en el prototipo de la función le estoy pasando el *&

Sabemos que el puntero es un asterisco (*) y referencia es un et (&), así que *& es una referencia a puntero. Si leíste el hilo que enlacé, sabrás que una referencia (&) a efectos prácticos es indistinguible del objeto al que referencian, y es por eso que si quitas el et (&) el código sigue funcionando.

Podemos comprobarlo con este código de ejemplo:

#define MUESTRA(X) std::cout << __FUNCTION__ << '\n' \
        << '\t' << "Typeid: " << typeid(X).name() << '\n' \
        << '\t' << "Valor: " << X << '\n' \
        << '\t' << "Direccion: " << &X << '\n' \
        << '\t' << "Apunta a: " << *X << '\n'

void puntero(int *p)
{
    MUESTRA(p);
}

void referencia_puntero(int *&rp)
{
    MUESTRA(rp);
}

int main(int argc, char **)
{
    int *p = &argc;

    MUESTRA(p);

    puntero(p);
    referencia_puntero(p);

    return 0;
};

El código anterior genera una salida parecida a esta (he editado los valores de los punteros para mejor comprensión, en cada ejecución serán diferentes):

main
    Typeid: Pi
    Valor: 0x28
    Direccion: 0x18
    Apunta a: 1
puntero
    Typeid: Pi
    Valor: 0x28
    Direccion: 0xe8
    Apunta a: 1
referencia_puntero
    Typeid: Pi
    Valor: 0x28
    Direccion: 0x18
    Apunta a: 1

Vemos que el puntero p de main contiene la dirección 0x28 y está alojado en la dirección 0x18, cuando lo pasamos a la función puntero su contenido es el mismo que en main (0x28) pero es un puntero diferente porque su dirección ha variado (antes 0x18, ahora 0xe8); pero cuando lo pasamos a la función referencia_puntero tanto el valor como la dirección es la misma que en main, a efectos prácticos la referencia a puntero es indistinguible del puntero original (como cualquier referencia a otros datos).

Dado que tanto en formato referencia a puntero como en formato puntero, el contenido del puntero no varía (0x28) es "indiferente" usar puntero (*) o referencia a puntero (&*) para este caso concreto.

PaperBirdMaster
  • 44,474
  • 6
  • 44
  • 82