Ver Mensaje Individual
  #3 (permalink)  
Antiguo 28/04/2016, 17:32
eferion
 
Fecha de Ingreso: octubre-2014
Ubicación: Madrid
Mensajes: 1.212
Antigüedad: 9 años, 6 meses
Puntos: 204
Respuesta: Duda con programa que gestiona ITV

Por lo que veo estás programando en C++. Has de saber entonces que

Código C++:
Ver original
  1. typedef struct vehiculo

lo puedes dejar en simplemente en

Código C++:
Ver original
  1. struct vehiculo

Que va a funcionar perfectamente. De hecho, el typedef sirve única y exclusivamente para definir alias y su uso es "typedef <tipo> <alias>" y el uso que tu le estás dando es "typedef <tipo>".

Esto en C se usaría así:

Código C:
Ver original
  1. typedef struct vehiculo
  2. {
  3. } vehiculo;

O así:

Código C:
Ver original
  1. struct vehiculo
  2. {
  3. };
  4. typedef struct vehiculo vehiculo;

Y la ventaja que obtienes con esta sintaxis (ojo, para el caso de struct y C) es que ya no es necesario usar struct para referirte a la variable.

Peeeero como hemos dicho, en C++ esto ya no es necesario, luego la siguiente declaración funciona igual de bien y es más limpia:

Código C++:
Ver original
  1. struct vehiculo
  2. {
  3.   vehiculo *sig;
  4. };

Aun así, no es por tocar mucho las narices pero yo esa clase la dividiría en dos: Por un lado los datos propios del vehículo y por otro la gestión de nodos:

Código C++:
Ver original
  1. struct vehiculo
  2. {
  3.   string matricula;
  4.   bool estado ;
  5.   char tipo;
  6.   string marca;
  7.   string modelo;
  8. };
  9.  
  10. struct nodo
  11. {
  12.   vehiculo datos;
  13.   nodo* sig;
  14. };

En programación conviene no mezclar churras con merinas para evitar errores. También estaría bien, dado que tienes tipos nativos (bool, char, un puntero...) implementar los constructores por defecto para evitar tener que hacer inicializaciones manuales so pena de dejar basura en las clases:

Código C++:
Ver original
  1. struct vehiculo
  2. {
  3.   string matricula;
  4.   bool estado ;
  5.   char tipo;
  6.   string marca;
  7.   string modelo;
  8.  
  9.   // opcion 1 para el constructor por defecto
  10.   vehiculo() : estado(false), tipo(' ')
  11.   {}
  12.  
  13.   // opcion 2: son equivalentes
  14.   vehiculo()
  15.   {
  16.     estado = false;
  17.     tipo = ' ';
  18.   }
  19. };
  20.  
  21. struct nodo
  22. {
  23.   vehiculo datos;
  24.   nodo* sig;
  25.  
  26.   nodo() : sig(0)
  27.   {}
  28. };

Otro consejo que te doy, pero depende también de si es requisito del ejercicio hacerlo así o no, es que en tu caso el tipo no debería ser un char sino un enumerado:

Código C++:
Ver original
  1. enum TipoVehiculo
  2. {
  3.   Ninguno,
  4.   Gasolina,
  5.   Diesel,
  6.   Moto,
  7.   Industrial
  8. };
  9.  
  10. struct vehiculo
  11. {
  12.   TipoVehiculo tipo;
  13. };

¿Por qué? En primer lugar porque así queda claro que esa variable no admite cualquier valor... ¿qué pasa si esperas encontrar una 'G' de gasolina y en alguna parte del programa se pone una 'g'? ¿Te dedicas a comprobar las dos combinaciones? ¿Y si te equivocas y pones una 'h' que está justo al lado del teclado? Con un enumerado no tienes esos problemas... de hecho si intentas hacer "tipo=gaoslina" el compilador te marcará un error en esa línea, ayudándote en la tarea de evitar errores en el código.

Si te preocupa la entrada de datos del usuario, para eso puedes usar bien una función:

Código C++:
Ver original
  1. TipoVehiculo ConvertirATipoVehiculo(char c)
  2. {
  3.   switch( c )
  4.   {
  5.     case 'G':
  6.       return Gasolina;
  7.     default:
  8.       return Ninguno;
  9.   }
  10. }

Seguimos:
Código C++:
Ver original
  1. typedef struct Cola
  2. {
  3.   struct vehiculo *cabecera;
  4.   struct vehiculo *ultimo;    
  5. }*lacola;

Dos cosas mal que veo en este código:
  • Si la lista es enlazada simple no tiene sentido alguno que tengas un puntero al último elemento de la lista.
  • Si creas un alias de un puntero por favor que el nombre indique esa característica de alguna forma.

Entre otras cosas puedes hacer que tu programa funcione sin hacer que "lacola" sea puntero. Para eso inventaron las referencias.

Yo la declaración de esa clase la dejaría así (asumiendo que haces también el cambio referente a separar la clase vehiculo):

Código C++:
Ver original
  1. struct Cola
  2. {
  3.   nodo*cabecera;
  4.  
  5.   Cola() : nodo(0)
  6.   {}
  7.  
  8.   // o si lo prefieres:
  9.  
  10.   Cola()
  11.   { nodo = 0; }
  12. };

Por otro lado no se si has visto o conoces de alguna manera los destructores y los métodos miembros de la clase, que te serían de gran utilidad. En este tema no me enrollo más porque entonces no me entraría todo en el mensaje, pero si quieres más detalles tu pregunta.

Bueno, el caso es que hemos dicho que no hace falta usar un puntero para "lacola" ¿Cómo es eso? Más o menos así:

Código C++:
Ver original
  1. // En C++ no hace falta poner void en la lista de parámetros
  2. Cola crearCola()
  3. {
  4.   Cola cola
  5.    
  6.   // No entiendo por qué la cola ha de tener de primeras un vehículo
  7.   // Lo suyo es que inicialmente estuviese vacía
  8.   // cola.cabecera = new struct vehiculo;
  9.  
  10.   // Esta línea estaba mal. Si la lista solo tiene un elemento por qué creas dos nodos?
  11.   // co->ultimo = new struct vehiculo;
  12.  
  13.   // y para colmo de males la reserva anterior la pierdes creando lagunas de memoria
  14.   // co->ultimo = NULL;
  15.  
  16.   // como las clases ahora tienen constructores, esto sobra
  17.   // co->cabecera->sig = NULL;
  18.  
  19.   return cola;
  20. }

Es decir, esta función no es necesaria:

Código C++:
Ver original
  1. int main()
  2. {
  3.   // Y ya está, ya son colas usables
  4.   Cola gasolina, diesel, moto, industrial;
  5. }

Otro ejemplo con referencias:

Código C++:
Ver original
  1. bool colaVacia(Cola& laco)
  2. {
  3.   // Las referencias usan el punto en vez de la flecha
  4.   return (laco.ultimo == NULL);
  5.  
  6.   // Si compilas con C++11 (estándar del 2011) también puedes dejarlo así
  7.   return (laco.ultimo == nullptr);
  8. }
  9.  
  10. // Las siguientes las puedes actualizar tu

Es de agradecer no usar memoria dinámica si podemos evitarlo porque su uso es complicado y es facil meter la pata y usar un puntero que no apunta a ningún sitio o dejarnos memoria sin liberar. Como por ejemplo...

Código C++:
Ver original
  1. void volver(lacola laco)
  2. {
  3.     struct vehiculo *nodo_aux;
  4.     nodo_aux = new struct vehiculo;
  5.     nodo_aux = laco->cabecera->sig; // <<--- AQUI!!!
  6.     laco->cabecera->sig = nodo_aux->sig;
  7.     laco->ultimo->sig = nodo_aux;
  8.     nodo_aux->sig = NULL;
  9.     laco->ultimo = nodo_aux;
  10. }

Fíjate que justo en la linea anterior has creado un objeto con new... si después asignas otro valor a ese puntero has perdido la memoria reservada con new. Ese new lo puedes eliminar con mucho cariño.

Te explico: Que uses un puntero no quiere decir que SIEMPRE tengas que hacer un new. Esto se hace únicamente cuando quieres crear un objeto nuevo. Si lo que pretendes es usar el puntero para acceder a elementos ya existentes no hay que usar new:

Código C++:
Ver original
  1. int* miClase1 = new int;
  2. int miClase2 = 2;
  3.  
  4. *miClase = 1;
  5.  
  6. int* ptr = miClase; // ptr y miClase apuntan al mismo elemento
  7. std::cout << *ptr << *miClase << std::endl; // imprime 11
  8.  
  9. *ptr = 10;
  10. std::cout << *ptr << *miClase << std::endl; // imprime 1010
  11.  
  12. // Ahora el puntero apunta a miClase2
  13. ptr = &miClase2;
  14. std::cout << *ptr << miClase2 << std::endl; // imprime 22
  15.  
  16. // Por cada new tenemos que poner un delete
  17. delete miClase;

Repasemos la implementación de la función quitar.

Código C++:
Ver original
  1. void quitar(lacola laco)
  2. {
  3.   // he quitado el código porque no me entraba el mensaje
  4. }

Vale, antes hemos visto que tener un puntero al último en una lista enlazada simple es un absurdo. Aquí vas a ver uno de las razones: Tener información redundante implica tener que actualizarla correctamente so pena de tener errores. Dicho en cristiano: "Si sabes que la lista termina cuando el "sig" de un nodo es 0... ¿Por qué te complicas usando cola->ultimo?

Aparte de eso tienes variables que no usas ni necesitas para nada:

Código C++:
Ver original
  1. void quitar(Cola& laco)
  2. {
  3.   if(colaVacia(laco))
  4.      cout << "LA COLA ESTA VACIA" << endl;
  5.   else
  6.   {
  7.     nodo* temp = laco.cabecera;
  8.     laco.cabecera = temp->cabecera;
  9.     delete temp;
  10.   }
  11. }

Y ya está, ya has quitado el primer elemento de la cola. Simplemente hay que hacer que cabecera apunte al segundo elemento y después borrar el nodo que queda huérfano. El sistema va a funcionar porque sabes que SIEMPRE se va a cumplir que el último nodo de la lista tiene su sig apuntando a 0 o NULL o nullptr, como prefieras.

Código C++:
Ver original
  1. void volver(lacola laco)
  2. {
  3.     struct vehiculo *nodo_aux;
  4.     nodo_aux = new struct vehiculo;
  5.     nodo_aux = laco->cabecera->sig;
  6.     laco->cabecera->sig = nodo_aux->sig;
  7.     laco->ultimo->sig = nodo_aux;
  8.     nodo_aux->sig = NULL;
  9.     laco->ultimo = nodo_aux;
  10. }

Otro cacao descomunal. Viendo un poco tu programa deduzco que esta función cola el primer nodo al final de la lista... veamos:

Código C++:
Ver original
  1. // estado inicial: A->B->C->D
  2.  
  3. // nodo_aux = B
  4. nodo_aux = laco->cabecera->sig;
  5.  
  6. // A->C->D
  7. laco->cabecera->sig = nodo_aux->sig;
  8.  
  9. // A->C->D->B
  10. laco->ultimo->sig = nodo_aux;

¿No es lo que esperabas, verdad?

Código C++:
Ver original
  1. void volver(Cola& laco)
  2. {
  3.   nodo* nodo = laco.cabecera;
  4.   // Si está vacía no podemos tocar nada.
  5.   // Si solo tiene un elemento tampoco porque el nodo es a la vez el primero y el último
  6.   if( nodo != 0 && nodo ->sig != 0)
  7.   {
  8.     // Ahora la lista empieza en el segundo nodo
  9.     laco.cabecera=nodo->sig;
  10.  
  11.     // Navegamos hasta el final de la lista
  12.     nodo* ptr = laco.cabecera;
  13.     while(ptr->sig)
  14.       ptr = ptr->sig;
  15.  
  16.     // ptr apunta al último nodo de la lista. Colocamos detrás el que era el primer nodo y actualizamos su sig.
  17.     ptr->sig = nodo;
  18.     nodo->sig = 0;
  19. }

Y bueno, recapitulando. Tus mayores problemas están en la gestión de la cola y el uso de punteros. En eso tienes que ponerte las pilas.

También sería interesante y recomendable que empezases a usar el depurador de código. Es una herramienta imprescindible para programar. Puede costar empezar a manejarla pero en seguida verás lo útil que resulta y pronto pasará a ser imprescindible para ti.

Un saludo.
__________________
La ayuda se paga con esfuerzo o con dinero. Si no estás dispuesto a esforzarte y quieres que te hagan los deberes pide presupuesto, al menos así ahorrarás tiempo.