• 文章
  • 让控制台保持打开足够长的时间以查看
发布者:
2009年1月28日(最后更新:2009年1月31日)

让控制台保持打开足够长的时间以查看程序的输出

评分:4.0/5(103票)
*****
按任意键继续 . . .
这通常是Windows上的一个问题,原因是有些非常糟糕的IDE不知道如何确保程序完成后控制台保持打开。然而,这确实触及了您思考编程方式的一个主要哲学问题。毕竟,控制台程序应该从控制台运行——否则一旦它终止,控制台就应该消失。我们稍后会解决这个问题。目前,我们先专注于眼前的问题。


- - - - - - - - - - - - - - - - 简单答案 - - - - - - - - - - - - - - - -
虽然简单,但这确实是一件坏事。请参阅为什么system()很糟糕了解更多。

1
2
3
4
5
6
7
#ifdef __cplusplus__
  #include <cstdlib>
#else
  #include <stdlib.h>
#endif

system("PAUSE");

它也仅限于Windows。要在Linux上执行相同操作,您需要使用read shell命令
1
2

system("read -p \"Press a key to continue...\" -n 1 -s");



- - - - - - - - - - - - - - - - 标准方法 - - - - - - - - - - - - - - - -
最正确的方法是这样的
1
2
3
4
5
6
7
8
#include <iostream>
#include <limits>

void PressEnterToContinue()
  {
  std::cout << "Press ENTER to continue... " << flush;
  std::cin.ignore( std::numeric_limits <std::streamsize> ::max(), '\n' );
  }
1
2
3
4
5
6
7
8
9
#include <stdio.h>

void PressEnterToContinue()
  {
  int c;
  printf( "Press ENTER to continue... " );
  fflush( stdout );
  do c = getchar(); while ((c != '\n') && (c != EOF));
  }

之后,只需在程序末尾调用它即可
1
2
3
4
5
6
7
8
#include <stdio.h>

int main()
  {
  puts( "Hello world!" );
  PressEnterToContinue();
  return 0;
  }

这种方法的主要问题在于它仅在输入正确同步时才有效。如果不同步,您将需要刷新标准输入。有关正确同步的更多信息,请参阅读取用户输入时出现的问题

此外,它要求用户按下回车键继续。这可能不像您想的那么糟糕。神秘的任意键让很多人感到困惑。(一个更好的说法应该是“按任意键继续……”——最坏的情况下,人们会按下A键。)请记住,非程序员几乎总是您的目标受众。即使不是,您想到的内容通常也不像您想象的那么明显。请确保向用户提供关于您的程序接下来需要何种输入的清晰指示。A键。)请记住,非程序员几乎总是您的目标受众。即使不是,您想到的内容通常也不像您想象的那么明显。请确保向用户提供关于您的程序接下来需要何种输入的清晰指示。


- - - - - - - - - - - - - - - - 使用NCurses - - - - - - - - - - - - - - - -
Curses库是为处理控制台而设计的。优点:它是跨平台的。缺点:它与标准流的交互不太好。换句话说,您不应该混合使用printf()等或cout等与curses。二者选一,不要混用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <curses.h>

int main()
  {
  initscr();

  addstr( "Hello world!\n\n" );

  addstr( "\nPress a key to continue..." );
  cbreak();    /* turn off line-buffering */
  noecho();    /* turn off character echo */

  getch();     /* read and discard a single character (caveats!) */

  echo();      /* turn echo back on */
  nocbreak();  /* turn line-buffering back on */

  endwin();
  return 0;
  }
在使用[w]getch()时,还有一些其他事项需要注意。请务必阅读开始使用Curses

因此,如果您的所有需求只是暂停程序一两次,那么使用Curses就有点大材小用了。


- - - - - - - - - - - - - - - - 使用<conio.h> - - - - - - - - - - - - - - - -
首先,是一些代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <conio.h>
#include <stdio.h>

void PressAKeyToContinue()
  {
  int c;
  printf( "\nPress a key to continue..." );
  c = getch();
  if (c == 0 || c == 224) getch();
  }

int main()
  {
  puts( "Hello world!" );
  PressAKeyToContinue();
  return 0;
  }
该库已严重弃用,但它非常流行,以至于在大多数80x86硬件编译器上都存在某种形式的它——几乎总是在Windows编译器上,并且也存在Linux版本。然而,如果可以的话,请使用NCurses……

需要注意的是,它是非标准的,这意味着它提供的实际函数变化很大,而且它们的行为并不总是恰到好处。(微软很多年前就发明了它。没错,不是Borland——尽管Borland因此[臭名昭著])。

因此,对于Windows程序以外的任何程序,它也是一个次优的解决方案。请参阅使用<conio.h>了解更多。

- - - - - - - - - - - - - - - - 特定于操作系统的实现方式 - - - - - - - - - -
要做得更好,您需要做一些特定于操作系统的操作。C,特别是C++的设计理念之一是,操作系统不存在(或者如果存在,它是一个神奇的黑盒子)。因此,要做一些与控制台相关的特殊操作(记住,C和C++不知道什么是控制台)意味着您将不得不编写与您的操作系统相关的特殊代码。

这是一个真正允许您按任意键并返回被按下键的函数。Windows提供了一些不错的、简单的函数来处理控制台。在Linux上需要更多的“魔法”,但几乎和它一样简单。

Windows
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/* ---------------------------------------------------------------------------
 * PressAnyKey()
 * ---------------------------------------------------------------------------
 * Copyright 2008 Michael Thomas Greer
 * <a href="https://boost.ac.cn/LICENSE_1_0.txt">https://boost.ac.cn/LICENSE_1_0.txt</a>
 *
 * function
 *   Optionally print a message and and wait for the user to press (and
 *   release) a single key.
 *
 * arguments
 *   The message to print. If NULL, uses a default message. Specify the empty
 *   string "" to not print anything.
 *
 * returns
 *   The virtual keycode for the key that was pressed.
 *
 *   Windows #defines virtual keycode values like
 *     VK_UP
 *     VK_DOWN
 *     VK_RIGHT
 *     VK_LEFT
 *   which you can use to identify special keys.
 *
 *   Letter keys are simply the upper-case ASCII value for that letter.
 */
#include <windows.h>

int PressAnyKey( const char *prompt )
  {
  DWORD        mode;
  HANDLE       hstdin;
  INPUT_RECORD inrec;
  DWORD        count;
  char         default_prompt[] = "Press a key to continue...";

  /* Set the console mode to no-echo, raw input, */
  /* and no window or mouse events.              */
  hstdin = GetStdHandle( STD_INPUT_HANDLE );
  if (hstdin == INVALID_HANDLE_VALUE
  || !GetConsoleMode( hstdin, &mode )
  || !SetConsoleMode( hstdin, 0 ))
    return 0;

  if (!prompt) prompt = default_prompt;

  /* Instruct the user */
  WriteConsole(
    GetStdHandle( STD_OUTPUT_HANDLE ),
    prompt,
    lstrlen( prompt ),
    &count,
    NULL
    );

  FlushConsoleInputBuffer( hstdin );

  /* Get a single key RELEASE */
  do ReadConsoleInput( hstdin, &inrec, 1, &count );
  while ((inrec.EventType != KEY_EVENT) || inrec.Event.KeyEvent.bKeyDown);

  /* Restore the original console mode */
  SetConsoleMode( hstdin, mode );

  return inrec.Event.KeyEvent.wVirtualKeyCode;
  }

POSIX(Unix、Linux、Mac OSX等)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/* ---------------------------------------------------------------------------
 * PressAnyKey()
 * ---------------------------------------------------------------------------
 * Copyright 2008 Michael Thomas Greer
 * <a href="https://boost.ac.cn/LICENSE_1_0.txt">https://boost.ac.cn/LICENSE_1_0.txt</a>
 *
 * function
 *   Optionally print a message and and wait for the user to press (and
 *   release) a single key.
 *
 * arguments
 *   The message to print. If NULL, uses a default message. Specify the empty
 *   string "" to not print anything.
 *
 * returns
 *   The keycode for the key that was pressed.
 *
 *   Extended key codes (like arrow keys) are properly handled, but their
 *   keycode is not understood; they are simply returned as the last code in
 *   the sequence, negated. For example, it is likely that the arrow keys are:
 *
 *     UP_ARROW    = -'A' = -65
 *     DOWN_ARROW  = -'B' = -66
 *     RIGHT_ARROW = -'C' = -67
 *     LEFT_ARROW  = -'D' = -68
 *
 *   Exactly identifying the values for these keys requires a foray into the
 *   terminfo database, which is a subject for later. For now we'll leave it
 *   at this.
 */
#include <stdio.h>
#include <unistd.h>
#include <termios.h>

int PressAnyKey( const char* prompt )
  {
  #define MAGIC_MAX_CHARS 18
  struct termios initial_settings;
  struct termios settings;
  unsigned char  keycodes[ MAGIC_MAX_CHARS ];
  int            count;

  tcgetattr( STDIN_FILENO, &initial_settings );
  settings = initial_settings;

  /* Set the console mode to no-echo, raw input. */
  /* The exact meaning of all this jazz will be discussed later. */
  settings.c_cc[ VTIME ] = 1;
  settings.c_cc[ VMIN  ] = MAGIC_MAX_CHARS;
  settings.c_iflag &= ~(IXOFF);
  settings.c_lflag &= ~(ECHO | ICANON);
  tcsetattr( STDIN_FILENO, TCSANOW, &settings );

  printf( "%s", prompt ? prompt : "Press a key to continue..." );
  count = read( stdin, (void*)keycodes, MAGIC_MAX_CHARS );

  tcsetattr( STDIN_FILENO, TCSANOW, &initial_settings );

  return (count == 1)
       ? keycodes[ 0 ]
       : -(int)(keycodes[ count -1 ]);
  }

如果您正在使用C++,您可以这样增加一些便利性
1
2
3
4
5
6
7
#if defined(__cplusplus)
  #include <string>
  inline int PressAnyKey( const std::string& prompt = "Press a key to continue..." )
    {
    return PressAnyKey( prompt.c_str() );
    }
#endif 

好了,就到这里吧。要了解更多关于无缓冲(也称为“原始”)输入的信息,请看看读取单个按键作为输入

好了,今天就到这里。