getch和箭头键代码

53

我正在编写一个程序,使用getch()扫描箭头键。到目前为止,我的代码是:

switch(getch()) {
    case 65:    // key up
        break;
    case 66:    // key down
        break;
    case 67:    // key right
        break;
    case 68:    // key left
        break;
}

问题是当我按下 'A''B''C''D' 键时,代码也会执行,因为 65 是十进制代码表示的 'A' 等等...

有没有方法在不调用其他键的情况下检测箭头键?

谢谢!


2
我已经很久没有玩这个了,它根本没有标准化......但是回到我使用 getch() 的时候,对于“特殊”键,它实际上会返回两次。第一次返回0,然后是特殊键的代码,这样你就可以将其与其他键区分开来。 - FatalError
65 只代表字符 A。您必须使用控制码才能接收这些键。请参阅此帖子。https://dev59.com/aHE85IYBdhLWcg3wOw_s - P.P
@FatalError 对不起,我晚了7年,但你说的话引起了我的好奇心。getch()如何可能返回两次?一个函数只能返回一次,对吧? - Anchith Acharya
3
你很幸运,因为我还在7年后 ;-). 我的意思是,要读取“特殊键”,你必须实际调用getch()两次。第一次调用会返回0(即下一个值将是特殊键),然后在第二次调用getch()时,它会返回一个不同的值,表示哪个特殊键被按下。请注意,getch()是DOS时代的非标准工具,因此它可能因编译器而异。但是这里有一个MSDN上的示例:https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/getch-getwch?view=vs-2019 - FatalError
Solution - Scott
显示剩余2条评论
11个回答

90

按下一个箭头键getch会将三个值推入缓冲区:

  • '\033'
  • '['
  • 'A''B''C''D'

所以代码将会像这样:

if (getch() == '\033') { // if the first value is esc
    getch(); // skip the [
    switch(getch()) { // the real value
        case 'A':
            // code for arrow up
            break;
        case 'B':
            // code for arrow down
            break;
        case 'C':
            // code for arrow right
            break;
        case 'D':
            // code for arrow left
            break;
    }
}

5
有没有类似于getch的Unix版本,使得这个答案是正确的?这里的代码是使用getchar时从大多数终端获得的代码,它们与<conio.h>中的getch所获得的不同。 - Cheers and hth. - Alf
1
你知道为什么在我的终端里第二个字符显示的是“O”而不是“[”吗?顺便说一下,我正在tmux会话中运行zsh。谢谢! - Muffo
1
@Cheersandhth.-Alf 是的,这是Unix环境下的 getch 方法,具体来说是 ncurses库中的getch (未启用“键盘”模式)。 - zwol
@Cheersandhth.-Alf,你可以使用ncurses,但如果你不想使用它,那么你必须启用终端原始模式。请查看此链接:https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html - DarthCucumber
Solution - Scott
显示剩余2条评论

25

如FatalError在评论中提到的那样,getch()函数返回箭头键(以及其他一些特殊键)的两个键码。它首先返回0(0x00)或224(0xE0),然后返回一个标识所按下键的代码。

对于箭头键,它首先返回224,接着是72(上),80(下),75(左)和77(右)。如果按下数字键盘箭头键(NumLock关闭),则getch()返回0而不是224。

请注意,getch()没有任何标准化的方式,这些代码可能因编译器而异。这些代码是由Windows上的MinGW和Visual C++返回的。

一个方便的程序来查看各种键的getch()行为:

#include <stdio.h>
#include <conio.h>

int main ()
{
    int ch;

    while ((ch = _getch()) != 27) /* 27 = Esc key */
    {
        printf("%d", ch);
        if (ch == 0 || ch == 224)
            printf (", %d", _getch ()); 
        printf("\n");
    }

    printf("ESC %d\n", ch);

    return (0);
}

这适用于MinGW和Visual C++。 这些编译器使用名称 _getch() 而不是 getch() 来表示它是一个非标准函数。

因此,您可以像这样做:

ch = _getch ();
if (ch == 0 || ch == 224)
{
    switch (_getch ())
    {
        case 72:
            /* Code for up arrow handling */
            break;

        case 80:
            /* Code for down arrow handling */
            break;

        /* ... etc ... */
    }
}

它无法识别 _getch() 函数。 - Scott
@Scott _getch() 是 MSVC 对他们之前的 getch() 的更新,因为它不是标准的 C 函数而被重命名。这个键序列是特定于 Windows 操作系统的。 - Weather Vane
截至2023年,此处为解决方案。 - Scott

7

经过长时间的挣扎,我奇迹般地解决了这个一直令人烦恼的问题!我试图模仿Linux终端,并卡在了保留命令历史记录并能够通过按上下箭头键访问的部分。我发现ncurses库难以理解且学习速度缓慢。

char ch = 0, k = 0;
while(1)
{
  ch = getch();
  if(ch == 27)                  // if ch is the escape sequence with num code 27, k turns 1 to signal the next
    k = 1;
  if(ch == 91 && k == 1)       // if the previous char was 27, and the current 91, k turns 2 for further use
    k = 2;
  if(ch == 65 && k == 2)       // finally, if the last char of the sequence matches, you've got a key !
    printf("You pressed the up arrow key !!\n");
  if(ch == 66 && k == 2)                             
    printf("You pressed the down arrow key !!\n");
  if(ch != 27 && ch != 91)      // if ch isn't either of the two, the key pressed isn't up/down so reset k
    k = 0;
  printf("%c - %d", ch, ch);    // prints out the char and it's int code

这有点大胆,但解释了很多。祝你好运!


3
keypad允许用户终端键盘将功能键解释为单个值(即没有转义序列)。如man手册所述:启用键盘选项可以启用用户终端的小键盘。如果启用(bf为TRUE),则用户可以按下功能键(例如箭头键),并且wgetch返回表示功能键的单个值,例如KEY_LEFT。如果禁用(bf为FALSE),curses不会特殊处理功能键,程序必须自己解释转义序列。如果终端上的小键盘可以打开(传输)和关闭(本地工作),则打开此选项会在调用wgetch时打开终端小键盘。键盘的默认值是false。

2
实际上,要读取箭头键,需要读取其扫描码。以下是箭头键按下时(而不是松开)产生的扫描码:
当数字锁定关闭时:
  • 左箭头 E0 4B
  • 右箭头 E0 4D
  • 上箭头 E0 48
  • 下箭头 E0 50
当数字锁定打开时,这些键会先出现 E0 2A
  • Byte E0 is -32
  • Byte 48 is 72 UP
  • Byte 50 is 80 DOWN

    user_var=getch();
    if(user_var == -32)
    {
        user_var=getch();
        switch(user_var)
        {
        case 72:
            cur_sel--;
            if (cur_sel==0)
                cur_sel=4;
            break;
        case 80:
            cur_sel++;
            if(cur_sel==5)
                cur_sel=1;
            break;
    
        }
    }
    
在上面的代码中,我假设程序员只想移动4行。

请您能否解释一下您的答案? - MD XF
MS VC的getch()函数返回一个int类型的值,该值将为224而不是-32。如果使用错误的变量类型,则可能会得到-32 - Weather Vane

2

试试这个怎么样?

void CheckKey(void) {
int key;
if (kbhit()) {
    key=getch();
    if (key == 224) {
        do {
            key=getch();
        } while(key==224);
        switch (key) {
            case 72:
                printf("up");
                break;
            case 75:
                printf("left");
                break;
            case 77:
                printf("right");
                break;
            case 80:
                printf("down");
                break;
        }
    }
    printf("%d\n",key);
}

int main() {
    while (1) {
        if (kbhit()) {
            CheckKey();
        }
    }
}

(如果您不理解为什么会出现224,请尝试运行以下代码:)
#include <stdio.h>
#include <conio.h>

int main() {
    while (1) {
        if (kbhit()) {
            printf("%d\n",getch());
        }
    }
}

但我不知道为什么是224。如果您知道原因,请在评论中写下来。

2
请对您的代码进行一些重新格式化。此外,请添加关于代码如何运行的解释,特别是像“224”这样的魔数的解释。同时也请解释为什么要这样做。 - lolbas

1

0
    void input_from_key_board(int &ri, int &ci)
{
    char ch = 'x';
    if (_kbhit())
    {
        ch = _getch();
        if (ch == -32)
        {
            ch = _getch();
            switch (ch)
            {
            case 72: { ri--; break; }
            case 80: { ri++; break; }
            case 77: { ci++; break; }
            case 75: { ci--; break; }

            }
        }
        else if (ch == '\r'){ gotoRowCol(ri++, ci -= ci); }
        else if (ch == '\t'){ gotoRowCol(ri, ci += 5); }
        else if (ch == 27) { system("ipconfig"); }
        else if (ch == 8){ cout << " "; gotoRowCol(ri, --ci); if (ci <= 0)gotoRowCol(ri--, ci); }
        else { cout << ch; gotoRowCol(ri, ci++); }
        gotoRowCol(ri, ci);
    }
}

C++不同于C。 - Anic17

0
我只是一个新手,但是我创建了一个字符变量(例如“b”),并使用conio.h库的命令_getch()来给它赋值,然后检查它。
If (b == -32)
b = _getch();

同时检查按键(72 上,80 下,77 右,75 左)


0
我使用getch编写了一个函数来获取箭头代码。这是一种快速且不太优美的解决方案,但该函数将根据箭头键返回ASCII代码: UP:-10 DOWN:-11 RIGHT:-12 LEFT:-13
此外,使用此函数,您将能够区分ESC键和箭头键。但是,您必须按两次ESC才能激活ESC键。
以下是代码:
char getch_hotkey_upgrade(void)
{
  char ch = 0,ch_test[3] = {0,0,0};

    ch_test[0]=getch();

    if(ch_test[0] == 27)
    {
        ch_test[1]=getch();

        if (ch_test[1]== 91)
        {
            ch_test[2]=getch();

            switch(ch_test[2])
            {
            case 'A':
                //printf("You pressed the up arrow key !!\n");
                //ch = -10;
                ch = -10;
                break;
            case 'B':
                //printf("You pressed the down arrow key !!\n");
                ch = -11;
                break;
            case 'C':
                //printf("You pressed the right arrow key !!\n");
                ch = -12;
                break;
            case 'D':
                //printf("You pressed the left arrow key !!\n");
                ch = -13;
                break;
            }
        }
        else
         ch = ch_test [1];
    }
    else
        ch = ch_test [0];
  return ch;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接