Clase pila estática

Iniciado por GominaTilted, 29 Octubre 2019, 12:46 PM

0 Miembros y 1 Visitante están viendo este tema.

GominaTilted

Buenas, necesito implementar una clase pila estática, aunque la pila ne sí no es un array, sino un puntero a un array de enteros. El problema es que no sé cómo implementar la función copiar. Tengo un constructor de copia declarado como : Pila(const Pila&), y una sobrecarga del = tal que así const Pila& operator= (const Pila&);

Entonces, la cabecera de la función copiar debería de ser void Copiar(const Pila &), pero al intentar utilizar = para asignar las posiciones de la pila que le paso por referencia a otra y así realizar la copia me dice que al ser const no lo puede modificar (obviamente, pero es que solo estoy leyendo, no he cambiado nada). Luego viene el problema de que no puedo desapilar sobre la que paso por referencia, menudo lío llevo xD:

También me gustaría que alguien me explicara la recursividad con un ejemplo que no fuera el factorial xD. Tengo que implementar de manera recursiva una función que devuelva el tamaño de la pila, pero solo le paso por argumento una pila, no lo entiendo. Lo único que se me ocurre es poner en el caso base i = 0, y en el caso recursivo después de cada llamada a la función i++, pero no sé si funcionaría como en prolog.

K-YreX

El compilador no sabe si sólo estás leyendo o estás modificando algo. Las funciones miembro de la clase que sólo "leen" y no van a modificar nada es recomendable declararlas como constantes (no constante el valor de retorno, sino constante la función). Haciendo esto permites que tanto los objetos constantes como los variables llamen a esa función pero si no lo pones entonces el compilador asume que esa función modifica algo y no te deja usarla con objetos constantes.

Te pongo un pequeño ejemplo para que veas como funciona:
Código (cpp) [Seleccionar]

class Persona{
    private:
        string nombre;
        int edad;

    public:
        Persona(){}

        Persona(string nombre, int edad){
            this->nombre = nombre;
            this->edad = edad;
        }

        string getNombre()const{
            return nombre;
        }

        int getEdad()const{
            return edad;
        }

        const Persona& operator=(const Persona &original){
            Persona *nuevaPersona = new Persona(original.getNombre(), original.getEdad());
            return *nuevaPersona;
        }
};

Como ves en la sobrecarga del operator=, estamos pasando un objeto de tipo Persona constante y por referencia y después estamos llamando a las funciones <getNombre()> y <getEdad()>. Para poder llamar a esas funciones, éstas deben ser constantes. (Puedes probarlo y quitar los <const> de los <get> para que veas que da un error.




Para el tema de la recursividad tienes que pensar en que el/los parámetros que le pases a la función tienen que cambiar en algún momento. Entonces para tu caso que quieres calcular el tamaño de la pila puedes hacer lo siguiente:
1º Pensar en el problema sin usar recursividad.
Podrías hacer por ejemplo:

tam := 0
MIENTRAS !pila.vacia HACER
    pila.pop // quitar el elemento del tope
    tam := tam + 1 // sumar uno al tamaño de la pila
FIN MIENTRAS


2º Quitar el bucle.
Para ello tienes que pensar en que cada vez que llames a la función es como una iteración. Y en cada iteración lo que hacías antes era comprobar si estaba vacía y si no lo estaba, eliminabas el tope e incrementabas el tamaño en 1.

Funcion tamRecursivo(Pila pila):int
INICIO
    SI pila.vacia HACER // El caso base (el que termina la recursividad): si la pila esta vacia...
        return 0 // devuelve un 0
    // Si no esta vacia...
    SINO HACER // No hace falta este <else> ya que antes hay un return. Lo pongo para que lo entiendas mejor
        pila.pop // Quitamos un elemento a la pila
        return 1 + tamRecursivo(pila) // Y devolvemos un 1 + el tamaño de la pila al haber quitado un elemento
    FIN SI
FIN

Recuerda no pasar la pila constante ya que la estás modificando ni por referencia ya que entonces te cargarías la original.
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

GominaTilted

Gracias, la recursividad ya la he entendido. El problema lo sigo teniendo con la función Copiar, ya que al ser una array la pila, he planteado lo siguiente:
- La cima de inicio está en la posición -1.
- Desapilar resta 1 a la variable privada "cima", y apilar suma 1 y en esa posición siguiente sobreescribe el dato.

Por lo tanto mi función Copiar es así:

void Pila::Copiar(const Pila &p)
{
  Pila p1;
  int i = 0;
  while (!p.Vacia()) //Vacia devuelve true si la cima == -1
  {
    p1.Apilar(p.Cimapila());
    p.Desapilar();
    i++;
   }
}

K-YreX

#3
Lo que tú tienes es una función miembro de una clase, por lo tanto esa función se la aplicas a una pila que ya existe, no tienes que crear una Pila dentro de la función.
La Pila p1 es local a esa función. Tú tienes que trabajar con el objeto implícito que llama a la función <Copiar()>.
Además de eso si estás pasando como parámetro una Pila <p> por referencia y constante, no puedes desapilarla porque es constante.
Código (cpp) [Seleccionar]

void Pila::copiar(Pila p){
   while(!p.vacia()){
       apilar(p.cimaPila());
       p.desapilar();
   }
}



EDIT: Además si lo haces de esa forma estás invirtiendo las pilas.
Si tienes una pila original: p = {1,2,3,4,5} y llamas a la función copiar con otra pila p2, el resultado sería: p2 = {5,4,3,2,1}
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

GominaTilted

Muchas gracias. No había pensado que podía usar las funciones sobre el propio objeto, muchas horas delante de esto xD.

GominaTilted

Vengo con la última duda, pensaba que había acabado pero no :(. He intentado hacer la sobrecarga del operador =. Tengo esto:


const Pila& Pila::operator= (const Pila & p)
{
    Vaciar(); //vacía la pila objeto
    Copiar(p); //copia la p en la pila objeto.
    Return p;
}


Creo que está bien, pero al compilar la línea p = p2 (siendo p una pila llena con valores {1...4} y p2 una pila vacía), salta el error "exited, segmentation fault".

K-YreX

Cita de: GominaTilted en 29 Octubre 2019, 18:59 PM
Muchas gracias. No había pensado que podía usar las funciones sobre el propio objeto, muchas horas delante de esto xD.
Esa es precisamente la utilidad de las funciones miembro de una clase: actuar sobre el objeto/instancia que las llama de forma implícita.


Cita de: GominaTilted en 29 Octubre 2019, 21:22 PM
Vengo con la última duda, pensaba que había acabado pero no :(. He intentado hacer la sobrecarga del operador =. Tengo esto:


const Pila& Pila::operator= (const Pila & p)
{
    Vaciar(); //vacía la pila objeto
    Copiar(p); //copia la p en la pila objeto.
    Return p;
}


Creo que está bien, pero al compilar la línea p = p2 (siendo p una pila llena con valores {1...4} y p2 una pila vacía), salta el error "exited, segmentation fault".
Es mejor que añadas también el código de las funciones que intervengan en todo el proceso de alguna manera u otra para saber dónde está el fallo exactamente.
Además ten cuidado con las mayúsculas. Suele ser habitual usar la nomenglatura UpperCamelCase (que como se nota en el nombre, cada palabra empieza por mayúscula, para designar clases) y la nomenglatura lowerCamelCase (todas las palabras empiezan por mayúscula menos la primera) para funciones. Esto es una convención, no son reglas estrictas pero en el caso del <return> si es necesario ya que C++ es un lenguaje sensible a mayúsculas y minúsculas (no es lo mismo escribir una palabra usando mayúsculas que minúsculas).
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

GominaTilted

#7
Lo del return es que lo estaba copiando a mano y me he equivocado  :-X. Las funciones implicadas son:


void Pila::Vaciar()
{
 while(!Vacia())
   Desapilar();
}


void Pila::Copiar(Pila p)
{
 Pila aux;
 while (!p.Vacia())
 {
   aux.Apilar(p.Cimapila());
   p.Desapilar();
 }
 while (!aux.Vacia())
 {
   Apilar(aux.Cimapila());
   aux.Desapilar();
 }
}


void Pila::Apilar(int n)
{
   accesos++;
   
   if (cima >= max)
       throw Pilallena();
   else
   {
     cima ++;
     v[cima] = n;
   }
       
}

void Pila::Desapilar()
{
   accesos++;
   
   if (cima == -1)
       throw Pilavacia();
   else
       cima--;
}

int Pila::Cimapila()
{
   int res;
   v[cima] = res;
   accesos++;
   
   return res;
}


EDIT: dejo también el constructor de la clase, constructor de copia y desrtuctor.

Pila::Pila()
{
    cima = -1;
    max = 3;
    accesos = 0;
   
    v = new int[max];
   
}

Pila::Pila (const Pila & p)
{
  cima = -1;
  Copiar(p);
}

Pila::~Pila()
{
  Vaciar();
}

K-YreX

Te recomiendo cuando pongas un código en el que tienes problemas que lo copies del original. No sería la primera vez que el código está mal escrito, al escribirlo aquí se escribe bien y es imposible detectar el error.
Aunque en este caso veo un par de problemas:
  • Si en el constructor asignas siempre un tamaño de 3 al array que usas como contenedor para la pila, es mejor que directamente lo hagas de forma estática (v[MAX]) en vez de usar un puntero y asignarlo de forma dinámica (te ahorrarás de tener que liberar la memoria dinámica manualmente cosa que parece que no haces).
  • En el constructor reservas memoria para el array pero... y en el constructor de copia?? En ningún momento estás reservando memoria. Tienes que pensar qué estado tendrá un objeto cuando llama a una función. En el caso de los constructores, son objetos que todavía no se han creado (se crean con el constructor) entonces todavía no tienen memoria reservada. Aprovecho para decirte que pienses cómo debería funcionar la función <Copiar()> (si debería reservar memoria o no).
  • La función <Cimapila()> no funciona bien. Estás perdiendo el valor del tope y devolviendo un valor basura.
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

GominaTilted

Gracias, ya tengo la <Cimapila()>, estaba la asignación al revés. Supongo que en <Copiar()> sí que tengo que generar un espacio nuevo en memoria, ya así te aseguras que hay un espacio reservado, aunque creo que al crear el objeto se debería de reservar ese espacio, no lo entiendo muy bien. Al hacer la sobrecarga de la asignación estamos en el mismo caso. Lo que si que tendría que hacer es un "delete [] v" en el destructor en vez de <Vaciar()>, y creo que también en la sobrecarga del operador.
Te dejo la función <Copia()>, la sobrecarga y un pantallazo con los valores que obtengo al realizar la asignación:
const Pila& Pila::operator= (const Pila & p)
{
  delete[] v;
  v = new int[max];
  Copiar(p);
  return p;
}


void Pila::Copiar(Pila p)
{
  v = new int [max];
  for (int i = 0; i < p.cima; i++)
    v[i] = p.v[i];
}


https://ibb.co/LzQbPKT

PD: perdón no sé cómo se ponen imágenes xD.