validacion de datos c++

Iniciado por alejhgpm, 16 Junio 2017, 20:36 PM

0 Miembros y 2 Visitantes están viendo este tema.

alejhgpm

Hola amigos del el haker. Tengo una duda quizas ustedes tienen la respuesta. Primero debo decir que soy nuevo en esto de la programacion y soy autodidacta llevo aproximadamente 4 meses en esto. De esta manera , en cierta forma tengo limitaciones a la hora de hacer codigos.  Quiero realizar una validacion de datos pero de una forma muy particular. En un programa de consola de windows, quiero que se ingresen datos de tipo numerico, pero que si el usuario desea terminar de introducir numeros, pueda ingresar por ejemplo  'g' para guardar y salir. El asunto es que claramente no se puede ingresar caracteres a una variable de tipo float por ejemplo. Puedo realizar la accion de verificar el estado del objeto cin con cin.fail() y limpiar el buffer para luego mostrar un mensaje que diga que se debe ingresar un numero, pero no se si se pueda, una vez verificado el estado del stream, recuperar los datos ingresados y asignarlos a un tipo char por ejemplo para que se cumpla la condicion de poder guardar o salir. La solucion primera fue que se ingresara un digito para salir y guardar pero como dije me gustaria hacerlo de la forma ingresa un char para salir.De ante mano muchas gracias

Serapis

Cuando haces un trabajo y tiras para adelante... lo haces de unammanera concreta...
Luego a veces te vienen con cambio de especificaciones, y quieren cosas que antes no venían...

Bueno, entonces toca remodelar de nuevo toda esa área, despreciar esa parte y no tratar de aprovecharla al 100%, porque será un engorro el código necesario para soportar esos cambios. Mucho mejor rehazlo ahora pensando en que si antes solo tenías que soportar el objeto A, ahora lo rediseñas teniendo presente el objeto A y el objeto B.

Que sean distintos, implica que debes pensar en una solución común a ambos casos.

en general usa una función donde pases diferentes parámetros y al menos tenga uno de retorno, para verificar diferentes casos que pudieren darse.

Si antes tenías algo equivalente a esto 8da igual como implementes el código):
Entero = Funcion HacerUnaCosa( TipoX A )
 Hacer lo que sea con A
  Devolver 1 // por ejemplo...
fin funcion


Ahora podrías tener esto otro (también da igual como implementes tu código):
Entero = Funcion HacerUnaCosaYverificarOtra( TipoX A, TipoY B)
 Hacer una cosa con A

 //Verificar TipoY
      Si B = "Q" luego
         Devolver 1
      Si B = "T"
         Devolver 2
      Si ninguno de los casos previos
         Devolver 0
      Fin si
Fin funcion  


Has hecho lo mismo que antes con el objeto A (la materia en cuestión que hacías antes, sea cual sea), ahora puedes chequear además lo que sea para el objeto B, aunque sea de otro tipo.
Y al final, devuelves un entero, es decir puedes unificar un criterio, incluso por cuestiones distintas.
Si el criterio fuera inaceptable para ser unificado, entonces el valor para el objeto B, lo podrías devolver por referencia, dejando libre el retorno de la función para el objeto A... Y al retorno, debes verificar además del resultado del objeto A, el retorno por referencia del objeto B (antes o después del A, eso debe saberlo quien lo programa), o descartarlo, según proceda.

MAFUS

Captura la entrada como una cadena y después la procesas:
Si el primer caracter es una g guardas y sales. Sino es tu número. Si no es ni la g ni son números la entrada no es válida.

alejhgpm

#3
Gracias amigos por responder. No lo había hecho antes por que me puse a pensar en sus respuestas antes de hacerlo.
Cita de: NEBIRE en 16 Junio 2017, 21:53 PM
Cuando haces un trabajo y tiras para adelante... lo haces de unammanera concreta...
Luego a veces te vienen con cambio de especificaciones, y quieren cosas que antes no venían...

Bueno, entonces toca remodelar de nuevo toda esa área, despreciar esa parte y no tratar de aprovecharla al 100%, porque será un engorro el código necesario para soportar esos cambios. Mucho mejor rehazlo ahora pensando en que si antes solo tenías que soportar el objeto A, ahora lo rediseñas teniendo presente el objeto A y el objeto B.


Me van a creer que ya lo había pensado de esa manera!!  Tenia considerado la entrada por teclado de dos variables para hacer lo que me dices, pero me propuse la meta de ingresar solo una variable y con eso hacer lo que sea. Obsesiones mías.
El caso lo plantie de una forma parecida a esta.

mi_pila pila;
float num;
while(true){ cout<<"ingrese un numero o 100  para salir";cin>>num;
               if(num==100) break;
               else pila.agregar(num);
}

Un bucle donde se vayan ingresando datos en un pila hasta que el usuario decida terminar, para este caso 100.

Quise ir mas lejos y hacer que no fuera un valor numérico el que terminara el bucle, sino que fuera un carácter.

El problema de eso esta en esta linea:

float num; cout<<"ingrese un numero o 100  para salir";cin>>num;

No se puede agregar un caracter a una variable numerica!! Asi de simple.
Por lo que tuve que buscar otro camino.

Cita de: MAFUS en 16 Junio 2017, 23:59 PM
Captura la entrada como una cadena y después la procesas:
Si el primer caracter es una g guardas y sales. Sino es tu número. Si no es ni la g ni son números la entrada no es válida.

Efectivamente entendí que ese era el camino. Pero no es tan simple para el alcance de mis conocimientos. Me explico
pd: De todas formas es necesario agregar un variable de tipo char o string.

mi_pila pila;
float num;
char letra;
while(true){ cout<<"ingrese un numero o 's'  para salir";cin>>letra;
               if(letra=='s') break;
               else  // ¿ num=(float)letra;  ? // no sirve
}

Un cast o  el uso de un puntero void*(divagación mía), no se acercan ni un poco a la solución real.

Me inclino a pensar que la solución, parte por saber si es posible recuperar las entradas por teclado en caso de que alguna bandera de error del cin se active.

algo asi:

float num;
char letra;
mi_pila pila;

while(true){cout<<"Ingrese un numero o 's' para salir ";cin>>num;
if(cin.fail()){// no es necesario volver a introducir nada
                 //recuperar la entrada y devolverla a buffer ;
                cin>>letra;
                if(letra=='s'){break;}
                else{cout<<"Debe ingresar un numero ";cin.ignore(256,'\n');}
              }
else{pila.agregar(num);}
}

Ahora en concreto, mi pregunta es ¿Sera posible recupera una entrada fallida y devolverla a la cola del bufferr o solo se pierde y se sigue con la siguiente elemento del stream?

De nuevo gracias por el aporte y el interes en ayudar a quienes somos neófitos en esto.
           

alejhgpm

Amigos de el hacker. con alegría les informo que encontré la solución. Una solución a media, pero solución al fin y al cabo.

No estaba mal en la dirección que iba solo tenia que cambiar el enfoque.

La duda de si se puede recuperar una entrada por teclado fallida, aun no la resuelvo. Pero en vez de preguntarme si se podía recuperar una entrada fallida, cambie el enfoque y me pregunte si es que se podía devolver al buffer una entrada valida. Asi que me enfoque en el proceso que hay detrás de un stream.

Me encontré que existe una clase streambuf para la manipulación del buffer, la cual es también  base para la clase ios.

Siendo sincero, es poco lo que pude rescatar de de esta clase debido a que opera a bajo nivel, para lo cual no estoy preparado. A demás, no todos los compiladores llevan consigo las funciones de esta clase, por lo que una posible solución a mi problema (o el de cualquiera) usando los métodos de esta clase, es poco viable.

Aun así. encontré que esta clase tiene la función  int sputbackc(char); que devuelve un caracter al buffer de entrada interno, lo que era compatible con mi cambio de enfoque.
La verdad ni siquiera supe como instanciarla  :-(

De esta forma seguí mas arriba en la clase ios, luego a la clase istream y llegue a notar que la solución estaba en un método del propio objeto cin.

El objeto cin tiene un método con el mismo nombre istream& putback(char); que también devuelve un caracter a la entrada de buffer interno. Yo supongo que en realidad hace uso del método int sputbackc(char) de la clase streambuf (es solo una suposicion).

Asi que la probe:

char letra;
cin.sputback('r');
cin>>letra;
cout<<"letra vale: "<<letra;

y que creen!! La salida fue

letra vale: r

Esa era!!

Ahora puedo reintegrar una entrada valida (el valor de una variable) al flujo de entrada.

Entre ensayo y error, los cuales van dejando conclusiones sin base teórica, mi código quedo así.

  mi_pila pila;
  float num;
  char letra;

while(true){
 
  cout<<"Ingrese un numero o 's' para salir ";cin>>letra;
 
  if(letra=='s'){ cin.ignore(256,'\n');break;}

  else{ cin.sputback(letra);

          cin>>num;

          cin.clear();

          cin.ignore(256,'\n');

          //ahora viene el detalle y porque es solo una solución a medias

          if(num==0){cout<<" Ingreso invalido para salir o agregar num"<<endl;}

          else{cout<<"Agregamos a la pila"<<endl;  pila.agregar(num);}
};


Una breve explicación:

Para que esto resulte, es necesario pensar que toda entrada debe ser siempre caracteres ya sea string o char.
Probé con una variable tipo string y realmente se hace demasiado engorroso utilizar el método sputback(). No lo recomiendo!! Así que me quede con con un tipo char considerando que solo necesito un carácter para determinar que se hace dentro el bucle.

//Ingresamos un caracter o una cadena da igual. Ya que se someterá a evaluación solo el primer caracter ingresado. Si el usuario (tengo un hijo de 4 años) digita el teclado por error y la primera entrada fue una 's' lo peor que sucederá sera es que saldrá del bucle.

cout<<"Ingrese un numero o 's' para salir ";cin>>letra;


Como siempre la entrada que se almacena en una variable de tipo char sera valida (pueden haber excepciones pero las desconozco), evaluamos la variable.

if(letra=='s'){ cin.ignore(256,'\n');break;}

La linea anterior de código nos dice que si la variable letra contiene un valor igual a 's' ,o mejor dicho, letra es igual a 's', vacíe el buffer de entrada (en caso de que se haya ingresado mas de un caracter por error) y salga del bucle.

Pero sino, devolvemos el contenido de la variable letra al buffer de entrada de esta forma.

cin.sputback(letra);

pd: Como resulta, tiendo a pensar que el metodo sputback(char) devuelve al buffer de entrada un caracter al inicio de la cola.

Como ahora como el primer valor ingresado esta devuelta en el buffer (que puede considerarse como numero, letra o cualquier tipo de valor), si agregamos la linea

cin>>num;

la variable num tomara este valor automáticamente(sin volver a ingresar por teclado) como de tipo numérico, si es que lo es. Si no lo es, por alguna razón que aun desconozco, el valor de num sera 0 (cero) pero se activara una bandera de error por lo que es necesario agregar la linea

cin.clear();

para eliminar el estado de error. Esto es necesario ya que si no se hace, este estado no permitirá que se efectué una posterior asignacion de la entrada a num y el valor agregado a la cola del buffer quedara ahi en un bucle eterno.

luego limpiamos el buffer (de nuevo por si hay una cadena que se ingreso por error)
con la linea

cin.ignore(256,'\n');

que basicamente dice que se ignore toda entrada almacenada en el buffer hasta 256 caracteres o hasta encontrar un enter.

Y es en la linea que viene el detalle y es la razón de porque dije al comienzo que era una solución a medias.

¿Recuerdan que dije que por algún motivo si la entrada no era un valor numérico la variable num tomaba el valor de cero?

bueno, este valor de 0 tendrá que ser el criterio para indicar al usuario que la entrada no es valida, por lo que si alguien de ustedes quiere que su variable tome el valor de cero, realmente no sabrá si fue ingresada a propósito o fue un error de digitación. en mi caso no importa ya que los valores que si ingresen serán mayores a cero.

if(num==0){cout<<"No es un una entrada valida para salir o para ser asignado"<<endl;}

y si no es cero agregamos o asignamos, lo que se quiera hacer, en mi caso agrego a la pila.

else{cout<<"Ingreso valido como numero distinto de cero"<<endl; pila.agregar(num);

nuevamente agrego el codigo para que no esten subiendo a mirarlo  :D


mi_pila pila;
  float num;
  char letra;

while(true){
 
  cout<<"Ingrese un numero o 's' para salir ";cin>>letra;
 
  if(letra=='s'){ cin.ignore(256,'\n');break;}

  else{ cin.sputback(letra);

          cin>>num;

          cin.clear();

          cin.ignore(256,'\n');

          //ahora viene el detalle y porque es solo una solución a medias

          if(num==0){cout<<" Ingreso invalido para salir o agregar num"<<endl;}

          else{cout<<"Agregamos a la pila"<<endl;  pila.agregar(num);}
};


Esto dio resultado para mi y es portable en la medida que la clase iostream siga siendo parte del estándar de c++

Bueno. Espero que también les sirva y si hay alguien que pueda mejorar esto se lo agradecería bastante.

Muchas gracias a quienes participaron. Fueron realmente de ayuda.



Serapis

#5
No es habitual tener que retener entradas fallidas. si son fallos se aporta info y se devuelve un error y listo. Si el sistema es desatendido y no va a estar presente nadie  (o no conviene dejar al programa en espera por una interacción con el usuario), simplemente se escribe un log y listo.

Indistintamente de que una clase provea o no una cola y un manejo profundo de la cola, nada le impide a uno, generarlo si lo precisa y en ese caso basta que el sistema provea exclusivamente la entrada actual (quiero decir que con que solo provea eso nos es suficiente, y ya aportaríamos nosotros el resto).

El siguiente pseudocódigo procesa entradas (de una forma muy simple) mientras el buffer del sistema nos provea de ellos. aunque se supone que lo correcto es que haya una suscripción a un evento del sistema que avisa automáticamente cuando esto sucede.


Char c = InputStreamSystem  // quedan alguna entrada pendeinte en el buffer del sistema?
Hacer  mientras c distinto de nulo
   Entero i = ProcesarChar(c)  // procesar la entrada, errores se devuelven con un valor negativo.
   Si (i < 0) entonces     // si hubo un error...
      Añadir c a las ListaError  // se guarda en una lista de errores (para qué?... en otra parte deberá procesarse esta lista para que no crezca hasta el infinito.
   En otro caso
      Añadir c a la ListaExito   // se desea guardar también los errores?
   Fin si
    Char c = InputStreamSystem  // quedan alguna entrada pendeinte en el buffer del sistema?
Repetir


Da igual si usas una lista enlazada o una pila... básicamente una pila es una estructura ligera pensada para contener pocos ítems e introducirlos y rescatarlos en un orden específico. Ese orden también puede manenerse con una lista enlazada, aunque no tan evidente como la pila, sin embargo una lista enlazada está pensada para mantener una cantidad indefinida de elementos, una pila en general requiere especificar cuantos elementos ha de tener...

La cuestión, es que si mantienes aparte un buffer de fallos, la pregunta es para que lo quieres, es realmente necesario????.  Y más aún, es la forma correcta retener sólo los fallidos y no todos desde el primer fallo?, etc... (etc = surgen muchas más preguntas, que no es preciso enumerar y que hay que responder). Un breve comentario, para hacerte reflexionar...

Imagina un usuario que abre un vídeo, este está ultracomprimido y tarda en cargarse, mientras el usuario, pulsa 'play' 'avance hasta x posición', luego 'pausa', luego 'volumen +', luego 'fullscreen', luego como todavía no responde, pulsa 'stop'... Puede optarse por guardar todas esas acciones y procesarlas en ese mismo orden cuando se haya completado la carga del vídeo... pero ahora imagina el mismo caso con un menú... uno lo despliega y pulsa 'play', no responde, lo despliega nuevamente y pulsa 'play' , luego 'pausa', luego otra vez 'play'...etc... en este caso puede no interesar procesar cada entrada, ni mucho menos guardar en un buffer todos los fallos...

esto es, si uno considera que se deben procesar, o se procesan todos en el orden en que se producen o tras un fallo, hay que investigar que produce el fallo y en tal caso decidir, si abortar todo o bien guardar todos desde el fallo 8incluído la entrada con fallo) y esperar (si procede), para luego volver a intentar procesarlo todo desde ese punto.

La cuestión es que 'play', 'pausa', 'stop':
- A - Solo deben ser procesables si hay un vídeo cargado.
- B - Guardar al búffer si hay un vídeo cargándose.
- C - E ignorarlas por completo si no hay un vídeo cargado....
Incluso podría simplificarse para incluir B en el caso C.
Y por su parte cuando se carga un vídeo (al completarse la carga), debe verificar si hay un buffer de entradas sin procesar, y en consecuencia lanzar un evento para comunicar que ya está disponible para las entradas 'play', 'pausa' 'stop'...
en cambio fíjate que la entrada 'volumen +' y 'volumen -', se pueden ejecutar independientemente de si el vídeo está listo o no... y aunque sean entradas en medio de las otras. Al caso se provee otro parámetro asociado a cada entrada, que indica si  es prioritario su ejecución es ignorable, o encolable...

Si las entradas de esas acciones fueran del modo 'P' para pausa, 'Y' para 'play', 'S' para 'Stop', 'V' para 'volumen +' y 'v' para 'Volumen -', y 'L' para 'Cargar vídeo' y 'D' para 'Descargar vídeo' tu debes mantener si 'L' ha sido procesado ya o no, 'D', deshabilita 'L'. Tras un fallo debes hacer un segundo procesado en base a su 'priorización', y en este caso ver el estado de 'L'...

En fin, uno debe saber que entradas pueden ser procesadas y cuales no... pero espero que esto te haga reflexionar para encontrar la solución adecuada que necesites.

alejhgpm

Cita de: NEBIRE en 18 Junio 2017, 15:15 PM


Imagina un usuario que abre un vídeo, este está ultracomprimido y tarda en cargarse, mientras el usuario, pulsa 'play' 'avance hasta x posición', luego 'pausa', luego 'volumen +', luego 'fullscreen', luego como todavía no responde, pulsa 'stop'... Puede optarse por guardar todas esas acciones y procesarlas en ese mismo orden cuando se haya completado la carga del vídeo... pero ahora imagina el mismo caso con un menú... uno lo despliega y pulsa 'play', no responde, lo despliega nuevamente y pulsa 'play' , luego 'pausa', luego otra vez 'play'...etc... en este caso puede no interesar procesar cada entrada, ni mucho menos guardar en un buffer todos los fallos...

esto es, si uno considera que se deben procesar, o se procesan todos en el orden en que se producen o tras un fallo, hay que investigar que produce el fallo y en tal caso decidir, si abortar todo o bien guardar todos desde el fallo 8incluído la entrada con fallo) y esperar (si procede), para luego volver a intentar procesarlo todo desde ese punto.




Muchas gracias por el aporte que me diste. Realmente no había dimensionado el alcance de lo que estaba yo mismo planteando. Lo que quería hacer en realidad, era un programa de consola para que datos numéricos fueran guardados y posteriormente se hicieran operaciones matemáticas con ellos(algo básico. Solo llevo 4 meses aprendiendo). Al desconocer el numero de datos que se ingresarían y por las características propias de acceso a la información, decidí que la mejor manera de hacerlo era a través de un pila dinámica. Nunca imagine esto como una forma de ir guardando entradas para ser agregadas, por ejemplo, a una lista de eventos implementada para algún programa de reproducción de vídeo u otro programa, que desde mi posición en la curva de aprendizaje, son mas complejos.

Efectivamente, no me interesaba recuperar una entrada fallida como un fallo (valga la redundancia) para su posterior procesamiento. Sino que mi intención era recuperar dicha entrada para que fuese asignada a un tipo de variable distinta . No quería que ocurriera esto:

Desea ingresar otro numero? (s/n) s
Ingrese numero 56.3
Desea ingresar otro numero? (s/n) s
Ingrese numero _

Esto implica que el usuario debe hacer dos entradas y yo quería que solo se hiciera una, a la manera de:

Ingrese numero o 's' para salir _

Esta entrada la recogería una variable de tipo float y si era una entrada fallida, estaba el presupuesto de que esta entrada traería consigo la letra 's'. Es por eso que me vi en la necesidad de recuperar la entrada fallida para asignarla automáticamente a una variable de tipo char para su evaluación.

Como me di cuenta de que no era necesario que la entrada fuera fallida, primero decidí evaluar si la entrada contenía la letra 's'(tipo char) para salir y sino, al no ser 's', se presupone que la entrada es un valor numérico. Por lo tanto, también se hace necesario devolver el caracter al buffer del sistema para que sea asignada automáticamente a una variable de tipo correcto, en mi caso float.
Así podía lograr mi cometido, ya que no encontré otra forma de hacerlo.

Ahora, gracias a ti, comienzo a comprender de que esto mismo, puede ser aplicado a diferentes programas mas complejos, pero que podría resultar de mejor manera en la forma en que lo indicas. Eso lo veré mas adelante, ya que hiciste que  me propusiera nuevas metas.

Lo extraño, es que me parece que comienzo nuevamente de cero. No se por donde partir.

Serapis

Para casos como el que comnentas, lo más sencillo es asignar un valor numérico que denote que el usuario a cancelado. en tu caso podría ser un valor 0.
Es decir:

Ingrese otro número (0 para finalizar): 56'3

Entonces tu detectas si se entró "0" u otro valor... considera también como 0, un valor no procesable como número, por ejemplo:

Ingrese otro número (0 para finalizar): hola


MAFUS

#8
Un ejemplo de lo que te había dicho: controlar la entrada y actuar según lo que te hayan escrito.
#include <stdio.h>
#include <string.h>
#include <ctype.h>

int main() {
   char entrada[255];
   double numero;
   while(1) {
       printf("> ");
       fgets(entrada, 255, stdin);
       char *c = strchr(entrada, '\n');
       if(c)
           *c = '\0';
       entrada[0] = toupper(entrada[0]);
       if(strcmp(entrada, "S") == 0)
           break;
       if(sscanf(entrada, "%lf", &numero) == 1) {
           // Exito con adquirir el número, lo proceso
           printf("%f\n", numero);
       }
       else
           puts("Error en la entrada: Debe ser un numero real o 's' salir.");
   }
   puts("FIN DEL PROGRAMA");
}