Ayuda con lectura de teclado en C

Foro dedicado a la programación en todo tipo de sistemas clásicos.
Avatar de Usuario
zup
Amiga 2500
Amiga 2500
Mensajes: 2967
Registrado: 04 Sep 2009, 20:07
Sistema Favorito: Spectrum 16Kb/48Kb
primer_sistema: Spectrum 16Kb/48Kb
consola_favorita: Nintendo DS/3DS
Primera consola: Nintendo GameBoy
Ubicación: Navarra
Gracias dadas: 68 veces
Gracias recibidas: 322 veces
Contactar:

Ayuda con lectura de teclado en C

Mensajepor zup » 21 Abr 2018, 10:34

Estoy intentando reaprender a programar en C y me he tropezado con un problema inesperado al leer el teclado en C, lo explico un poco.

El programa que intento hacer pretendo que sea portable entre plataformas, por lo que intento que no tenga más dependencias que la propia runtime de C. Lo que estoy intentando leer del teclado una cadena de (como mucho) 10 caracteres. Además, como no todos los caracteres son válidos, intento que el usuario solo pueda introducir letras, números y algún espacio.

Uno de los problemas es que Windows no envía los caracteres a C hasta que se pulse enter... no sé si Linux lo hará igual.

Soluciones que he intentado:
  • Usando gets() -> El mayor problema es que cualquier algunos caracteres no alfanuméricos (como el espacio, tab) actuán como separador entre cadenas... y yo quiero capturar también el espacio.
  • Un bucle usando getc() -> Como he dicho, Windows no enviará los caracteres hasta que se pulse enter, de manera que no puedo cogerlos de uno en uno.
  • Usando scanf() -> He visto por ahí que podía usar scanf("%10[0-9a-zA-Z ]",cadena), pero el problema es que C trata como separador de cadenas cualquier carácter que no esté en el filtro (por ejemplo, un $) y yo trato de ignorarlos.

La solución más obvia sería hacer un bucle que leyera una pulsación cada vez. Si está en los caracteres admitidos, lo muestro en pantalla y lo añado a mi cadena. Así hasta que se pulse enter.

El problema de esta solución es que no encuentro la función que me lea pulsación a pulsación. Bueno, sí, están por ahí flotando getch() y getche(), pero pertenecen a conio.h (biblioteca de Borland, solo presente en Windows) o curses.h (portable) pero yo quería que mi programa no tuviera dependencias. Una opción sería compilar estáticamente curses.h (porque no suele estar instalada en Windows), pero no sé bien cómo hacerlo ni el impacto que tendría en el tamaño de mi ejecutable.

Otra opción sería hacer un bucle que lea cadenas y las concatene hasta que no haya nada pendiente de leer en el teclado, pero no sé cómo hacerlo.

¿Alguna idea más?
I have traveled across the universe and through the years to find Her. Sometimes going all the way is just a start.
Además vendo cosas!

BlackHole
Amiga 1200
Amiga 1200
Mensajes: 1442
Registrado: 07 Nov 2009, 11:38
Sistema Favorito: C64
primer_sistema: Spectrum 16Kb/48Kb
consola_favorita: Nintendo SNES
Primera consola: Nintendo SNES
Ubicación: Madrid
Gracias dadas: 9 veces
Gracias recibidas: 209 veces

Re: Ayuda con lectura de teclado en C

Mensajepor BlackHole » 21 Abr 2018, 12:29

Bueno, cuando compilas estáticamente una biblioteca, no se te pega al ejecutable toda la biblioteca cuando la enlazas (link), sino solamente las rutinas usadas, con lo que el tamaño no debería irse de madre. De todas formas, gets es una función insegura tal como lo indica el compilador (warning: this program uses gets(), which is unsafe). El problema es que no puedes controlar el número de caracteres que introduce el usuario pudiendo ocurrir que se copien en la cadena más caracteres que los permitidos por su tamaño máximo, y habría que usar fgets con stdin como entrada y definir antes el buffer con una matriz fija.

Pero tal vez la función que estabas buscando es getchar: http://www.cplusplus.com/reference/cstdio/getchar/

Si aún así prefieres, por las características de tu código, usar un buffer de teclas que puedes querer evaluar a posteriori, la forma correcta es usar scanf (o fscanf) con un conjunto negado de delimitadores con [^]. Aunque tu problema es un recurrente en la programación, y muchos aconsejan usar sscanf y acompañarlo de varias rutinas que chequeen las entradas.

Revisa https://stackoverflow.com/questions/12885628/changing-the-scanf-delimiter. Se puede abordar el tema de diversas formas, pero creo que las tres que elegiste inicialmente son las usadas por principiantes y justamente las que no se deben utilizar.

Avatar de Usuario
zup
Amiga 2500
Amiga 2500
Mensajes: 2967
Registrado: 04 Sep 2009, 20:07
Sistema Favorito: Spectrum 16Kb/48Kb
primer_sistema: Spectrum 16Kb/48Kb
consola_favorita: Nintendo DS/3DS
Primera consola: Nintendo GameBoy
Ubicación: Navarra
Gracias dadas: 68 veces
Gracias recibidas: 322 veces
Contactar:

Re: Ayuda con lectura de teclado en C

Mensajepor zup » 21 Abr 2018, 22:43

BlackHole escribió:...


Vaaale... creo que ya tengo DOS soluciones a mi problema.

Mi primera intención nunca ha sido usar gets (entiendo perfectamente lo divertidos que son los desbordamientos de búfer), pero como no encontraba la solución he ido repasando todo lo que se me ocurría incluyendo las barbaridades. Por otra parte, getchar() equivale a getc(stdin), por lo que (en principio) no es muy diferente.

Las soluciones:
- Me ha gustado la de scanf con los delimitadores negados. En principio voy a utilizar algo del estilo de error=scanf("%30[^\n]",cadena); y luego un bucle con getchar para librarme de los caracteres que queden en el buffer hasta el enter. Después de eso, usaré un bucle para ir copiando a una cadena de hasta 10 caracteres los que sean letras, números y espacios.
- O la otra opción es usar un bucle de getchars e ir añadiendo a mi cadena solo los que me cuadren. Esa era una solución que había pensado antes, pero me despistaba el hecho de que el sistema operativo no me pase todos los caracteres que se escriben (estaba pensando en algo estilo INKEY$, donde se detectan también los backspaces). Bueno, como parece que el sistema también los procesa a lo mejor no es tanto problema.

A ver si mañana hago una prueba más (la segunda opción parece más limpia, pero hasta que no la haga funcionar no lo tendré claro) y lo meto al programa.

Vale, la rutina definitiva es esta:

Código: Seleccionar todo

int leer_nombre (char *nombre_tap)
{
    char c;
    int i;

    i=0;
    do
    {
        c=getchar();
        if (c==' ' || (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9'))
        {
            nombre_tap[i]=c;
            i++;
        }
    } while (c!='\n' && i<10);
    nombre_tap[i]=0;

    // Elimino el resto de caracteres hasta enter
    if (c!='\n')
    {
        do
            c=getchar();
        while (c!='\n');
    }
    return i;
}


En principio hace lo que quiero... ya que el sistema operativo va a procesar tonterías como los backspaces y demás. No van a pasar por el filtro todos los caracteres que son comunes, pero imagino que con los que hay son suficientes.
I have traveled across the universe and through the years to find Her. Sometimes going all the way is just a start.
Además vendo cosas!


Volver a “Programación”

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 7 invitados