我从来都不记得我是怎么做到的,因为它很少出现在我身上。但是在C或c++中,从标准输入中读取字符而不等待换行符(按enter)的最佳方法是什么?

理想情况下,它也不会将输入字符回显到屏幕上。我只是想在不影响控制台屏幕的情况下捕捉击键。


当前回答

管道演示:如何从C语言的系统调用管道读取键盘按键

理想情况下,它也不会将输入字符回显到屏幕上。我只是想在不影响控制台屏幕的情况下捕捉击键。

要在Linux上做到这一点,您可以使用以下bash命令:

read -sn1 c && printf "You Pressed: %s\n" "$c"

请看我在这里的回答关于这个的详细信息:shell脚本响应按键。

因此,要在Linux上的C或c++中做到这一点,您只需要通过使用popen()和fgets()的管道通过系统调用调用上面的bash命令,这样您就可以从bash命令读取输出。

下面是一个完整的例子,在Linux上运行良好的C和c++:

read_system_call_via_pipe__keypress.c:

#include <stdbool.h> // For `true` (`1`) and `false` (`0`) macros in C
#include <stdint.h>  // For `uint8_t`, `int8_t`, etc.
#include <stdio.h>   // For `printf()`
#include <stdlib.h>

#define BUFSIZE 32

// Read a keyboard key press and return the character pressed, or a negative
// number in the event of an error.
// NB: for help reading output from system calls, see here:
//  1. https://stackoverflow.com/a/28971647/4561887
//  2. https://stackoverflow.com/a/18297075/4561887
char getKeypress()
{
    // This bash cmd is from my answer here:
    // https://stackoverflow.com/a/70979348/4561887
    const char* cmd = "bash -c 'read -s -n1 c && printf \"%s\" \"$c\"'";
    FILE *fp = popen(cmd, "r");
    if (fp == NULL)
    {
        printf("\nError opening pipe!\n");
        return -1;
    }

    char buf[BUFSIZE] = {0};
    char* retval1 = fgets(buf, BUFSIZE, fp);
    if (retval1 == NULL)
    {
        printf("\nFailed to read cmd response.\n");
        return -2;
    }

    // See meaning of this return value here:
    // https://stackoverflow.com/questions/43116/how-can-i-run-an-external-program-from-c-and-parse-its-output/28971647#comment60311936_28971647
    int retval2 = pclose(fp);
    if (retval2 == -1)
    {
        printf("\nError obtaining the cmd's exit status code.\n");
        return -3;
    }
    else if (retval2 != 0)
    {
        printf("\nCommand exited with exit status code %i.\n", retval2);
        return -4;
    }

    char keyPressed = buf[0];
    return keyPressed;
}

// int main(int argc, char *argv[])  // alternative prototype
int main()
{
    printf("Press any key to continue: ");
    fflush(stdout);
    char keyPressed = getKeypress();
    if (keyPressed > 0)
    {
        printf("\nKey pressed = %c\n", keyPressed);
    }

    return 0;
}

C和c++编译和运行命令是下面输出的一部分。下面是一些演示:

在C:

eRCaGuy_hello_world/c$ gcc -Wall -Wextra -Werror -O3 -std=gnu17 read_keypress_system_call.c -o bin/a && bin/a
Press any key to continue:
Key pressed = P

或,在c++中:

eRCaGuy_hello_world/c$ g++ -Wall -Wextra -Werror -O3 -std=c++17 read_keypress_system_call.c -o bin/a && bin/a
Press any key to continue:
Key pressed = u

参见:

要更进一步,请参阅我在这里的另一个回答,我一次检测并解析3个字符,以便检测方向键的向上、向下、向左或向右:在C语言中读取键按下。方向键,Enter键

引用:

How I learned to read from a pipe to get system call output: How can I run an external program from C and parse its output? Is there a way to obtain the output of a linux command(like ifconfig) on a .txt file using a C program? [duplicate] How to compile and use popen() in C: use -std=gnu17 instead of -std=c17: popen implicitly declared even though #include <stdio.h> is added [my answer] How to read without blocking, via bash: shell script respond to keypress [my answer] How do I read in the Enter key as an input in C?

关于这个话题的3个问题

从标准输入中捕获字符,而不需要等待按enter键 C非阻塞键盘输入 如何避免按Enter与getchar()只为读取单个字符?

其他回答

我在另一个论坛上发现了这个,当时我想解决同样的问题。根据我的发现,我做了一些修改。效果很好。我运行的是OS X,所以如果你运行的是微软,你需要找到正确的system()命令来切换到raw和cooked模式。

#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; 
}

我也遇到过同样的问题。这里是一个小的解决方案为windows控制台使用cygwing++与if(GetKeyState(keycode) & bitANDcompare){};

#include <windows.h>
#include <fstream>
#include <iostream>

using namespace std;
void clear() {
    COORD topLeft  = { 0, 0 };
    HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO screen;
    DWORD written;

    GetConsoleScreenBufferInfo(console, &screen);
    FillConsoleOutputCharacterA(
        console, ' ', screen.dwSize.X * screen.dwSize.Y, topLeft, &written
    );
    FillConsoleOutputAttribute(
        console, FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE,
        screen.dwSize.X * screen.dwSize.Y, topLeft, &written
    );
    SetConsoleCursorPosition(console, topLeft);
}

class Keyclick{
    private:
    int key;
    char id;
    public:
    bool keydown = false;
    Keyclick(int key1, char id1){
        key=key1;
        id=id1;
    };
    void watch(){
        if(keydown==false){
            if(GetKeyState(key) & 0x8000 ){
                cout << id;
                cout << "  pressed.\r\n";
                keydown = true;
            }
        }
        if(keydown == true){
            if(!(GetKeyState(key) & 0x8000)) {
                cout << "released!!!!!!!!!!\r\n";
                keydown = false;
                clear();
            }
        }
    };
};

int main()
{
    bool primaryloop =true;
    Keyclick keysp(VK_SPACE,'S');
    Keyclick keyw(0x57,'w');
    Keyclick keya(0x41,'a');
    Keyclick keys(0x53,'s');
    Keyclick keyd(0x44,'d');
    Keyclick keyesc(VK_ESCAPE,'E');
    
    while(primaryloop){
        keysp.watch();
        keyw.watch();
        keya.watch();
        keys.watch();
        keyd.watch();
        keyesc.watch();
        
        if(keyesc.keydown){
            primaryloop=false;
        };      
    }
    return 0;
}

https://github.com/wark77/windows_console_keypoller/blob/main/getkeystate_SOLUTION01.cpp

下面是一个不向系统提供外壳的版本(在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 );
}

C和c++对I/O有一个非常抽象的看法,没有标准的方法来做你想做的事情。有从标准输入流中获取字符的标准方法(如果有的话),这两种语言都没有定义其他任何方法。因此,任何答案都必须是特定于平台的,可能不仅取决于操作系统,还取决于软件框架。

这里有一些合理的猜测,但如果不知道目标环境是什么,就无法回答您的问题。

#include <conio.h>

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

它使用kbhit()来检查键盘是否被按下,并使用getch()来获取被按下的字符。