釋出者:
2009年4月29日 (最後更新: 2011年11月27日)

清屏

評分: 4.1/5 (868票)
*****

目錄


引言

這篇短文介紹了清除控制檯顯示的所有文字並將文字游標定位到主位置(左上角)的方法。

在輕易進行此類操作之前,請確保您已閱讀並理解了控制檯應用程式的型別和用途(以及為什麼這很重要)。

在整篇文章中,程式碼片段都不會假定為 C 或 C++,因此 #include 部分將根據您使用的語言,用適當的 #ifdef 測試括起來。如果您知道自己只使用一種語言,則可以刪除所有內容,只保留正確的 #includes。

如果您不知道這意味著什麼,不用擔心。


與作業系統無關的方法


以下方法通常受到各種平臺的廣泛支援,但它們在功能或效用或兩者兼顧方面存在重大權衡。

簡單的答案

雖然簡單,但它確實是一件壞事。有關更多資訊,請參閱為什麼 system() 是邪惡的

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

if (system("CLS")) system("clear");


標準方法

這種方法很糟糕,但它能完成工作,而且通常是正確的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifdef __cplusplus__

  #include <iostream>
  #include <string>

  void ClearScreen()
    {
    cout << string( 100, '\n' );
    }

#else

  #include <stdio.h>

  void ClearScreen()
    {
    int n;
    for (n = 0; n < 10; n++)
      printf( "\n\n\n\n\n\n\n\n\n\n" );
    }

#endif 

這當然是透過在顯示器上列印一百個換行符來實現的。透過一個緩衝不良的網路連線,這可能會很。唉。

使用 Curses

Curses 庫專為處理控制檯而設計。優點:它是跨平臺的。缺點:它與標準流互動不佳。換句話說,您不應該將 printf() 等函式或 cout 等函式與 Curses 混合使用。請使用標準 I/O 或 Curses,但不要同時使用。(當然,您仍然可以對終端以外的事物使用標準 I/O。)
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
#include <curses.h>

int main()
  {
  char users_name[ 100 ];

  initscr();
  (void)echo();

  addstr( "What is your name> " );
  refresh();
  getnstr( users_name, sizeof( users_name ) - 1 );

  /* Here is where we clear the screen.                  */
  /* (Remember, when using Curses, no change will appear */
  /* on the screen until <b>refresh</b>() is called.     */
  clear();

  printw( "Greetings and salutations %s!\n", users_name );
  refresh();

  printw( "\n\n\nPress ENTER to quit." );
  refresh();
  getnstr( users_name, sizeof( users_name ) - 1 );

  endwin();
  return 0;
  }  

同樣,如果您只想偶爾清屏,那麼使用 Curses 就過於殺雞用牛刀了。(如果您確實使用 Curses,請參閱NCurses 以獲取 Unix 和 Linux 以及其他 POSIX 系統,以及PDCurses 以獲取 DOS、Windows、OS/2 和其他一些隨機系統。)

使用 <conio.h>

該庫已嚴重棄用,但由於(歇斯底里原因)如此受歡迎,大多數 80x86 硬體編譯器都支援某種形式的它——幾乎所有 Windows 編譯器都有,並且也存在 Linux 版本。但是,如果可以的話,請改用Curses...

需要注意的是,它是非標準的,這意味著它提供的實際函式差異很大,而且它們的行為並不總是恰到好處。因此,對於 Windows 程式以外的任何程式,它也是一個次優的解決方案。(請參閱維基百科的 Conio 文章以獲得對其侷限性的非常簡潔的描述。)

如果您毫不畏懼,那麼這裡有一些程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <conio.h>
#include <stdio.h>
#include <string.h>

int main()
  {
  char users_name[ 100 ];

  printf( "What is your name> " );
  fgets( users_name, sizeof( users_name ), stdin );
  *strchr( users_name, '\n' ) = '\0';

  /* Here is where we clear the screen */
  clrscr();

  printf( "Greetings and salutations %s!\n", users_name );

  printf( "\n\n\nPress ENTER to quit." );
  fgets( users_name, sizeof( users_name ), stdin );

  return 0;
  }

現在,有進取心的您可能已經嘗試過編譯它。如果對您有效,那您就很幸運了。如果無效,那麼您將親身體驗到 <conio.h> 庫的不足之處。唉。


與作業系統相關的方法


那麼,到了我們這些有駭客精神的人的部分了:我們想以正確的方式去做。

Windows API

Windows 控制檯有一個特定大小的單元資料緩衝區,其組織方式與舊的 EGA/VGA/HGC 卡完全相同,但具有使用者指定的尺寸:每個“單元”包含屬性資訊(顏色)和字元程式碼(為簡單起見,您可以將其視為 ASCII 程式碼——其實際含義取決於當前的內碼表)。因此,清屏是一種簡單的方法,即將當前的字元屬性和一個空格字元寫入螢幕上的每個單元,然後將游標定位到 (0,0)。

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
#include <windows.h>

void ClearScreen()
  {
  HANDLE                     hStdOut;
  CONSOLE_SCREEN_BUFFER_INFO csbi;
  DWORD                      count;
  DWORD                      cellCount;
  COORD                      homeCoords = { 0, 0 };

  hStdOut = GetStdHandle( STD_OUTPUT_HANDLE );
  if (hStdOut == INVALID_HANDLE_VALUE) return;

  /* Get the number of cells in the current buffer */
  if (!GetConsoleScreenBufferInfo( hStdOut, &csbi )) return;
  cellCount = csbi.dwSize.X *csbi.dwSize.Y;

  /* Fill the entire buffer with spaces */
  if (!FillConsoleOutputCharacter(
    hStdOut,
    (TCHAR) ' ',
    cellCount,
    homeCoords,
    &count
    )) return;

  /* Fill the entire buffer with the current colors and attributes */
  if (!FillConsoleOutputAttribute(
    hStdOut,
    csbi.wAttributes,
    cellCount,
    homeCoords,
    &count
    )) return;

  /* Move the cursor home */
  SetConsoleCursorPosition( hStdOut, homeCoords );
  }

POSIX (Unix, Linux, Mac OSX 等)

Unix 系統沒那麼簡單。雖然 PC 硬體遵循非常嚴格的標準,但 Unix 系統處理的硬體種類卻不止一種。(實際上是數百種。)為了簡化為所有這些不同型別的終端編寫程式的難度,一位名叫 Bill Joy 的先生編寫了termcap 庫,該庫早已被terminfo 庫所取代,terminfo 庫最初由 Mark Horton 程式設計,並由 Eric S. Raymond 大量更新和維護。

terminfo 資料庫和庫使得查詢和使用高階終端功能相對容易。當然,需要注意的是,您可能會遇到不支援所需功能(如“清除並歸位”)的舊系統。(幸運的是,絕大多數現代終端都支援。)

幸運的是,由於終端可以處理這些事情,因此生成的程式碼比 Windows 版本要短得多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <unistd.h>
#include <term.h>

void ClearScreen()
  {
  if (!cur_term)
    {
    int result;
    setupterm( NULL, STDOUT_FILENO, &result );
    if (result <= 0) return;
    }

  putp( tigetstr( "clear" ) );
  }

您必須連結到正確的庫(如 -lcurses-lterminfo 等)才能編譯最後一個。(如果兩者都不起作用,您必須詢問您的系統管理員應該連結什麼。我知道在一些舊的 SunSPARC 工作站上,您必須連結 -lncurses 才能獲得正確的庫。)

其他系統,如 DOS

本文件專門針對現代系統。如果您使用的是較舊的系統,例如 DOS 或一些非常奇怪的系統,則必須查閱您系統的文件。例如,在 DOS 上,您需要使用影片中斷功能來完成此操作,或者,正如最佳化程式經常做的那樣,直接寫入影片記憶體。此類事的細節是古老而晦澀的。祝您好運!


附錄


本文件最初發表時,引起了一些好壞參半的評論。以下內容是從這些評論中編輯而來的,以回答一些有效的問題。


ANSI 轉義碼

為什麼不直接發出 ANSI 轉義碼,比如 printf( "\033[2J" );

答案是它可能無效。正如在POSIX 程式碼的引言中所解釋的,並非所有終端都接受 ANSI/VT100+ 轉義序列。(請記住,DOS 和 Windows 的次優解決方案是要求您的使用者載入 ANSI.SYS——才能使用其中一小部分轉義序列!)但除此之外,實際上可能發生的是,終端收到的內容與您認為的不同,因為您 printf() 的內容在到達終端本身之前可能會被修改!

在 *nix 系統上做到這一點最好的方法是使用 putp() 函式與終端進行適當通訊,並使用 tigetstr() 函式來獲取要傳送的正確終端轉義序列。它很可能就是 "\033[2J"。也可能不是。如果您使用 terminfo 資料庫,您的程式將幾乎在所有地方都能正常工作,而不是在相當多的系統上神秘地列印亂碼或失敗。

在 Windows 上,請按 Windows 的方式進行。


等等,我該如何使用這些東西?

這在技術上不屬於這裡,但有關實際使用這些程式碼的問題出現了。以上所有示例都是片段,您應該知道如何將它們正確地整合到您的程式中。對於簡單的事情,只需將程式碼複製並貼上到程式中使用它的某個位置即可。

但是,如果您真的想做得花哨並使用多個檔案,那麼快速粗糙的方法是:

不要在標頭檔案中定義函式。您應該只宣告它們的原型。
1
2
3
4
5
6
7
8
9
// clearscreen.h
// #include <disclaimer.h>

#ifndef CLEARSCREEN_H
#define CLEARSCREEN_H

void ClearScreen();

#endif 

原始碼放在單獨的 .cpp 檔案中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// clearscreen.cpp
// #include <disclaimer.h>

#include "clearscreen.h"

// Paste one of the above ClearScreen code snippets here.
// For example, here's the POSIX stuff:
#include <unistd.h>
#include <term.h>

void ClearScreen()
  {
  if (!cur_term)
    {
    int result;
    setupterm( NULL, STDOUT_FILENO, &result );
    if (result <= 0) return;
    }

  putp( tigetstr( "clear" ) );
  }

要使用這些程式碼,您需要做兩件事。

首先,您需要 #include "clearscreen.h" 並像使用任何其他庫一樣使用它。
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <limits>
#include <clearscreen.h>

int main()
  {
  ClearScreen();
  std::cout << "Hello world!\n\nPress ENTER to quit.";
  std::cin.ignore( std::numeric_limits <std::streamsize> ::max(), '\n' );
  return 0;
  }

其次,您必須將 "clearscreen.cpp" 新增到您的專案中,以便它也能被編譯和連結。如果您將程式碼移植到另一個平臺,您需要做的就是編譯另一個 "clearscreen.cpp" 檔案來連結您的程式碼。


好了,今天就到這裡。祝您使用愉快!