Ayuda con clases en C++

Josekron

Hola, en una asignatura llamada "Tipos Abstractos de Datos" hemos empezado a dar las clases en C++, o lo que es lo mismo, nos han dado un manual que no se entiende nada y un plazo para terminar una práctica.

Llevo todo el finde mirando manuales por la red y no me aclaro nada, no soy capaz ni de trabajar con una simple cola. Se nos ha suministrado el fichero .hpp, el cual no podemos modificar y tenemos que crear el .cpp con la implementación de las funciones que aparecen en dicho fichero.

//Cola.hpp

#ifndef __Cola__
#define __Cola__

class Cola{
 public:
 // los elementos de la cola son de tipo entero
 typedef int Item;
 // constructor de la clase. Inicializa una cola y la deja sin elementos
 Cola();
 // destructor de la clase. Libera los recursos ocupados cuando acaba el ambito del objeto
 ~Cola();
 // agrega el elemento x como ultimo elemento de la cola
 void meter(Item x);
 // devuelve el primer elemento de la cola. Si esta vacia, eleva la excepcion ColaVacia
 Item frente() const;
 // elimina el primer elemento de la cola
 void sacar();
 // devuelve verdadero si no hay elementos en la cola, falso en caso contrario
 bool esVacia() const;
 // excepciones que se elevan en caso de error
 enum ErrorCola{ColaVacia};
 private:
 // tipos privados
 struct Nodo{
 Item dato;
 Nodo * siguiente;
 };
 // atributos
 Nodo * ultimo;
};
#endif

Mirando manuales y demás he logrado implementar la primera función (no sé si estará bien):

//Cola.cpp
#include <cstdlib>
#include "Cola.hpp"

Cola::Cola()
{
    Cola c1;
    c1.ultimo=NULL; 
}
Cola::~Cola()
{
    
}

A partir de ahí, estoy rallado pues para borrar una cola antes creaba un puntero auxiliar que fuese recorriendo la cola y borrandose y tal.

//sin clases:
//definición:
typedef TNodoCola *TPuntero;
   struct TCola
   { 
      TPuntero frente;
      TPuntero final;
   };
----------------------
//implementación:
 void DestruirCola(TCola &c)
 {
   TPuntero aux;
   while (!ColaVacia(c))
   {
     aux = c.frente;
     c.frente = c.frente->sig;
     delete aux;
   }
   c.final = NULL;
 }

Pero, ¿con clases cómo creo un puntero? En el .hpp el puntero "ultimo" aparece por arte de magia Nodo * ultimo. No se si me entiendéis...

Si no tenéis tiempo para explicarme lo básico, me conformo con que implementéis 1 o 2 funciones para tenerlas como ejemplo para implementar las demás.

Saludos.

Poisonous

El constructor lo tienes mal, deberia ser

Cola::Cola()
{
    this.ultimo=NULL; 
}

Y si necesitas un puntero en cualquier metodo

Pones

Nodo * aux;

y lo usas normalmente. Te hago otra facilita xd

bool Cola::esVacia() const {
  if (this.ultimo == NULL)
     return true;
  else 
     return  false;

}

Ojo, no he probado compilar ni nada, no se si estarán formalmente correctas.

Josekron

Gracias por contestar, ya sé como crear los punteros :)

Al poner this.ultimo, Dev-C++ me da el error de que ultimo no es un tipo.

Al no pasarme la cola por parámetro en la función, no sé como trabajar con ella, ¿sería todo con this. (this.ultimo, this.ultimo->siguiente,this.ultimo->dato,...)?

Riu

#3 cuando la termines me la puedes pasar plz?, es simple curiosidad para ver como lo has hecho, ya que yo no fui capaz de hacerla cuando estaba en la carrera...

dagavi

A ver, vamos por partes xD

El constructor:

Cola::Cola()
{
    Cola c1;
    c1.ultimo=NULL; 
}

Como puedes observar en ningún momento hay un "return" ni nada, por lo que podrías suponer que ese "c1" desaparece, y por tanto eso no hace nada. Como ya te han dicho, está mal.

Debes de pensar que se instancia un objeto de lo que sea y después se llama al constructor, que por lo general suele contener inicializaciones (obviamente puede contener cualquier tipo de código).

Las funciones y acciones, en orientación a objetos se llaman métodos, se aplican* sobre una instancia. Concretamente sobre la instancia que llama al método. Pongamos un ejemplo que supongo que habrás visto y utilizado:

vector<int> v1(10), v2(20);
v1.size(); // Devuelve 10
v2.size(); // Devuelve 20

El constructor pasa algo por el estilo, se crea una instancia y después se aplica el código del constructor sobre la instancia creada.

Pero ¿como coño modifico entonces los valores en un método que no tiene ningún parámetro ni nada?

Pues simplemente haciendo asignaciones normales como siempre a las variables de la clase o llamando a otros métodos. También puedes referirte al propio objeto con el puntero "this" pero solo es necesario si hay conflicto entre los nombres, ahora te quedará más claro.

Imagina una clase, bastante inútil, que solo guarda un número y puede realizar cosas sobre el.

class Numero {

private:
    int numero;
    
public: Numero(); // Constructor por defecto Numero(const Numero& n); // Constructor por copia Numero(int numero); // constructor con parámetros
void setNumero(int numero); int getNumero() const; };

Se puede observar un constructor por defecto, uno con parámetros y otro por copia (este último es también bastante estándar). Además daremos dos métodos, una para cambiar el valor del número guardado y otro para consultarlo.

Pues el constructor por defecto podría ser, si lo queremos inicializar a 0 el número, algo como:

Numero::Numero() {
    numero = 0;
}

El constructor por copia en este caso es simple, solo debemos copiar los valores. Si fuera algo más elaborado, por ejemplo una pila y quisiéramos crear una pila nueva este constructor debería encargarse también de copiar todos los nodos de la pila (si se quiere implementar como una pila encadenada):

Numero::Numero(const Numero& n) {
    numero = n.numero;
}

Destacar que pasamos el número "original" como una referencia constante. Constante porque no queremos modificarlo y es una buena costumbre, por referencia para optimizar el paso del parámetro. (imagínate que esto tuviera 1000 ints y lo pasaras por valor...)

Finalmente un constructor con parámetro, y encima ese parámetro tiene el mismo nombre que nuestra variable en la clase. ¿Cuando ponga "numero" a que hago referencia? ¿A la variable o al parámetro? Pues al parámetro que es el que está "mas cerca", para hacer referencia al de al clase tienes el this.

Numero::Numero(int numero) {
    this->numero = numero;
}

Obviamente puedes usar el this siempre que quieras referirte al propio objeto, en los ejemplos anteriores también se podría haber usado.

El resto sigue la misma filosofía:

void Numero::setNumero(int numero) {
    this->numero = numero;    
} int Numero::getNumero() const { return numero; }

¿Porque getNumero() tiene un const? Eso indica que ese método no modificará el estado, normalmente se usa en métodos consultores (como este caso).

Igualmente también es bueno (y casi obligado si se trabaja con memoria dinámica) crear destructores. Si borras una Pila (implementada con nodos encadenados) deberías ir cargándote todos los nodos que forman la pila.

*Excepto sin son métodos estáticos.

dagavi

#3 this, como te digo en el tochopost anterior, es un puntero al propio objeto.
Los punteros se acceden en C++ o con el *this o con -> (esto es muchísimo más cómodo).

Por lo tanto a un campos se accede:

this->campo

O en su versión más guarra:
(*this).campo

E igualmente con los métodos, el constructor con variable de antes se podía haber definido usando el propio "setNumero"

Numero::Numero(int numero) {
    this->setNumero(numero);
}

Te remito como otro ejemplo más completo a dos códigos escritos por mi en la Wikipedia:
· Una implementación de Pila: http://es.wikipedia.org/wiki/Pila%28estructura_de_datos%29#En_C.2B.2B
· Y otra de Cola: http://es.wikipedia.org/wiki/Cola
%28estructura_de_datos%29#Colas_en_C.2B.2B
(siendo la Wikipedia no se si me lo habrán modificado, pero parece que no)

Estos ejemplos usan plantillas, pero no debería asustarte, simplemente piensa que las "T" son un tipo cualquiera (ej: vector<int> pues T = int)

Edit:
Última cosa, pero es para arrancarse los ojos xD

No hagáis NUNCA algo como:

bool Cola::esVacia() const {
  if (this.ultimo == NULL)
     return true;
  else 
     return  false;

}

Ahí lo que pone exactamente es:
return this.ultimo == NULL;

Y ya está.

this.ultimo == NULL retorna un booleano, que es exactamente lo que quieres retornar, no debes de decir "si es cierto, retorno cierto. Si es falso retorno falso", coño, retorna directamente lo que te da xD
Es lo mismo que:

if (x < y) return true;
else return false;

Retorna directamente x < y ! (esto es un ejemplo genérico, no que lo haya visto aquí).

Josekron

#4 Claro, no problem.

#6 Muchas gracias por tu explicación, me ha servido mucho de ayuda. Creo que ya lo empiezo a controlar. Según he entendido, el objeto al estar dentro de private, se puede usar directamente sin que te lo pasen por parámetro (me explico como el culo).

He implementado estas funciones y por lo menos el Dev-C++ no me da ningún error (otra cosa es que funcione o no xD)

Cola::Cola()
{
    ultimo=NULL; 

}
Cola::~Cola()
{
    Nodo *aux;
    while (!esVacia()) //¿no habría que poner !esVacia(ultimo)?
    {
       aux = ultimo;
       ultimo = ultimo->siguiente;
       delete aux;
    }
    ultimo = NULL;
}
bool Cola::esVacia() const 
{
  return ultimo==NULL;
}

El this. como dije, por ejemplo this.ultimo, al usarlo me da error de que ultimo no es un tipo. Pero bueno, no importa si no hace falta usarlo. EDIT: claro, no había caido en que es un puntero y no un tipo xD (this->ultimo).

JuAn4k4

#6 Los compiladores ya optimizan eso por ti, lo normal seria que ni llamara a funcion.
Edit: Una cosa,
Numero::Numero(const Numero& n) {
numero = n.numero;
}
¿funciona? porque violas la encapsulacion ( de c++ no lo he tocado mucho. )

#7
Voy a tratar de explicarte la Orientacion a Objetos de una forma muy burda.

Tienes Clases y tienes Objetos.
Clase: Cola.
Objeto: colaDeSupermercado , colaINEM, ...

colaDeSupermercado.esVacia(); === ¿ Hola ColaDeSupermercado, estas vacia ? Le mando un mensaje a la instancia de cola que se llama (esta referenciada) "colaDeSupermercado".

Dentro de la cola de supermercado, hay un chino metido en ese objeto, que puede hacer todas estas cosas:
Mirar todos sus atributos.

Para saber si esta vacia puede mirar a quien apunta "ultimo".
Entonces el chino (eres tu si) dira: Si this(MI).ultimo == NULL entonces si estoy vacia.

PD: ¿ Que clase de cola te han hecho implementar ? ¿ Una en la que solo miras al ultimo ? ¿ No sera primero y ultimo ? Es lo logico porque cojes por delante y metes por detras. (Que no me lea ningun panchito esta ultima frase)

Lo de this.blablabla es bueno ponerlo.

dagavi

"el objeto al estar dentro de private, se puede usar directamente sin que te lo pasen por parámetro (me explico como el culo)."

No es así. El método no es que esté dentro del private, del public o del protected. Es que el método ES de la clase.

Por lo que tu ámbito de visibilidad abarca también los métodos y atributos de la propia clase.
Así que desde un método de una clase puedes acceder a todos sus atributos y funciones, ya sea directamente (si no hay conflictos de nombres) o con this->algo (si así lo necesitas o prefieres).

Lo puedes ver como que en cada método se define un parámetro implícito, ese parámetro implícito es un puntero al objeto que lo ha llamado:

int Numero::getNumero(Numero* this) {
return this->numero;
}

Simplemente que ese parámetro no aparece en la lista de argumentos de la función y además no es necesario ni poner el "this" si no hay conflicto de nombres ya que el propio ámbito de visibilidad te permite hacer directamente:

return numero;

Edit: #8 Pero una cosa es que optimice y otra que después tengamos que leerlo nosotros xD (ya que supongo que harás referencia a lo de esVacia() )

Josekron

#8 Es verdad, se me olvidó decir el tipo de cola que es... es una cola circular.

La Cola está implementada mediante una lista circular, de forma que el puntero al campo siguiente del último elemento apunta al primero. El atributo de un objeto de tipo Cola apuntará directamente al último elemento y, por tanto, indirectamente al primero tal y como se ve en la siguiente figura.

Uff me hago un lio con la orientación a objetos, intentaré pasarme mañana por el despacho del profesor aunque no es día de tutorías.

Tres cosas y os dejo en paz y mañana le doy la tabarra al profesor que para eso es su trabajo xD:

1- ¿porqué os empeñais en poner this.ultimo? ¿no sería this->ultimo ya que es un puntero? Con this.ultimo me da un error de que ultimo no es un tipo.

2- ¿Cómo compruebo esVacia()? es que si pongo ultimo.esVacia() como en el ejemplo de #8; me dice que esVacia no es un tipo.

3º- ¿Las funciones de #7 están bien o me he hecho un lio con ultimo?

Edit: #9 gracias por la aclaración si hay que poner o no el this y cómo usarlo y a #8 y #9 por la explicación de las clases y objetos

dagavi

1) Como ya te he dicho, si es "this->" ya que es un puntero (y esto es C++)
2) esVacia en un metodo de Cola, no de Nodo. No tiene el menor sentido hacer "ultimo.esVacia()" ya que último es un Nodo (o mejor dicho un puntero a Nodo, así que incluso debería ser ultimo->esVacia() pero ya te digo que no tiene sentido alguno)

Un usuario de esa clase lo que hará será comprobar si una cola es vacía haciendo;
Cola c;
// Cosas con c
c.esVacia();

3) Si "sacar" está bien implementado lo que puedes hacer es:
while (not esVacia()) sacar();

Y en 1 línea tienes el destructor implementado.
Diría que la implementación que das del destructor no podría funcionar ya que según tu dibujo de #10 ningún puntero "nodo->siguiente" será NULL, por lo que nunca podrás asignar "ultimo = ultimo->siguiente = NULL" ya que todos tienen un siguiente.

Lo más simple es lo que te digo, utiliza tu propio sacar() [obviamente siempre que esté bien implementado] mientras no esté vacía.

Josekron

#11 Si, no tuve en cuenta que la cola era circular. Pensandolo de forma rápida supongo que será así:

Cola::Cola()
{
    ultimo=NULL;
    ultimo->siguiente = ultimo; 

}

void Cola::sacar() // elimina el primer elemento de la cola
{
     if (!esVacia())
     {
         Nodo *aux;
         aux = ultimo->siguiente; //ultimo->sig apunta al primer nodo de la cola
         ultimo->siguiente = ultimo->siguiente->siguiente;
         delete aux;            
} else { //ErrorCola{ColaVacia}; }
}

Bueno, corregiré los fallos y mñn iré a tutoría sino seguiré dando por saco aqui xD

Gracias por la ayuda :)

dagavi

El constructor ya no funciona.

No puedes decir:
ultimo = NULL;

Y después:
ultimo->siguiente ....

No puedes acceder a ultimo ya que último no existe, es un puntero NULL.

Es como poner:

int* x = NULL; // Puntero a NULL

*x = 3; // En la posición de memoria apuntada por X meto 3, bonita excepción.

JuAn4k4

El primer nodo que creas es el primero y el ultimo.

Al añadir

Si no hay ninguno : Creas 1 y ultimo apunta a el y siguiente a el también.
Si hay un nodo ( this->ultimo == this->ultimo->siguiente ) :: Creas un nodo y se lo pones a : this->ultimo->siguiente->siguiente. y despues se lo pones a ultimo.

hay mas de 1 nodo. HAZTE EL DIBUJO.
Creas un nodo nuevo (N) y haces
aux= ultimo->siguiente.
ultimo->siguiente = N
N->siguiente = aux;

Al crear la cola, quita lo de ultimo->siguiente = ultimo; No tiene sentido y sobra.

ultimo.esVacia() te dira que no, al que le preguntas si esta vacio es a la cola, no a un atributo., this.esVacia() desde dentro de la clase si te refieres al objeto THIS, Cola c; c.esVacia() para otras listas que tengas que no sean el objeto en cuestion.

Tienes que darte cuenta de una cosa, estas definiendo el comportamiento de todas las LISTAS, y el objeto "THIS" hace referencia a una sola, a la que le mandan el mensaje del que te hablo (son los metodos).

NeB1

muy importante lo que dice #14 cuando trabajas con punteros, sean listas, colas o lo que sean, DIBUJATELO.

Josekron

Bueno ya lo he hecho y comprobado que funciona. Pongo la solución para #4

Cola.cpp
//Menú principal para probar las funciones:
principal.cpp

#13 lo hice rápido y al rato me dí cuenta de que era una tontería lo que había puesto. Para inicializarla, simplemente es ultimo=NULL como tenía desde el principio porque aunque sea una cola circular, está vacía sin ningún nodo.

Usuarios habituales

  • Josekron
  • NeB1
  • JuAn4k4
  • dagavi
  • Riu
  • Poisonous