我从来都不记得我是怎么做到的,因为它很少出现在我身上。但是在C或c++中,从标准输入中读取字符而不等待换行符(按enter)的最佳方法是什么?
理想情况下,它也不会将输入字符回显到屏幕上。我只是想在不影响控制台屏幕的情况下捕捉击键。
我从来都不记得我是怎么做到的,因为它很少出现在我身上。但是在C或c++中,从标准输入中读取字符而不等待换行符(按enter)的最佳方法是什么?
理想情况下,它也不会将输入字符回显到屏幕上。我只是想在不影响控制台屏幕的情况下捕捉击键。
当前回答
C和c++对I/O有一个非常抽象的看法,没有标准的方法来做你想做的事情。有从标准输入流中获取字符的标准方法(如果有的话),这两种语言都没有定义其他任何方法。因此,任何答案都必须是特定于平台的,可能不仅取决于操作系统,还取决于软件框架。
这里有一些合理的猜测,但如果不知道目标环境是什么,就无法回答您的问题。
其他回答
下面是一个不向系统提供外壳的版本(在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 );
}
以下是一个从Expert C Programming: Deep Secrets中提取的解决方案,它应该可以在SVr4上工作。它使用stty和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");
}
在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
ssinfod对Linux的回答的变体,对我的口味来说稍微干净一些,为wcout和wchar_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;
}
由于这里之前的解决方案不能跨平台工作,并且在特殊键上存在问题,所以这里是我的解决方案,它在Windows和Linux上都可以工作,并且使用了最少的外部库(Windows.h和Linux的sys/ioctl.h+termios.h)。
对于ASCII字符(换行符/tab/space/backspace/delete, !"#$%&'()*+,-./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;
}