C++ 检测用户按下箭头键时的方法

27

我在我的 C++ 控制台应用程序中检测箭头键时遇到了问题。我已经尝试了我所能找到的所有方法,包括这里和其他教程网站上的方法,但每当我按下箭头键时都得到相同的结果:

Process returned 0 <0x0> execution time : 2.249 s
Press any key to continue.

以下是我尝试过的所有检测按键的方法,最终结果都相同。这是我的代码中剩下的两种方法,其余的我已经删除而非注释掉。

方法一:

c1 = getch();
if(c1 == 0)
{

    c2 = getch();

    if(c2 == 72) {cout << endl << "Up Arrow" << endl;}
    else if(c2 == 80) {cout << endl << "Down Arrow" << endl;}
    else{cout << endl << "Incorrect Input" << endl;}

}

第二种方法:

switch(getch()) {
case 65:
       cout << endl << "Up" << endl;//key up
    break;
case 66:
    cout << endl << "Down" << endl;   // key down
    break;
case 67:
    cout << endl << "Right" << endl;  // key right
    break;
case 68:
    cout << endl << "Left" << endl;  // key left
    break;
}

我的代码中是否有错误导致我回到了主方法,或者它跳过了某些代码?有没有更快的方法来解决这个问题?我几乎可以确定我的其他代码与这个问题无关,因为我将代码与程序的任何其他方面隔离开来,并且我一直遇到同样的问题。

再次说明,我尝试了所有我能找到的获取箭头键按下的方法,但我仍然遇到了同样的问题。如果有影响的话,我使用的是Windows 8三星ATIV Smart PC并使用键盘底座。

提前感谢任何帮助。


你从哪里得到这些字符代码的?它们只是大写字母,例如在第一个示例中尝试按下H,在第二个示例中尝试按下A,看看会发生什么。 - PeterJ
@PeterJ,这是“getch”的一个好处。不过,“ReadConsoleInput”可能是更合适的替代方案。 - chris
@chris,我在哪里可以找到一个好的例子? - SplatFace Development
这个怎么样?(http://stackoverflow.com/questions/24274310/why-does-switch-always-run-default-with-break-included/24274588#24274588) - chris
请参考以下链接中关于getch()和箭头键代码的内容:https://dev59.com/P2kv5IYBdhLWcg3wpCMz - James
7个回答

28
#include <conio.h>
#include <iostream>
using namespace std;

#define KEY_UP 72
#define KEY_DOWN 80
#define KEY_LEFT 75
#define KEY_RIGHT 77

int main()
{
    int c = 0;
    while(1)
    {
        c = 0;

        switch((c=getch())) {
        case KEY_UP:
            cout << endl << "Up" << endl;//key up
            break;
        case KEY_DOWN:
            cout << endl << "Down" << endl;   // key down
            break;
        case KEY_LEFT:
            cout << endl << "Left" << endl;  // key left
            break;
        case KEY_RIGHT:
            cout << endl << "Right" << endl;  // key right
            break;
        default:
            cout << endl << "null" << endl;  // not arrow
            break;
        }

    }

    return 0;
}

输出结果如下:

Up

Down

Right

Left

Up

Left

Right

Right

Up

检测到箭头键按下!


非常感谢!我测试了你的代码,它完美地运行了(除了一个缺失的花括号),并给了我一个可用的结果!现在我将把它放入我的代码中,看看它是否会继续工作。 - SplatFace Development
1
Visual Studio 2013 告诉我要使用 _getch(),因为 getch() 已经被弃用。"警告 C4996: 'getch': 此项的 POSIX 名称已被弃用。请改用符合 ISO C++ 标准的名称:_getch。" - aj.toulan
不同的语言环境怎么样?这对于使用中文键盘布局的键盘也适用吗? - normanius
1
arbboter的答案为您指明了正确的方向,但未考虑箭头键的扫描码实际上返回两个值。您可以查看我的答案或以下三个答案中的任何一个以获取更多信息:https://dev59.com/P2kv5IYBdhLWcg3wpCMz#10473315,https://dev59.com/aHE85IYBdhLWcg3wOw_s#2877857,https://dev59.com/aHE85IYBdhLWcg3wOw_s#16510089 - Erik Anderson
不要使用conio.h,它是非标准的、非常古老的头文件,在现代编译器/IDE中不被支持。 - Haseeb Mir

15

arbboter先前的答案接近,但忽略了箭头键(以及其他特殊键)返回两个字符的扫描码这一事实。第一个字符是(0)或(224),表示该键是扩展键;第二个包含扫描码的值。

如果不考虑这一点,“H”、“K”、“M”和“P”的ASCII值会被误解为“上”、“下”、“左”和“右”。

这是arbboter代码的修改版本,演示了在按下箭头键时读取扩展值:

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

#define KEY_UP    72
#define KEY_LEFT  75
#define KEY_RIGHT 77
#define KEY_DOWN  80

int main()
{
    int c, ex;

    while(1)
    {
        c = getch();

        if (c && c != 224)
        {
            cout << endl << "Not arrow: " << (char) c << endl;
        }
        else
        {
            switch(ex = getch())
            {
                case KEY_UP     /* H */:
                    cout << endl << "Up" << endl;//key up
                    break;
                case KEY_DOWN   /* K */:
                    cout << endl << "Down" << endl;   // key down
                    break;
                case KEY_LEFT   /* M */:
                    cout << endl << "Left" << endl;  // key left
                    break;
                case KEY_RIGHT: /* P */
                    cout << endl << "Right" << endl;  // key right
                    break;
                default:
                    cout << endl << (char) ex << endl;  // not arrow
                    break;
            }
        }
    }

    return 0;
}

11
// Example for inputting a single keystroke in C++ on Linux
// by Adam Pierce <adam@doctort.org> on http://www.doctort.org/adam/nerd-notes/reading-single-keystroke-on-linux.html
// This code is freeware. You are free to copy and modify it any way you like.
// Modify by me Putra Kusaeri


#include <iostream>
#include <termios.h>
#define STDIN_FILENO 0
using namespace std;
int main()
{
// Black magic to prevent Linux from buffering keystrokes.
    struct termios t;
    tcgetattr(STDIN_FILENO, &t);
    t.c_lflag &= ~ICANON;
    tcsetattr(STDIN_FILENO, TCSANOW, &t);

// Once the buffering is turned off, the rest is simple.
    cout << "Enter a character: ";
    char c,d,e;
    cin >> c;
    cin >> d;
    cin >> e;
    cout << "\nYour character was ";
// Using 3 char type, Cause up down right left consist with 3 character
    if ((c==27)&&(d==91)) {
        if (e==65) { cout << "UP";}
        if (e==66) { cout << "DOWN";}
        if (e==67) { cout << "RIGHT";}
        if (e==68) { cout << "LEFT";}
    }
    return 0;
}

reference


4
回答问题时最好能解释一下为什么你的答案是正确的。参考网址:http://stackoverflow.com/help/how-to-answer - Stephen Rauch
1
if语句中有一个拼写错误,应该是:if ((c==27)&&(d==91)) { 即应该是比较运算符 **==**,而不是赋值运算符 **=**。(我知道这是一个旧答案。但是对于任何遇到此问题的人,请注意在使用之前进行更正) - cbandera

5

这里有一种不使用getch()而使用事件的替代方法(进行了良好的注释,我尽力使其尽可能简单易懂)

#include <iostream>
#include <Windows.h>

int main(int argc, char *argv[]){

    HANDLE rhnd = GetStdHandle(STD_INPUT_HANDLE);  // handle to read console

    DWORD Events = 0;     // Event count
    DWORD EventsRead = 0; // Events read from console

    bool Running = true;

    //programs main loop
    while(Running) {

        // gets the systems current "event" count
        GetNumberOfConsoleInputEvents(rhnd, &Events);

        if(Events != 0){ // if something happened we will handle the events we want

            // create event buffer the size of how many Events
            INPUT_RECORD eventBuffer[Events];

            // fills the event buffer with the events and saves count in EventsRead
            ReadConsoleInput(rhnd, eventBuffer, Events, &EventsRead);

            // loop through the event buffer using the saved count
            for(DWORD i = 0; i < EventsRead; ++i){

                // check if event[i] is a key event && if so is a press not a release
                if(eventBuffer[i].EventType == KEY_EVENT && eventBuffer[i].Event.KeyEvent.bKeyDown){

                    // check if the key press was an arrow key
                    switch(eventBuffer[i].Event.KeyEvent.wVirtualKeyCode){
                        case VK_LEFT:
                        case VK_RIGHT:
                        case VK_UP:
                        case VK_DOWN:   // if any arrow key was pressed break here
                            std::cout<< "arrow key pressed.\n";
                            break;

                        case VK_ESCAPE: // if escape key was pressed end program loop
                            std::cout<< "escape key pressed.\n";
                            Running = false;
                            break;

                        default:        // no handled cases where pressed 
                            std::cout<< "key not handled pressed.\n";
                            break;
                    }
                }

            } // end EventsRead loop

        }

    } // end program loop

    return 0;
}

感谢评论者的指出,我现在知道这段代码不是标准的,但如果您使用g++编译,它仍然可以工作。更多信息请见评论区。


1
这段代码会在 INPUT_RECORD eventBuffer[Events]; 处失败,因为 Events 是非常量。但是你可以逐个读取事件。 - vladon
我不知道Events必须是const。我只是用g++编译它,它看起来像我想要的那样运行...如果这是错误的代码,我会尝试修复它或者(很可能)删除答案,但在此之前,我想知道这段代码应该如何失败,以便我可以自己尝试看到它。 - James
动态数组是 g++ 的扩展,而不是标准的 C++。 - vladon
啊,我明白了,谢谢提供信息。我想我会进行修改。 - James

4

这里提供的一些答案没有考虑到按下箭头键时会接收到2个字符。此外,需要注意输入字符应该是无符号字符。这是因为我们使用ASCII值224来确定是否按下箭头键,这个值只能存储在8位字符(无符号字符)中,而不是7位有符号字符。

您可以使用下面的代码片段。这里处理了2种类型的输入。ch1是用户输入的第一个字符。这是用户输入的内容。但是,在按下箭头键的情况下,会接收到2个字符的序列ch1和ch2。ch1用于标识用户按下了某个箭头键,ch2用于确定具体按下了哪个箭头键。

const int KEY_ARROW_CHAR1 = 224;
const int KEY_ARROW_UP = 72;
const int KEY_ARROW_DOWN = 80;
const int KEY_ARROW_LEFT = 75;
const int KEY_ARROW_RIGHT = 77;

unsigned char ch1 = _getch();
if (ch1 == KEY_ARROW_CHAR1)
{
    // Some Arrow key was pressed, determine which?
    unsigned char ch2 = _getch();
    switch (ch2) 
    {
    case KEY_ARROW_UP:
        // code for arrow up
        cout << "KEY_ARROW_UP" << endl;
        break;
    case KEY_ARROW_DOWN:
        // code for arrow down
        cout << "KEY_ARROW_DOWN" << endl;
        break;
    case KEY_ARROW_LEFT:
        // code for arrow right
        cout << "KEY_ARROW_LEFT" << endl;
        break;
    case KEY_ARROW_RIGHT:
        // code for arrow left
        cout << "KEY_ARROW_RIGHT" << endl;
        break;
    }
}
else
{
    switch (ch1)
    {
        // Process other key presses if required.
    }
}

2

请查看http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspxhttp://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx

#include<windows.h>
#include <stdio.h>

int main()
{
    HANDLE hInput = GetStdHandle(STD_INPUT_HANDLE);
    DWORD NumInputs = 0;
    DWORD InputsRead = 0;
    bool running = true;

    INPUT_RECORD irInput;

    GetNumberOfConsoleInputEvents(hInput, &NumInputs);

    ReadConsoleInput(hInput, &irInput, 1, &InputsRead);

    switch(irInput.Event.KeyEvent.wVirtualKeyCode)
    {
        case VK_ESCAPE:
        puts("Escape");
        break;

        case VK_LEFT:
        puts("Left");
        break;

        case VK_UP:
        puts("Up");
        break;

        case VK_RIGHT:
        puts("Right");
        break;

        case VK_DOWN:
        puts("Down");
        break;
    } 

}

这个解决方案给了我同样的问题,我把代码放到一个新的项目文件中,它就直接到了“进程返回到0”。在这个例子中,是什么阻止它直接到达“返回0”? - SplatFace Development

1
#include <iostream>
#include <conio.h>

const int KB_UP = 72;
const int KB_DOWN = 80;
const int KB_RIGHT = 77;
const int KB_LEFT = 75;
const int ESC = 27;

int main() {
    
    while (true) {
        int ch = _getch();
        if (ch == 224) {
            ch = _getch();
            switch (ch) {
            case KB_UP: 
                std::cout << "up\n";   
                break;
            case KB_DOWN:
                std::cout << "down\n";
                break;
            case KB_RIGHT:
                std::cout << "right\n";
                break;
            case KB_LEFT:
                std::cout << "left\n";
                break;
            default: std::cout << "unknown\n";
            }
        }
        else if (ch == ESC)
        {
            std::cout << "Escape pressed, going out!\n";
            break;
        }
    }
}

这与上面的示例非常相似,只是我使用了_getchar()而不是getchar(),因为Visual Studio编译器对相反的操作给出了错误。
我还将它放在一个循环中,直到按下Escape键。

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