• 文章
  • 讓控制檯保持開啟足夠長的時間以檢視
釋出者:
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="http://www.boost.org/LICENSE_1_0.txt">http://www.boost.org/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="http://www.boost.org/LICENSE_1_0.txt">http://www.boost.org/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 

好了,就到這裡吧。要了解更多關於無緩衝(也稱為“原始”)輸入的資訊,請看看讀取單個按鍵作為輸入

好了,今天就到這裡。