从标准输入中捕获字符,无需等待按回车键

Capture characters from standard input without waiting for enter to be pressed

提问人:Adam 提问时间:1/8/2009 最后编辑:jwwAdam 更新时间:3/21/2023 访问量:286688

问:

我永远不记得我是怎么做到的,因为它对我来说很少出现。但是在 C 或 C++ 中,在不等待换行符(按 Enter)的情况下从标准输入中读取字符的最佳方法是什么?

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

C++ C 输入流

评论

2赞 Roddy 1/8/2009
@adam - 你能澄清一下吗:你想要一个在没有可用字符时立即返回的函数,还是一个总是等待单次击键的函数?
5赞 Adam 5/28/2009
@Roddy - 我想要一个始终等待单次击键的函数。

答:

125赞 Johannes Schaub - litb 1/8/2009 #1

这在纯 C++ 中以可移植的方式是不可能的,因为它过多地依赖于可能连接的终端(它们通常是行缓冲的)。但是,您可以使用库来实现:stdin

  1. conio 可用于 Windows 编译器。使用该函数为您提供一个字符,而无需等待 Enter 键。我不是一个经常使用 Windows 开发人员的人,但我看到我的同学只是包含和使用它。参见维基百科的conio.h。它列出了 ,在 Visual C++ 中声明已弃用。_getch()<conio.h>getch()

  2. 适用于 Linux 的诅咒。兼容的诅咒实现也可用于 Windows。它还有一个功能。(尝试查看其手册页)。参见维基百科上的诅咒getch()man getch

如果您的目标是跨平台兼容性,我建议您使用诅咒。也就是说,我确信您可以使用一些函数来关闭线路缓冲(我相信这被称为“原始模式”,而不是“熟模式” - 查看)。如果我没记错的话,诅咒会以一种便携的方式为你处理这个问题。man stty

评论

17赞 5/7/2016
请注意,现在是 的推荐变体。ncursescurses
15赞 1/3/2017
如果你确实需要一个图书馆,那么他们是如何做到的?要制作库,你肯定必须对它进行编程,这意味着任何人都可以对它进行编程,那么为什么我们不能只编写我们自己需要的库代码呢?这也将使 这在纯 c++ 中不可能移植
2赞 Johannes Schaub - litb 1/4/2017
@kid8我不明白
5赞 Abhinav Gauniyal 1/18/2017
@JohannesSchaub,他可能想说的是,当你说这是不可能的时,库实现者是如何用纯 C/C++ 制作可移植方法的。
2赞 Johannes Schaub - litb 1/18/2017
@AbhinavGauniyal啊,我明白了。但是,我并没有说库实现者没有创建可移植的方法(即由合理的可移植程序使用)。我暗示他们没有使用便携式C++。很明显他们没有,我不明白为什么他的评论说我的这部分答案是错误的。
4赞 Tritium 1/8/2009 #2

假设是 Windows,请查看 ReadConsoleInput 函数。

评论

0赞 John Henckel 1/4/2017
链接在这里 msdn.microsoft.com/en-us/library/...
1赞 Xantium 10/7/2020
@JohnHenckel 这是针对 C# 的,您可能想要 learn.microsoft.com/en-us/windows/console/readconsoleinput
4赞 David Thornley 1/8/2009 #3

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

这里有一些合理的猜测,但是在不知道目标环境是什么的情况下,没有办法回答你的问题。

13赞 dddomodossola 1/8/2009 #4

科尼奥。H

您需要的功能包括:

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

conio.h http://sourceforge.net/projects/linux-conioh 的 Linux c++ 实现

评论

3赞 πάντα ῥεῖ 8/4/2021
conio.h已经过时了,仅适用于旧的 DOS API,请不要使用它
1赞 AHelps 9/11/2022
这适用于 Windows (Visual Studio 2022)。旧的 DOS conio.h 有 getch();当前的文档有 _getch(),就像上面的文档一样。它在 Windows 控制台中运行良好。
0赞 Cosmo 2/11/2023
科尼奥!过时的!
4赞 Norman Ramsey 1/8/2009 #5

最接近可移植的是使用 ncurses 库将终端置于“cbreak 模式”。API 是巨大的;您最想要的例程是

  • initscrendwin
  • cbreaknocbreak
  • getch

祝你好运!

14赞 hasen 1/8/2009 #6

如果你使用的是 Windows,则可以使用 PeekConsoleInput 来检测是否有任何输入,

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

然后使用 ReadConsoleInput 来“消费”输入字符 ..

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

老实说,这是来自我的一些旧代码,所以你必须稍微摆弄一下。

不过很酷的是,它会在不提示任何内容的情况下读取输入,因此根本不显示字符。

2赞 PolyThinker 1/8/2009 #7

以下是从 Expert C Programming: Deep Secrets 中提取的解决方案,它应该适用于 SVr4。它使用 sttyioctl

#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");
}
29赞 cwhiii 5/27/2009 #8

我在另一个论坛上发现了这个问题,同时希望解决同样的问题。我根据我发现的内容对其进行了一些修改。效果很好。我运行的是OS X,所以如果你运行的是Microsoft,你需要找到正确的system()命令来切换到原始模式和熟模式。

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

评论

18赞 Chris Lutz 5/27/2009
虽然这可行,但就其价值而言,在我看来,向系统掏钱很少是“最佳”方式。stty 程序是用 C 语言编写的,因此您可以包含 <termios.h> 或 <sgtty.h> 并调用 stty 使用的相同代码,而无需依赖外部程序/fork/whatnot。
1赞 Benjamin 11/4/2014
我需要它来随机验证概念的东西和乱七八糟的东西。正是我需要的。谢谢。需要注意的是:我肯定会把 stty 煮熟放在程序的末尾,否则你的 shell 将保持 stty raw 模式,这基本上在程序停止后破坏了我的 shell lol。
0赞 NerdOfCode 6/9/2018
我想你忘了#include <stdlib.h>
1赞 Sapphire_Brick 8/22/2020
使用是一个非常糟糕的主意。system
101赞 anon 5/27/2009 #9

在 Linux(和其他类 unix 系统)上,这可以通过以下方式完成:

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

基本上,您必须关闭规范模式(和回声模式以抑制回声)。

评论

0赞 Michael Francis 7/21/2012
我尝试实现此代码,但在调用 .我已经包含了两个标题。read
7赞 Falcon Momot 7/22/2012
可能是因为这段代码是错误的,因为 read() 是 unistd.h 中定义的 POSIX 系统调用。stdio.h 可能巧合地包含了它,但实际上你根本不需要 stdio.h 来编写此代码;用 unistd.h 替换它,它应该很好。
1赞 aknay 3/23/2015
我不知道当我在 Raspberry Pi 的 ROS 终端上寻找键盘输入时,我是如何来到这里的。这段代码对我有用。
3赞 Abhishek Kashyap 8/6/2016
@FalconMomot 在我的 NetBeans IDE 8.1(在 Kali Linux 上)中,它说:编译时,但与 一起包含时工作正常。error: ‘perror’ was not declared in this scopestdio.hunistd.h
3赞 Joe Strout 7/21/2017
有没有简单的方法可以将其扩展到不阻止?
8赞 Joseph Dykstra 7/8/2012 #10
#include <conio.h>

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

这用于检查是否正在按下键盘,并用于获取正在按下的字符。kbhit()getch()

评论

7赞 Kijewski 7/8/2012
科尼奥.h?“conio.h 是旧的 MS-DOS 编译器中用于创建文本用户界面的 C 头文件。”似乎有些过时了。
1赞 Ruchira 8/29/2012 #11

您可以使用 SDL(简单 DirectMedia 库)可移植地完成此操作,尽管我怀疑您可能不喜欢它的行为。当我尝试它时,我必须让 SDL 创建一个新的视频窗口(即使我的程序不需要它),并让这个窗口“抓取”几乎所有的键盘和鼠标输入(这对我的使用来说是可以的,但在其他情况下可能会很烦人或不可行)。我怀疑这是矫枉过正,不值得,除非必须完全可移植性 - 否则请尝试其他建议的解决方案之一。

顺便说一句,如果您喜欢的话,这将分别为您提供关键的按下和发布事件。

3赞 Setu Gupta 2/20/2015 #12

我一直想要一个循环来读取我的输入,而无需按回车键。 这对我有用。

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

    }

评论

1赞 Setu Gupta 2/20/2015
一旦您将处于RAW模式,就很难终止该过程。因此,请保持一点以杀死该过程,就像我“按”~“一样。................否则,您可以使用KILL从其他终端终止该进程。
3赞 user1438233 6/12/2015 #13

在 Windows 上对我有用:

#include <conio.h>
char c = _getch();
7赞 ssinfod 10/19/2015 #14

我使用 kbhit() 查看是否存在 char,然后使用 getchar() 读取数据。 在 Windows 上,您可以使用“conio.h”。在 linux 上,您必须实现自己的 kbhit()。

请参阅下面的代码:

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

评论

0赞 Andrew 5/3/2021
在此处发布了此解决方案的变体: stackoverflow.com/a/67363091/1599699 效果很好,谢谢。
7赞 AngryDane 6/27/2018 #15

ncurses 提供了一个很好的方法来做到这一点! 这也是我的第一篇文章(我记得),所以欢迎任何评论。我将不胜感激有用的,但欢迎所有人!

编译: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;
}
1赞 Jeff Szuhay 3/13/2020 #16

这是一个不向系统支付的版本(在 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 );
}
7赞 ProjectPhysX 4/11/2021 #17

由于以前的解决方案不能跨平台工作并且存在特殊键问题,因此这是我的解决方案,它适用于 Windows 和 Linux,并且使用最少的外部库(适用于 Windows 和 + 适用于 Linux)。Windows.hsys/ioctl.htermios.h

对于 ASCII 字符(换行符/制表符/空格符/退格键/删除、!”#$%&'()*+,-./0-9:;<=>?@A-Z[]^_'a-z{|}~üäÄöÖÜßμ'§°¹³²),返回 ASCII 代码(正数),对于特殊键(箭头键、向上/向下翻页、pos1/end、转义、插入、F1-F12),返回 Windows 虚拟键代码(负数)的负数。

#include <iostream>
#include <string>
#include <thread> // contains <chrono>
using namespace std;

void println(const string& s="") {
    cout << s << endl;
}
void sleep(const double t) {
    if(t>0.0) this_thread::sleep_for(chrono::milliseconds((int)(1E3*t+0.5)));
}



// ASCII codes (key>0): 8 backspace, 9 tab, 10 newline, 27 escape, 127 delete, !"#$%&'()*+,-./0-9:;<=>?@A-Z[]^_`a-z{|}~üäÄöÖÜßµ´§°¹³²
// control key codes (key<0): -38/-40/-37/-39 up/down/left/right arrow, -33/-34 page up/down, -36/-35 pos1/end
// other key codes (key<0): -45 insert, -144 num lock, -20 caps lock, -91 windows key, -93 kontext menu key, -112 to -123 F1 to F12
// not working: ¹ (251), num lock (-144), caps lock (-20), windows key (-91), kontext menu key (-93), F11 (-122)
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#include <Windows.h>
int key_press() { // not working: F11 (-122, toggles fullscreen)
    KEY_EVENT_RECORD keyevent;
    INPUT_RECORD irec;
    DWORD events;
    while(true) {
        ReadConsoleInput(GetStdHandle(STD_INPUT_HANDLE), &irec, 1, &events);
        if(irec.EventType==KEY_EVENT&&((KEY_EVENT_RECORD&)irec.Event).bKeyDown) {
            keyevent = (KEY_EVENT_RECORD&)irec.Event;
            const int ca = (int)keyevent.uChar.AsciiChar;
            const int cv = (int)keyevent.wVirtualKeyCode;
            const int key = ca==0 ? -cv : ca+(ca>0?0:256);
            switch(key) {
                case  -16: continue; // disable Shift
                case  -17: continue; // disable Ctrl / AltGr
                case  -18: continue; // disable Alt / AltGr
                case -220: continue; // disable first detection of "^" key (not "^" symbol)
                case -221: continue; // disable first detection of "`" key (not "`" symbol)
                case -191: continue; // disable AltGr + "#"
                case  -52: continue; // disable AltGr + "4"
                case  -53: continue; // disable AltGr + "5"
                case  -54: continue; // disable AltGr + "6"
                case  -12: continue; // disable num block 5 with num lock deactivated
                case   13: return  10; // enter
                case  -46: return 127; // delete
                case  -49: return 251; // ¹
                case    0: continue;
                case    1: continue; // disable Ctrl + a (selects all text)
                case    2: continue; // disable Ctrl + b
                case    3: continue; // disable Ctrl + c (terminates program)
                case    4: continue; // disable Ctrl + d
                case    5: continue; // disable Ctrl + e
                case    6: continue; // disable Ctrl + f (opens search)
                case    7: continue; // disable Ctrl + g
                //case    8: continue; // disable Ctrl + h (ascii for backspace)
                //case    9: continue; // disable Ctrl + i (ascii for tab)
                case   10: continue; // disable Ctrl + j
                case   11: continue; // disable Ctrl + k
                case   12: continue; // disable Ctrl + l
                //case   13: continue; // disable Ctrl + m (breaks console, ascii for new line)
                case   14: continue; // disable Ctrl + n
                case   15: continue; // disable Ctrl + o
                case   16: continue; // disable Ctrl + p
                case   17: continue; // disable Ctrl + q
                case   18: continue; // disable Ctrl + r
                case   19: continue; // disable Ctrl + s
                case   20: continue; // disable Ctrl + t
                case   21: continue; // disable Ctrl + u
                case   22: continue; // disable Ctrl + v (inserts clipboard)
                case   23: continue; // disable Ctrl + w
                case   24: continue; // disable Ctrl + x
                case   25: continue; // disable Ctrl + y
                case   26: continue; // disable Ctrl + z
                default: return key; // any other ASCII/virtual character
            }
        }
    }
}
#elif defined(__linux__)
#include <sys/ioctl.h>
#include <termios.h>
int key_press() { // not working: ¹ (251), num lock (-144), caps lock (-20), windows key (-91), kontext menu key (-93)
    struct termios term;
    tcgetattr(0, &term);
    while(true) {
        term.c_lflag &= ~(ICANON|ECHO); // turn off line buffering and echoing
        tcsetattr(0, TCSANOW, &term);
        int nbbytes;
        ioctl(0, FIONREAD, &nbbytes); // 0 is STDIN
        while(!nbbytes) {
            sleep(0.01);
            fflush(stdout);
            ioctl(0, FIONREAD, &nbbytes); // 0 is STDIN
        }
        int key = (int)getchar();
        if(key==27||key==194||key==195) { // escape, 194/195 is escape for °ß´äöüÄÖÜ
            key = (int)getchar();
            if(key==91) { // [ following escape
                key = (int)getchar(); // get code of next char after \e[
                if(key==49) { // F5-F8
                    key = 62+(int)getchar(); // 53, 55-57
                    if(key==115) key++; // F5 code is too low by 1
                    getchar(); // take in following ~ (126), but discard code
                } else if(key==50) { // insert or F9-F12
                    key = (int)getchar();
                    if(key==126) { // insert
                        key = 45;
                    } else { // F9-F12
                        key += 71; // 48, 49, 51, 52
                        if(key<121) key++; // F11 and F12 are too low by 1
                        getchar(); // take in following ~ (126), but discard code
                    }
                } else if(key==51||key==53||key==54) { // delete, page up/down
                    getchar(); // take in following ~ (126), but discard code
                }
            } else if(key==79) { // F1-F4
                key = 32+(int)getchar(); // 80-83
            }
            key = -key; // use negative numbers for escaped keys
        }
        term.c_lflag |= (ICANON|ECHO); // turn on line buffering and echoing
        tcsetattr(0, TCSANOW, &term);
        switch(key) {
            case  127: return   8; // backspace
            case  -27: return  27; // escape
            case  -51: return 127; // delete
            case -164: return 132; // ä
            case -182: return 148; // ö
            case -188: return 129; // ü
            case -132: return 142; // Ä
            case -150: return 153; // Ö
            case -156: return 154; // Ü
            case -159: return 225; // ß
            case -181: return 230; // µ
            case -167: return 245; // §
            case -176: return 248; // °
            case -178: return 253; // ²
            case -179: return 252; // ³
            case -180: return 239; // ´
            case  -65: return -38; // up arrow
            case  -66: return -40; // down arrow
            case  -68: return -37; // left arrow
            case  -67: return -39; // right arrow
            case  -53: return -33; // page up
            case  -54: return -34; // page down
            case  -72: return -36; // pos1
            case  -70: return -35; // end
            case    0: continue;
            case    1: continue; // disable Ctrl + a
            case    2: continue; // disable Ctrl + b
            case    3: continue; // disable Ctrl + c (terminates program)
            case    4: continue; // disable Ctrl + d
            case    5: continue; // disable Ctrl + e
            case    6: continue; // disable Ctrl + f
            case    7: continue; // disable Ctrl + g
            case    8: continue; // disable Ctrl + h
            //case    9: continue; // disable Ctrl + i (ascii for tab)
            //case   10: continue; // disable Ctrl + j (ascii for new line)
            case   11: continue; // disable Ctrl + k
            case   12: continue; // disable Ctrl + l
            case   13: continue; // disable Ctrl + m
            case   14: continue; // disable Ctrl + n
            case   15: continue; // disable Ctrl + o
            case   16: continue; // disable Ctrl + p
            case   17: continue; // disable Ctrl + q
            case   18: continue; // disable Ctrl + r
            case   19: continue; // disable Ctrl + s
            case   20: continue; // disable Ctrl + t
            case   21: continue; // disable Ctrl + u
            case   22: continue; // disable Ctrl + v
            case   23: continue; // disable Ctrl + w
            case   24: continue; // disable Ctrl + x
            case   25: continue; // disable Ctrl + y
            case   26: continue; // disable Ctrl + z (terminates program)
            default: return key; // any other ASCII character
        }
    }
}
#endif // Windows/Linux

最后,这里有一个如何使用它的示例:

int main() {
    while(true) {
        const int key = key_press(); // blocks until a key is pressed
        println("Input is: "+to_string(key)+", \""+(char)key+"\"");
    }
    return 0;
}

评论

1赞 Eugene K 5/30/2022
这就是我一直在寻找的确切答案!哎呀!只是想知道,循环的目的是什么:fflush();ioctl(0, FIONREAD,...)?
0赞 ProjectPhysX 5/30/2022
@Eugene K,不客气!循环通过定期检查键输入,使 key_press() 等待到按下键。
1赞 Eugene K 6/2/2022
明白了!我以为getchar()会阻止(并等待)键盘输入。
1赞 Andrew 5/3/2021 #18

ssinfod 对 Linux 的答案的变体,对我的口味来说更干净一些,为 和 实现,并擦除无效字符而没有错误。wcoutwchar_t

#include <functional>

//For Linux kbhit(). For Windows, use conio.h.
#ifdef __unix__
  #include <sys/ioctl.h> //For FIONREAD.
  #include <termios.h>

  //Call this at program start to setup for kbhit.
  void initTerminalInput()
  {
    //Disable internal buffering.
    std::wcout << std::unitbuf;

    //Turn off line buffering.
    struct termios term;
    tcgetattr(0, &term);
    term.c_lflag &= ~ICANON;
    tcsetattr(0, TCSANOW, &term);
    setbuf(stdin, NULL);
  }

  //Returns 0 if there's no input character to read.
  int kbhit()
  {
    static int nbbytes;
    ioctl(0, FIONREAD, &nbbytes);
    return nbbytes;
  }
#endif

//Waits for and retrieves a single validated character, calling a validation function on each character entered and
//erasing any that are invalid (when the validation function returns false).
static wchar_t getWChar(std::function<bool(wchar_t)> validationFunction)
{
  static wchar_t inputWChar;
  do
  {
    //Wait until there's an input character.
    while (!kbhit())
    {
    }
    inputWChar = getwchar();
    //Validate the input character.
    if (validationFunction(inputWChar))
    {
      //Valid.
      break;
    }
    else
    {
      //Erase the invalid character.
      std::wcout << L"\b \b";
    }
  } while (true);
  return inputWChar;
}

在下面的示例中,我希望用户输入 1、2 或 3。输入的任何其他字符都不会显示,它将等待,直到按下其中一个有效字符:

int main()
{
  #ifdef __unix__
    initTerminalInput();
  #endif

  getWChar([] (wchar_t inputWChar)
  {
    return (inputWChar >= L'1' && inputWChar <= L'3');
  });

  return 0;
}
1赞 thomas 1/14/2022 #19

在 WINDOWS 上我这样做:

#include <Windows.h>
int main()
{
    system("PAUSE>NUL");
    if (GetKeyState(0x41/*(the 'A' key, choosen e.g.)*/) & (0xff80/*That stands for "Default state / Key Down"*/)) {
        //whatever I want to do after 'A' is pressed
    }
}

在这里可以找到一个键列表,每个键都有自己的十六进制值:https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes

评论

0赞 Community 1/14/2022
您的答案可以通过其他支持信息进行改进。请编辑以添加更多详细信息,例如引文或文档,以便其他人可以确认您的答案是正确的。您可以在帮助中心找到有关如何写出好答案的更多信息。
1赞 Gabriel Staples 2/4/2022 #20

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

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

若要在 Linux 上执行此操作,可以使用以下 bash 命令

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

有关详细信息,请参阅我的答案:shell 脚本响应按键

因此,要在 Linux 上的 C 或 C++ 中执行此操作,这有点黑客,但您只需要通过管道调用系统调用上述 bash 命令,以便您可以读取 bash 命令的输出。popen()fgets()

下面是一个完整的示例,它在 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

另请参阅:

  1. 为了更进一步,请参阅我在此处的另一个答案,其中我一次检测并解析 3 个字符,以检测箭头键按下 、 、 或 : 读取 C 中的按键,例如箭头键,Enter 键UpDownLeftRight

引用:

  1. 我如何学会从管道读取以获取系统调用输出:
    1. 如何从 C 运行外部程序并解析其输出?
    2. 有没有办法使用 C 程序在 .txt 文件上获取 linux 命令(如 ifconfig)的输出?[复制]
    3. 如何在 C: use 而不是 : popen 隐式声明,即使添加了 #include < stdio.h>popen()-std=gnu17-std=c17
  2. [我的回答]如何在不阻塞的情况下阅读,通过 bash:shell 脚本响应按键
  3. [我的回答]如何在 C 中将 Enter 键作为输入读取?

关于此主题的所有 3 个问答

  1. 从标准输入中捕获字符,无需等待按回车键
  2. C 无阻塞键盘输入
  3. 如何避免按 Enter 和 getchar() 只读取单个字符?
1赞 wark77 2/10/2022 #21

我遇到了同样的问题。这是使用 cygwin g++ 和 .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