发布者:
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" 文件来链接您的代码。


好了,今天就到这里。祝您使用愉快!