如何模拟“按任意键继续”?

105

我想编写一个C++程序,当用户从键盘输入任何字符时,它应该移动到下一行代码。

这是我的代码:

char c;

cin>>c;

cout<<"Something"<<endl;

但是这并不起作用,因为它只有在我输入一些字符然后按下 ENTER 键时才会移到下一行。

或者

如果我使用这个:

cin.get() or cin.get(c)

当我按下 Enter 键时,它会移动到下一条指令。

但是我想让它在键盘上按任意键时都移到下一行,应该如何实现?


据我所知,问题在于你的 shell 正在等待你按下 ENTER 或 EOF,然后会让你的程序处理缓冲区中的任何内容,或者类似这样的情况。也许有更多知识的人可以提供一个真正的解释。但我认为这并不像它一开始看起来那么简单。 - Lucas
7
目前来看,最简单且唯一可移植的方法是将您的提示从“按任意键继续”更改为“按Enter键继续”。 - rob mayoff
@Lucas - 我正在使用带有Xcode的Mac。 - itsaboutcode
@Lucas:问题不在于外壳,而在于程序本身。 - Keith Thompson
@KeithThompson:这是两年多以前的事了,但我想我当时试图表达的观点是输入队列不是由用户进程处理,而是在内核内部处理。 - Lucas
15个回答

79

在Windows上:

system("pause");

并且在 Mac 和 Linux 上:

system("read");

将输出“按任意键继续......”,并等待按下任意键。 我希望这是您所想要的。


9
系统调用外部程序。你无需调用外部程序来等待按键!这就像当你坐在电脑前时打电话给另一个人来开启电脑一样,这是缓慢的解决方案。 - GingerPlusPlus
11
没错,但只有一行而已!有时候为了节省计算机的负担并不值得这么做。 - Elliot Cameron
21
忘掉慢速,它会调用在路径中找到的第一个名为“pause”的程序,并以调用程序的权限级别运行。即使你只是测试自己的代码的学生,这仍然是一个巨大的安全风险。 - Anne Quinn
6
@XDfaceme - "pause"是一个实际的程序,system()会请求Windows来运行它。但是,如果有另一个名为“pause”的程序且Windows先找到了那个程序,它将会运行那个程序。我可能有些夸大其重要性,但仍然更容易使用cin.get()并按回车键来暂停/恢复程序。 - Anne Quinn
2
我不喜欢绝对的说法。一概而论地认为system("pause")是一种安全风险,即使在测试自己的代码时也是不好的,这种说法太过夸张了。cin.get()可能是正确的解决方案,但它并不能解决被问到的问题... cin.get()只有在按下“enter”或“return”后才会响应。问题明确要求对任何按键作出响应。system("pause")恰好可以做到这一点。首先,我只在开发课程中从Visual Studio进行调试时看到过这个命令的用处,所以我认为system("pause")是有效的。 - Gregor
显示剩余4条评论

58

如果你使用的是Windows系统,可以使用kbhit()函数, 这个函数是Microsoft运行时库的一部分。如果你使用的是Linux系统,可以按照以下方式实现kbhit函数(来源):

#include <stdio.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>

int kbhit(void)
{
  struct termios oldt, newt;
  int ch;
  int oldf;

  tcgetattr(STDIN_FILENO, &oldt);
  newt = oldt;
  newt.c_lflag &= ~(ICANON | ECHO);
  tcsetattr(STDIN_FILENO, TCSANOW, &newt);
  oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
  fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

  ch = getchar();

  tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
  fcntl(STDIN_FILENO, F_SETFL, oldf);

  if(ch != EOF)
  {
    ungetc(ch, stdin);
    return 1;
  }

  return 0;
}

更新: 上面的函数在OS X上正常工作(至少在OS X 10.5.8-Leopard上,所以我希望它也能在更高版本的OS X上工作)。这个代码片段可以保存为kbhit.c并在Linux和OS X上编译。

gcc -o kbhit kbhit.c

当使用以下方式运行时

./kbhit

这会提示您按下一个键,并在按下键后退出(不仅限于Enter或可打印的键)。

@Johnsyweb - 请详细说明您所说的“详细规范答案”和“所有问题”。此外,“跨平台”:使用此 kbhit() 实现,您可以在Linux / Unix / OS X / Windows上的C ++程序中拥有相同的功能-您可能指的是哪些其他平台?

@Johnsyweb的进一步更新: C ++应用程序并未在封闭的C ++环境中运行。 C ++之所以成功的一个重要原因就是与C的互操作性。所有主流平台都是使用C接口实现的(即使内部实现是使用C ++),因此您谈到的“遗留”似乎不合适。另外,由于我们正在讨论一个单一的函数,您为什么需要C ++(“带类的C”)?正如我指出的那样,您可以在C ++中编写并轻松访问此功能,而您应用程序的用户不太可能关心您是如何实现它的。


1
很遗憾,处理控制台的标准方法并不存在,你总是不得不依靠一些操作系统的魔法来实现。 - Vargas
1
@Johnsyweb:谢谢,但是你的示例展示了我对C++的沮丧之处。你的kbhit()函数在一行中声明了一个terminal_settings变量,这似乎什么也没做,但它却做了一切。有些人可能认为这很好,但我觉得它太晦涩难懂了。 - Vinay Sajip
1
@VinaySajip:我的实现使用了C++中最重要的习语之一——RAII(Resource Acquisition Is Initialization)。虽然在这个例子中它并不是严格必要的,但我发现当你想保存一些状态并恢复它时,养成这个好习惯非常有益。 - johnsyweb
4
@Johnsyweb:我知道 RAII 及其带来的好处,我自己也是一个老练的 C++ 手。但我的观点仍然存在:声明与它在幕后所做的事情之间没有明显的联系。虽然它可能使用了闪亮的 C++ 习语而不是老式的 C 语言,但我认为它很少有助于阐明正在发生的事情,而且更容易使人困惑。这并不是针对你个人的任何方式 - 它只是关于如何使用 C++ 创建难以理解代码的一种观点。现在我会下台了,说够了。 - Vinay Sajip
2
虽然很好,但仍会捕获先前在stdin上的任何垃圾 :-) - Tiago
显示剩余6条评论

10

没有完全可移植的解决方案。

问题19.1在comp.lang.c FAQ中对此进行了详细说明,并提供了Windows、类Unix系统、甚至MS-DOS和VMS的解决方案。

以下是快速但不完整的摘要:

  • 您可以使用库;调用cbreak(),然后调用getch()(不要与Windows特定的getch()函数混淆)。请注意,通常会控制终端,因此这可能过于复杂。
  • 您可能可以使用ioctl()来操作终端设置。
  • 在符合POSIX标准的系统上,tcgetattr()tcsetattr()可能是更好的解决方案。
  • 在Unix系统上,您可以使用system()调用stty命令。
  • 在MS-DOS上,您可以使用getch()getche()
  • 在VMS(现在称为OpenVMS)上,屏幕管理(SMG$)例程可能起作用。

所有这些C语言解决方案同样适用于C++;我不知道任何专门针对C++的解决方案。


2
所有这些多平台解决方案都需要有人编写一个函数,根据您的平台实现正确的操作,并使用大量的预处理器来确定该平台是什么。 - CashCow
@CashCow "有很多预处理器"?!我的方法通常是一个头文件,然后一堆特定于平台的实现,除了#include之外没有任何预处理器。然后,在构建时只需选择正确的实现文件,就完成了。 - BitTickler

6
在Windows中,这个简短的程序可以实现目标:getch暂停控制台直到按下一个键(https://www.geeksforgeeks.org/difference-getchar-getch-getc-getche/)。
#include<iostream.h>
#include<conio.h>

using namespace std;

void  check()
{
    char chk; int j;
    cout<<"\n\nPress any key to continue...";
    chk=getch();
    j=chk;
    for(int i=1;i<=256;i++)
      if(i==j) break;
    clrscr();
}

void main()
{
    clrscr();
    check();
    cout<<"\n\nIt works!";
    getch();
}

需要注意的是,getch不是标准库的一部分。

1
这是一个非常简单的程序。它非常容易理解。 - BoldX
1
请解释一下getch()cin.getcin>>有何不同,可以附上相关文档链接。 - user2622016
3
conio仅适用于Windows操作系统。 - Andrew Wolfe
1
getch() 的功能很好。然而,它现在已经被弃用了。最好使用 _getch() 代替。 - Nick Deguillaume

6
您可以使用微软特定的函数 _getch 来实现:
#include <iostream>
#include <conio.h>
// ...
// ...
// ...
cout << "Press any key to continue..." << endl;
_getch();
cout << "Something" << endl;

5

我了解你想要实现的内容,因为我记得我也曾经想过做同样的事情。受到Vinay的启发,我写了一些对我有效并且我有点理解的东西。但我不是专家,请小心使用。

我不知道Vinay怎么知道你在使用Mac OS X。但是大多数类Unix操作系统应该可以像这样工作。真正有用的资源是opengroup.org

在使用函数之前,请确保刷新缓冲区。

#include <stdio.h>
#include <termios.h>        //termios, TCSANOW, ECHO, ICANON
#include <unistd.h>     //STDIN_FILENO


void pressKey()
{
    //the struct termios stores all kinds of flags which can manipulate the I/O Interface
    //I have an old one to save the old settings and a new 
    static struct termios oldt, newt;
    printf("Press key to continue....\n");

    //tcgetattr gets the parameters of the current terminal
    //STDIN_FILENO will tell tcgetattr that it should write the settings
    // of stdin to oldt
    tcgetattr( STDIN_FILENO, &oldt);
    //now the settings will be copied 
    newt = oldt;

    //two of the c_lflag will be turned off
    //ECHO which is responsible for displaying the input of the user in the terminal
    //ICANON is the essential one! Normally this takes care that one line at a time will be processed
    //that means it will return if it sees a "\n" or an EOF or an EOL
    newt.c_lflag &= ~(ICANON | ECHO );      

    //Those new settings will be set to STDIN
    //TCSANOW tells tcsetattr to change attributes immediately. 
    tcsetattr( STDIN_FILENO, TCSANOW, &newt);

    //now the char wil be requested
    getchar();

    //the old settings will be written back to STDIN
    tcsetattr( STDIN_FILENO, TCSANOW, &oldt);

}


int main(void)
{
  pressKey();
  printf("END\n");
  return 0;
}

O_NONBLOCK似乎也是一个重要的标志,但对我来说它并没有改变什么。

如果有一些更深入的知识的人能够评论并给出一些建议,我将不胜感激。


应该设置O_NONBLOCK,因为如果没有它,getchar()(调用read())将会阻塞等待输入。kbhit()解决方案可以告诉你是否有按键被按下,而不需要等待;你可以在等待循环或任何其他目的中使用它,因此它是一种更通用的解决方案。没有O_NONBLOCK的解决方案将等待直到按键被按下 - 对于这个问题来说没问题,但不太通用。 - Vinay Sajip

5
为了实现这个功能,您可以使用ncurses库,该库已在Windows和Linux上实现(据我所知也适用于MacOS)。

这是一项任务,我不能使用任何外部库等,所以只能留在“章节上下文”中。 - itsaboutcode
2
那么唯一的选择就是为您计划支持的每个操作系统实现所需的功能。 - Kirill V. Lyadvinsky
1
ncurses 大体上是一个 VT200 客户端。对于简单的从 TTY 中读取来说有点过度杀伤力了。 - greyfade

4

如果你使用的是Visual Studio 2012或更早版本,请使用getch()函数,如果你使用的是Visual Studio 2013或更高版本,请使用_getch()函数。你需要使用#include <conio.h>。示例:

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

int main()
{
   std::cout << "Press any key to continue. . .\n";
   _getch(); //Or getch()
}

3

这适用于Windows平台: 它直接使用微处理器寄存器,可用于检查按键或鼠标按钮。

    #include<stdio.h>
    #include<conio.h>
    #include<dos.h>
    void main()
    {
     clrscr();
     union REGS in,out;
     in.h.ah=0x00;
     printf("Press any key : ");

     int86(0x16,&in,&out);
     printf("Ascii : %d\n",out.h.al);
     char ch = out.h.al;
     printf("Charcter Pressed : %c",&ch);
     printf("Scan code : %d",out.h.ah);
     getch();
    }

2
你可以使用getchar例程。
从上面的链接中可以看到:
/* getchar example : typewriter */
#include <stdio.h>

int main ()
{
  char c;
  puts ("Enter text. Include a dot ('.') in a sentence to exit:");
  do {
    c=getchar();
    putchar (c);
  } while (c != '.');
  return 0;
}

3
如果我理解正确,我认为这不是提问者所要求的。在调用getchar()函数读取缓冲区内容之前,仍需按下ENTER键将输入缓冲区填充。 - Lucas

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