Programowanie w języku C/C++ » FAQ

Własna implementacja funkcji getch()

  • 0 komentarzy
  • 8910 odsłon
  • Oceń ten tekst jako pierwszy
<justify>Słowem wstępu dla niewtajemniczonych: funkcja getch() pobiera pojedynczy znak ze standardowego wejścia, jednak w przeciwieństwie do funkcji getchar nie buforuje go (tj. nie wyświetla go na ekranie, nie czeka na powrót karetki (kliknięcie klawisza RETURN/ENTER)), a co za tym idzie, od razu zwraca wartość numeryczną wciśniętego znaku. Nie da się ukryć, że funkcja ma dość szerokie zastosowanie, chociaż początkowi programiści używają jej głównie do zatrzymania wykonywania programu, tak aby kończyło je dopiero kliknięcie dowolnego klawisza.</justify><justify>    Okazuje się jednak, że funkcja ta, chociaż niezwykle przydatna, nie wchodzi w skład biblioteki standardowej (jest składnikiem nieprzenośnej biblioteki <conio.h>), co spędza sen z oczu każdemu, kto postanowił spróbować swych sił w programowaniu w C/C++ chociażby pod Linuksem.</justify><justify>    No dobrze, ale w końcu nie ma rzeczy niezastąpionych, w związku z czym w niedługim czasie powstało wiele wszelakich implementacji funkcji getch() dla pozostałych systemów. Można je znaleźć m.in w conio2.h, curses.h, ncurses.h, pdcurses.h, czy wreszcie w graphics.h. Okazuje się jednak, że możliwe jest stworzenie dość prostej (żeby nie powiedzieć prymitywnej) implementacji funkcji getch(), używając wyłącznie biblioteki standardowej. Okazuje się to nie takie trudne.</justify>

Sposób 1.


<justify>    Poniższy przykład zdaje się być idealny. Nie dość, że korzysta wyłącznie z biblioteki standardowej, to jeszcze jest wyjątkowo przenośny (działa na niemal wszystkich nie-windowsowych systemach). Poprawnie zbiera znaki alfanumeryczne i klawisze funkcyjne. Problem pojawia się jedynie, jeśli chodzi o strzałki oraz [backspace] (funkcja ich nie rozróżnia, wciśnięcie dowolnej strzałki da nam na wyjściu tą samą wartość).</justify><justify>    Działanie funkcji jest banalne. Polega na zmianie ustawień terminala tak, aby nie wyświetlał, ani nie buforował znaków. Następnie pobiera znak i przywraca poprzednie ustawienia terminala.</justify>
#include <stdio.h>
#include <unistd.h>
#ifdef WIN32
#include <conio.h>
#else
#include <termios.h>
 
int getch (void)
{
        int key;
        struct termios oldSettings, newSettings;    /* stuktury z ustawieniami terminala */
 
        tcgetattr(STDIN_FILENO, &oldSettings);    /* pobranie ustawień terminala */
        newSettings = oldSettings;
        newSettings.c_lflag &= ~(ICANON | ECHO);    /* ustawienie odpowiednich flag */
        tcsetattr(STDIN_FILENO, TCSANOW, &newSettings);    /* zastosowanie ustawień */
        key = getchar();    /* pobranie znaku ze standardowego wejścia */
        tcsetattr(STDIN_FILENO, TCSANOW, &oldSettings);    /* przywrócenie poprzednich ustawień terminala */
        return key;
}
#endif


Sposób 2.


<justify>    Poniższa interpretacja to rozwinięcie poprzedniej. Nie korzysta ona z funkcji getchar, zamiast tego używając funkcji kbhit(), która zwraca prawdę, gdy kliknięty zostanie dowolny klawisz. Dzięki temu bez problemu można pobrać wartość numeryczną każdego klawisza. Minusem tego rozwiązania jest to, że trzeba inicjować jej użycie poprzez funkcję openKeyboard() na początku i closeKeyboard() na końcu.</justify>
#include<stdio.h>
#include<stdlib.h>
#include<termios.h>
#include<unistd.h>
 
        static struct termios oldSettings, newSettings;        /* stuktury z ustawieniami terminala */
        static int chartest = -1;
 
        void openKeyboard (void);
        void closeKeyboard (void);
        int kbhit (void);
        int getch (void);
 
int main (int argc, char* argv[])
{
        int znak = 0;
 
        openKeyboard();
        while ( znak != 'q' )
        {
                printf("petla\n");
                sleep(1);
                if ( kbhit() )
                {
 
                        znak = getch();
                        printf("wcisnales %c\n", znak);
                }
        }
        closeKeyboard();
        return 0;
}
 
        /* ===== otwiera klawiaturę (zmienia ustawienia terminala) ===== */
void openKeyboard (void)
{
        /* pobranie ustawień terminala */
        tcgetattr(0, &oldSettings);
        newSettings = oldSettings;
        /* ustawienie odpowiednich flag */
        newSettings.c_lflag &= ~ICANON;
        newSettings.c_lflag &= ~ECHO;
        newSettings.c_lflag &= ~ISIG;
        newSettings.c_cc[VMIN] = 1;
        newSettings.c_cc[VTIME] = 0;
        /* zastosowanie zmian */
        tcsetattr(0, TCSANOW, &newSettings);
        return;
}
 
        /* ===== zwraca 1 w przypadku kliknięcia dowolnego klawisza ===== */
int kbhit (void)
{
        char key;
        int nread;
 
        if ( chartest != -1 )
                return 1;
        newSettings.c_cc[VMIN] = 0;
        tcsetattr(0, TCSANOW, &newSettings);
        nread = read(0, &key, 1);
        newSettings.c_cc[VMIN] = 1;
        tcsetattr(0, TCSANOW, &newSettings);
        if ( nread == 1 )
        {
                chartest = key;
                return 1;
        }
        return 0;
}
 
        /* ===== zamyka klawiaturę (przywraca poprzednie ustawienia terminala) ===== */
void closeKeyboard (void)
{
        /* przywrócenie poprzednich ustawień terminala */
        tcsetattr(0, TCSANOW, &oldSettings);
        return;
}
 
#ifndef __CONIO_H__
int getch (void)
{
        char key;
        if ( chartest != -1 )
        {
                key = chartest;
                chartest = -1;
                return key;
        }
        read(0, &key, 1);
        return key;
}
#endif

<justify>Reasumując, przykładów implementacji tej banalnej, ale jakże przydatnej funkcji jest wiele. To od użytkownika zależy, czy wybierze niestandardową bibliotekę, którą będzie musiał dołączać do projektu, gdy będzie chciał podzielić się źródłem, czy wykorzysta własną implementację, dzięki czemu program zyska na przenośności.</justify>
Zobacz też: