Capturez des caractères à partir de l'entrée standard sans attendre que l'entrée soit pressée

174

Je ne me souviens jamais comment je fais cela parce que cela me revient si rarement. Mais en C ou C ++, quelle est la meilleure façon de lire un caractère à partir d'une entrée standard sans attendre une nouvelle ligne (appuyez sur Entrée).

Idéalement également, il ne ferait pas écho au caractère d'entrée à l'écran. Je veux juste capturer des frappes sans affecter l'écran de la console.

Adam
la source
1
@adam - Pouvez-vous clarifier: Voulez-vous une fonction qui reviendra immédiatement si aucun caractère n'est disponible, ou qui attendra toujours une seule frappe?
Roddy
2
@Roddy - Je veux une fonction qui attendra toujours une seule touche.
Adam

Réponses:

99

Ce n'est pas possible de manière portable en C ++ pur, car cela dépend trop du terminal utilisé qui peut être connecté stdin(ils sont généralement mis en tampon en ligne). Vous pouvez cependant utiliser une bibliothèque pour cela:

  1. conio disponible avec les compilateurs Windows. Utilisez la _getch()fonction pour vous donner un caractère sans attendre la touche Entrée. Je ne suis pas un développeur Windows fréquent, mais j'ai vu mes camarades de classe simplement l'inclure <conio.h>et l'utiliser. Voir conio.hsur Wikipedia. Il répertorie getch(), qui est déclaré obsolète dans Visual C ++.

  2. curses disponibles pour Linux. Des implémentations de curses compatibles sont également disponibles pour Windows. Il a également une getch()fonction. (essayez man getchde voir sa page de manuel). Voir les malédictions sur Wikipedia.

Je vous recommanderais d'utiliser des malédictions si vous visez une compatibilité multiplateforme. Cela dit, je suis sûr qu'il existe des fonctions que vous pouvez utiliser pour désactiver la mise en mémoire tampon de la ligne (je crois que cela s'appelle "mode brut", par opposition au "mode cuisiné" - regardez dans man stty). Les malédictions géreraient cela pour vous de manière portable, si je ne me trompe pas.

Johannes Schaub - litb
la source
7
Notez que de nos jours, ncursesest la variante recommandée de curses.
Fund Monica's Lawsuit
4
si vous avez besoin d'une bibliothèque, comment l'ont-ils fabriquée? pour créer la bibliothèque, vous avez sûrement dû la programmer et cela signifie que n'importe qui pouvait la programmer, alors pourquoi ne pouvons-nous pas simplement programmer le code de bibliothèque dont nous avons besoin? cela rendrait également le Ce qui n'est peut-être pas portable en pur c ++ faux
OverloadedCore
@ kid8 je ne comprends pas
Johannes Schaub - litb
1
@ JohannesSchaub-litb il essaie probablement de dire comment l'implémenteur de la bibliothèque a créé cette méthode portable en pur c / c ++ quand vous dites que ce n'est pas possible.
Abhinav Gauniyal
@AbhinavGauniyal ah, je vois. Cependant, je n'ai pas dit que l'implémenteur de la bibliothèque n'a pas fait de méthodes portables (c'est-à-dire à utiliser par des programmes raisonnablement portables). J'ai laissé entendre qu'ils n'avaient pas utilisé de C ++ portable. Comme il est clair qu'ils ne l'ont pas fait, je ne comprends pas pourquoi son commentaire dit que cette partie de ma réponse est fausse.
Johannes Schaub - litb
81

Sur Linux (et d'autres systèmes de type Unix), cela peut être fait de la manière suivante:

#include <unistd.h>
#include <termios.h>

char getch() {
        char buf = 0;
        struct termios old = {0};
        if (tcgetattr(0, &old) < 0)
                perror("tcsetattr()");
        old.c_lflag &= ~ICANON;
        old.c_lflag &= ~ECHO;
        old.c_cc[VMIN] = 1;
        old.c_cc[VTIME] = 0;
        if (tcsetattr(0, TCSANOW, &old) < 0)
                perror("tcsetattr ICANON");
        if (read(0, &buf, 1) < 0)
                perror ("read()");
        old.c_lflag |= ICANON;
        old.c_lflag |= ECHO;
        if (tcsetattr(0, TCSADRAIN, &old) < 0)
                perror ("tcsetattr ~ICANON");
        return (buf);
}

En gros, vous devez désactiver le mode canonique (et le mode écho pour supprimer l'écho).

Faucon Momot
la source
J'ai essayé d'implémenter ce code, mais j'ai eu une erreur lors de l'appel à read. J'ai inclus les deux en-têtes.
Michael Dorst
6
Peut-être parce que ce code est erroné, car read () est un appel système POSIX défini dans unistd.h. stdio.h peut l'inclure par hasard, mais vous n'avez pas du tout besoin de stdio.h pour ce code; remplacez-le par unistd.h et cela devrait être bon.
Falcon Momot
1
Je ne sais pas comment je me retrouve ici alors que je cherchais à obtenir une entrée clavier sur le terminal ROS de Raspberry Pi. Cet extrait de code fonctionne pour moi.
aknay
2
@FalconMomot Dans mon IDE NetBeans 8.1 (chez Kali Linux), il est dit: error: ‘perror’ was not declared in this scopelors de la compilation, mais fonctionne bien lorsqu'il est inclus stdio.havec unistd.h.
Abhishek Kashyap
3
Existe-t-il un moyen simple d'étendre cela pour ne pas bloquer?
Joe Strout
18

J'ai trouvé cela sur un autre forum en cherchant à résoudre le même problème. Je l'ai un peu modifié par rapport à ce que j'ai trouvé. Cela fonctionne très bien. J'utilise OS X, donc si vous exécutez Microsoft, vous devrez trouver la bonne commande system () pour passer aux modes brut et cuit.

#include <iostream> 
#include <stdio.h>  
using namespace std;  

int main() { 
  // Output prompt 
  cout << "Press any key to continue..." << endl; 

  // Set terminal to raw mode 
  system("stty raw"); 

  // Wait for single character 
  char input = getchar(); 

  // Echo input:
  cout << "--" << input << "--";

  // Reset terminal to normal "cooked" mode 
  system("stty cooked"); 

  // And we're out of here 
  return 0; 
}
cwhiii
la source
13
Bien que cela fonctionne, pour ce que ça vaut, faire des bombardements au système est rarement la «meilleure» façon de le faire à mon avis. Le programme stty est écrit en C, vous pouvez donc inclure <termios.h> ou <sgtty.h> et appeler le même code que stty utilise, sans dépendre d'un programme externe / fork / whatnot.
Chris Lutz
1
J'en avais besoin pour une preuve de concept aléatoire et pour déconner. Juste ce dont j'avais besoin. Je vous remercie. Il convient de noter: je mettrais définitivement le stty cuit à la fin du programme sinon votre shell restera en mode stty raw, ce qui a essentiellement cassé mon shell lol après l'arrêt du programme.
Benjamin
Je pense que vous avez oublié#include <stdlib.h>
NerdOfCode
14

CONIO.H

les fonctions dont vous avez besoin sont:

int getch();
Prototype
    int _getch(void); 
Description
    _getch obtains a character  from stdin. Input is unbuffered, and this
    routine  will  return as  soon as  a character is  available  without 
    waiting for a carriage return. The character is not echoed to stdout.
    _getch bypasses the normal buffering done by getchar and getc. ungetc 
    cannot be used with _getch. 
Synonym
    Function: getch 


int kbhit();
Description
    Checks if a keyboard key has been pressed but not yet read. 
Return Value
    Returns a non-zero value if a key was pressed. Otherwise, returns 0.

libconio http://sourceforge.net/projects/libconio

ou

Implémentation Linux C ++ de conio.h http://sourceforge.net/projects/linux-conioh

chrissidtmeier
la source
9

Si vous êtes sous Windows, vous pouvez utiliser PeekConsoleInput pour détecter s'il y a une entrée,

HANDLE handle = GetStdHandle(STD_INPUT_HANDLE);
DWORD events;
INPUT_RECORD buffer;
PeekConsoleInput( handle, &buffer, 1, &events );

puis utilisez ReadConsoleInput pour "consommer" le caractère d'entrée.

PeekConsoleInput(handle, &buffer, 1, &events);
if(events > 0)
{
    ReadConsoleInput(handle, &buffer, 1, &events);  
    return buffer.Event.KeyEvent.wVirtualKeyCode;
}
else return 0

pour être honnête, cela provient d'un ancien code que j'ai, vous devez donc le manipuler un peu.

Ce qui est cool, c'est qu'il lit les entrées sans rien demander, de sorte que les caractères ne sont pas affichés du tout.

hasen
la source
9
#include <conio.h>

if (kbhit() != 0) {
    cout << getch() << endl;
}

Cela permet kbhit()de vérifier si le clavier est enfoncé et utilise getch()pour obtenir le caractère qui est enfoncé.

Joseph Dykstra
la source
5
conio.h ? "conio.h est un fichier d'en-tête C utilisé dans les anciens compilateurs MS-DOS pour créer des interfaces utilisateur texte." Semble un peu dépassé.
kay - SE is evil
5

C et C ++ ont une vue très abstraite des E / S et il n'y a pas de méthode standard pour faire ce que vous voulez. Il existe des moyens standard d'obtenir des caractères du flux d'entrée standard, s'il y en a à obtenir, et rien d'autre n'est défini par l'une ou l'autre des langues. Toute réponse devra donc être spécifique à la plate-forme, peut-être dépendant non seulement du système d'exploitation, mais également du cadre logiciel.

Il y a des suppositions raisonnables ici, mais il n'y a aucun moyen de répondre à votre question sans savoir quel est votre environnement cible.

David Thornley
la source
5

J'utilise kbhit () pour voir si un caractère est présent, puis getchar () pour lire les données. Sous Windows, vous pouvez utiliser "conio.h". Sous Linux, vous devrez implémenter votre propre kbhit ().

Voir le code ci-dessous:

// kbhit
#include <stdio.h>
#include <sys/ioctl.h> // For FIONREAD
#include <termios.h>
#include <stdbool.h>

int kbhit(void) {
    static bool initflag = false;
    static const int STDIN = 0;

    if (!initflag) {
        // Use termios to turn off line buffering
        struct termios term;
        tcgetattr(STDIN, &term);
        term.c_lflag &= ~ICANON;
        tcsetattr(STDIN, TCSANOW, &term);
        setbuf(stdin, NULL);
        initflag = true;
    }

    int nbbytes;
    ioctl(STDIN, FIONREAD, &nbbytes);  // 0 is STDIN
    return nbbytes;
}

// main
#include <unistd.h>

int main(int argc, char** argv) {
    char c;
    //setbuf(stdout, NULL); // Optional: No buffering.
    //setbuf(stdin, NULL);  // Optional: No buffering.
    printf("Press key");
    while (!kbhit()) {
        printf(".");
        fflush(stdout);
        sleep(1);
    }
    c = getchar();
    printf("\nChar received:%c\n", c);
    printf("Done.\n");

    return 0;
}
ssinfod
la source
4

La chose la plus proche du portable est d'utiliser la ncurseslibrairie pour mettre le terminal en "mode cbreak". L'API est gigantesque; les routines que vous voudrez le plus sont

  • initscr et endwin
  • cbreak et nocbreak
  • getch

Bonne chance!

Norman Ramsey
la source
3

Ce qui suit est une solution extraite de la programmation Expert C: Deep Secrets , qui est censé fonctionner sur SVr4. Il utilise stty et ioctl .

#include <sys/filio.h>
int kbhit()
{
 int i;
 ioctl(0, FIONREAD, &i);
 return i; /* return a count of chars available to read */
}
main()
{
 int i = 0;
 intc='';
 system("stty raw -echo");
 printf("enter 'q' to quit \n");
 for (;c!='q';i++) {
    if (kbhit()) {
        c=getchar();
       printf("\n got %c, on iteration %d",c, i);
    }
}
 system("stty cooked echo");
}
PolyThinker
la source
3

J'ai toujours voulu une boucle pour lire mon entrée sans appuyer sur la touche retour. cela a fonctionné pour moi.

#include<stdio.h>
 main()
 {
   char ch;
    system("stty raw");//seting the terminal in raw mode
    while(1)
     {
     ch=getchar();
      if(ch=='~'){          //terminate or come out of raw mode on "~" pressed
      system("stty cooked");
     //while(1);//you may still run the code 
     exit(0); //or terminate
     }
       printf("you pressed %c\n ",ch);  //write rest code here
      }

    }
Setu Gupta
la source
1
une fois que vous serez en MODE RAW, il est difficile de tuer le processus. alors gardez un point pour tuer le processus comme je l'ai fait "en appuyant sur" ~ "". ................ Sinon, vous pouvez tuer le processus d'un autre terminal en utilisant KILL.
Setu Gupta
3

ncurses fournit un bon moyen de faire cela! C'est aussi mon tout premier message (dont je me souviens), donc tous les commentaires sont les bienvenus. J'apprécierai ceux qui sont utiles, mais tous sont les bienvenus!

pour compiler: g ++ -std = c ++ 11 -pthread -lncurses .cpp -o

#include <iostream>
#include <ncurses.h>
#include <future>

char get_keyboard_input();

int main(int argc, char *argv[])
{
    initscr();
    raw();
    noecho();
    keypad(stdscr,true);

    auto f = std::async(std::launch::async, get_keyboard_input);
    while (f.wait_for(std::chrono::milliseconds(20)) != std::future_status::ready)
    {
        // do some work
    }

    endwin();
    std::cout << "returned: " << f.get() << std::endl;
    return 0;
}

char get_keyboard_input()
{
    char input = '0';
    while(input != 'q')
    {
        input = getch();
    }
    return input;
}
AngryDane
la source
2

fonctionne pour moi sur Windows:

#include <conio.h>
char c = _getch();
user1438233
la source
1

Vous pouvez le faire de manière portable en utilisant SDL (la bibliothèque Simple DirectMedia), même si je soupçonne que vous n'aimerez peut-être pas son comportement. Quand je l'ai essayé, j'ai dû demander à SDL de créer une nouvelle fenêtre vidéo (même si je n'en avais pas besoin pour mon programme) et de faire en sorte que cette fenêtre «saisisse» presque toutes les entrées du clavier et de la souris (ce qui convenait à mon utilisation mais pouvait être ennuyeux ou irréalisable dans d’autres situations). Je soupçonne que c'est exagéré et ne vaut pas la peine à moins qu'une portabilité complète ne soit un must - sinon essayez l'une des autres solutions suggérées.

À propos, cela vous donnera les événements de pression et de libération des touches séparément, si vous aimez cela.

Ruchira
la source
1

Voici une version qui ne s'applique pas au système (écrite et testée sur macOS 10.14)

#include <unistd.h>
#include <termios.h>
#include <stdio.h>
#include <string.h>

char* getStr( char* buffer , int maxRead ) {
  int  numRead  = 0;
  char ch;

  struct termios old = {0};
  struct termios new = {0};
  if( tcgetattr( 0 , &old ) < 0 )        perror( "tcgetattr() old settings" );
  if( tcgetattr( 0 , &new ) < 0 )        perror( "tcgetaart() new settings" );
  cfmakeraw( &new );
  if( tcsetattr( 0 , TCSADRAIN , &new ) < 0 ) perror( "tcssetattr makeraw new" );

  for( int i = 0 ; i < maxRead ; i++)  {
    ch = getchar();
    switch( ch )  {
      case EOF: 
      case '\n':
      case '\r':
        goto exit_getStr;
        break;

      default:
        printf( "%1c" , ch );
        buffer[ numRead++ ] = ch;
        if( numRead >= maxRead )  {
          goto exit_getStr;
        }
        break;
    }
  }

exit_getStr:
  if( tcsetattr( 0 , TCSADRAIN , &old) < 0)   perror ("tcsetattr reset to old" );
  printf( "\n" );   
  return buffer;
}


int main( void ) 
{
  const int maxChars = 20;
  char      stringBuffer[ maxChars+1 ];
  memset(   stringBuffer , 0 , maxChars+1 ); // initialize to 0

  printf( "enter a string: ");
  getStr( stringBuffer , maxChars );
  printf( "you entered: [%s]\n" , stringBuffer );
}
Jeff Szuhay
la source