Foro de elhacker.net

Programación => Programación C/C++ => Mensaje iniciado por: GominaTilted en 29 Octubre 2019, 12:46 pm



Título: Clase pila estática
Publicado por: GominaTilted en 29 Octubre 2019, 12:46 pm
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.


Título: Re: Clase pila estática
Publicado por: K-YreX en 29 Octubre 2019, 14:52 pm
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
  1. class Persona{
  2.    private:
  3.        string nombre;
  4.        int edad;
  5.  
  6.    public:
  7.        Persona(){}
  8.  
  9.        Persona(string nombre, int edad){
  10.            this->nombre = nombre;
  11.            this->edad = edad;
  12.        }
  13.  
  14.        string getNombre()const{
  15.            return nombre;
  16.        }
  17.  
  18.        int getEdad()const{
  19.            return edad;
  20.        }
  21.  
  22.        const Persona& operator=(const Persona &original){
  23.            Persona *nuevaPersona = new Persona(original.getNombre(), original.getEdad());
  24.            return *nuevaPersona;
  25.        }
  26. };
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:
Código:
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.
Código:
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.


Título: Re: Clase pila estática
Publicado por: GominaTilted en 29 Octubre 2019, 17:07 pm
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í:
Código:
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++;
   }
}


Título: Re: Clase pila estática
Publicado por: K-YreX en 29 Octubre 2019, 17:41 pm
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
  1. void Pila::copiar(Pila p){
  2.    while(!p.vacia()){
  3.        apilar(p.cimaPila());
  4.        p.desapilar();
  5.    }
  6. }

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}


Título: Re: Clase pila estática
Publicado por: 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.


Título: Re: Clase pila estática
Publicado por: 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:

Código:
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".


Título: Re: Clase pila estática
Publicado por: K-YreX en 30 Octubre 2019, 02:24 am
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.

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

Código:
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).


Título: Re: Clase pila estática
Publicado por: GominaTilted en 30 Octubre 2019, 10:20 am
Lo del return es que lo estaba copiando a mano y me he equivocado  :-X. Las funciones implicadas son:

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

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

Código:
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.

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

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

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


Título: Re: Clase pila estática
Publicado por: K-YreX en 30 Octubre 2019, 16:47 pm
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.


Título: Re: Clase pila estática
Publicado por: GominaTilted en 30 Octubre 2019, 18:57 pm
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:
Código:
 const Pila& Pila::operator= (const Pila & p)
{
  delete[] v;
  v = new int[max];
  Copiar(p);
  return p;
}


Código:
 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.


Título: Re: Clase pila estática
Publicado por: K-YreX en 30 Octubre 2019, 22:52 pm
Bueno, a ver. Lo primero es que en la imagen estás usando un constructor de Pila que recibe un parámetro (int) y ese constructor no sé qué hace por lo que supondré que el parámetro es el tamaño de la pila y el constructor sin parámetros pone un tamaño estándar de 3.

Los <new> y los <delete> no se usan a lo loco pensando "así me aseguro de que hay espacio reservado", no. Tienes que saber cuando hay espacio reservado y cuando no, reservar la memoria justa y necesaria y liberarla correctamente.

Los constructores son las funciones que llaman los objetos cuando se crean. Estos objetos como justo se están creando no tienen nada todavía por lo que sí necesitas reservar memoria. El de por defecto está bien, el de con parámetros supondré que también pero el de copia no. Cuando creas una instancia con el constructor de copia, no le estás asignando espacio.
Código
  1. Pila miPila1(5); // sigo suponiendo que esto es una pila de longitud 5
  2. Pila miPila2(miPila1); // miPila2 no tiene memoria reservada para el array interno

El destructor como bien dices debe liberar la memoria de esa Pila. Y es más, no necesitas <Vaciar()> la Pila; con liberar la memoria reservada sería suficiente.

La función <Copiar()> no está bien. Esa función la usarás siempre sobre instancias que has creado antes por lo que ya tienen memoria reservada. La duda es: tendrá el mismo tamaño la Pila sobre la que copias que la Pila que copias? Es algo que no puedes asegurar si puedes crear Pilas de distintos tamaños por lo que lo que tendrías que hacer es liberar la memoria de la que quieres copiar y asignarle la misma longitud que a la otra nuevamente (pero sin olvidar primero liberar la memoria que ya tenía).
Código
  1. Pila pilaTam5(5); // se reserva memoria para 5 elementos
  2. Pila pilaTam10(10); // se reserva memoria para 10 elementos
  3. pilaTam5.Copiar(pilaTam10); // la funcion Copiar() tiene que borrar la memoria para 5 elementos y crear nueva memoria para 10

El operador = está bien ya que haces lo que te digo para la función <Copiar()> pero claro, lo de borrar la memoria que tenía y crear memoria nueva... una de dos, o lo haces dentro de la función <Copiar()> o lo haces fuera pero no lo hagas dos veces.



EDIT: Si las funciones <Altura()> y <Altura2()> son para mostrar el tamaño de la Pila, no tiene sentido que las hagas externas a la clase y reciban la Pila como parámetro. Es mejor que las hagas dentro de la clase y que no crees dos funciones diferentes para lo mismo.


Título: Re: Clase pila estática
Publicado por: GominaTilted en 31 Octubre 2019, 12:48 pm
Ahora tengo dos constructores, el por defecto y el que le paso el tamaño de la pila. Todas las cosas que no tengan sentido en cuanto al planteamiento del problema es (como Altura y Altura2), tienen que ver con el enunciado de la práctica, está hecho para que si no te va la sobrecarga no te funcionen bien las recursivas con referencias.
Entonces a lo que iba, si le paso al de copia por parámetro el tamaño máximo de la pila no va, pero si no se lo paso lo ejecuta en bucle.
Mis constructores:
Código:
Pila::Pila()
{
    cima = -1;
    max = 10;
    accesos = 0;
   
    v = new int[max];
   
}

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

Pila::Pila (const Pila & p)
{
  cima = -1;
  max = 10;
  accesos = 0;
  v = new int[p.max];
  Copiar(p);
}

Pila::~Pila()
{
  delete[] v;
}

Y aquí Copiar() y la sobrecarga:

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

const Pila& Pila::operator= (const Pila & p)
{
  cout << "Se ejecuta la sobrecarga" << endl;
  Copiar(p);
  return p;
}

La función Copiar() funciona bien, ya que al ponerle cout saca los valores correctos, entonces el error supongo que es del constructor de copia. Cuando no entra en bucle infinito me sale el error "double free or corruption (fasttop)", el cual he buscado en stack overflow pero no he entendido mucho, simplemente que es posible que al hacer la asignación libere el espacio de memoria de la parte derecha, no izquierda. Copia bien en la pila 2, pero la original la modifica y se queda 0 0 3 4, como si hubiese borrado las dos primeras posiciones o fueran espacios de memoria a los que no se puede acceder.


Título: Re: Clase pila estática
Publicado por: K-YreX en 31 Octubre 2019, 13:41 pm
El constructor de copia tiene un problema y es que le asignas 10 a <max> en vez de asignarle <p.max> ya que la longitud del nuevo tiene que ser la misma que la del que copias.
Y la función <Copiar()> tiene otro problema y es que estás incrementando <cima> pero en ningún momento la pones a -1 antes de empezar a incrementar y tampoco actualizas el valor de <max>. Además como ya no estás usando las funciones <Apilar()> y <Desapilar()> en la función <Copiar()> (y por tanto ya no estás modificando la original) ahora sí puedes (y debes) pasar el parámetro constante y por referencia.


Título: Re: Clase pila estática
Publicado por: GominaTilted en 31 Octubre 2019, 13:54 pm
El constructor de copia tiene un problema y es que le asignas 10 a <max> en vez de asignarle <p.max> ya que la longitud del nuevo tiene que ser la misma que la del que copias.
Y la función <Copiar()> tiene otro problema y es que estás incrementando <cima> pero en ningún momento la pones a -1 antes de empezar a incrementar y tampoco actualizas el valor de <max>. Además como ya no estás usando las funciones <Apilar()> y <Desapilar()> en la función <Copiar()> (y por tanto ya no estás modificando la original) ahora sí puedes (y debes) pasar el parámetro constante y por referencia.
No sabía si tenía que actualizar el valor de max, ya que como el array se crea en función de p.max, pero claro, no había pensado que interviene todo el objeto. El resto podría haber estado dándole vueltas 1 mes y no lo saco xD. Muchas gracias, ya va. ¿Por qué en la función copiar es conveniente pasar el objeto por referencia y ponerlo como const? Gracias de nuevo, esta es la última pregunta de verdad ^^.


Título: Re: Clase pila estática
Publicado por: K-YreX en 31 Octubre 2019, 14:06 pm
Siempre que los objetos que se pasan como parámetro no se van a modificar es recomendable pasarlos constantes para asegurar que pase lo que pase no se va a modificar (como decirle al compilador "no me dejes modificar este objeto ni por error") y por referencia para que así se pase la dirección de memoria del objeto original y no se cree una copia local (ya que si no se pasa por referencia, se pasa por valor y entonces lo que se hace es crear una copia del objeto con su consiguiente gasto de memoria).


Título: Re: Clase pila estática
Publicado por: GominaTilted en 31 Octubre 2019, 14:50 pm
Siempre que los objetos que se pasan como parámetro no se van a modificar es recomendable pasarlos constantes para asegurar que pase lo que pase no se va a modificar (como decirle al compilador "no me dejes modificar este objeto ni por error") y por referencia para que así se pase la dirección de memoria del objeto original y no se cree una copia local (ya que si no se pasa por referencia, se pasa por valor y entonces lo que se hace es crear una copia del objeto con su consiguiente gasto de memoria).
Muchas gracias, no lo sabía.