Własna implementacja funkcji getch()

ceer

<justify>Słowem wstępu dla niewtajemniczonych: funkcja <font color="gray">getch()</span> 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 <font color="gray">getch()</span> 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 <font color="gray">getch()</span>, 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> ```c #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


<h2>Sposób 2.</h2>
<justify>    Poniższa interpretacja to rozwinięcie poprzedniej. Nie korzysta ona z funkcji [[C/Biblioteka_standardowa/stdio.h/getchar]], zamiast tego używając funkcji <font color=gray><b>kbhit()</b></span>, 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ę <font color=gray><b>openKeyboard()</b></span> na początku i <font color=gray><b>closeKeyboard()</b></span> na końcu.</justify>
```c
#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ż:

FAQ

0 komentarzy