2人参与 • 2026-09-18 • C/C++
写控制台小游戏的时候,最烦人的问题是什么?刷屏闪烁。你高高兴兴写了个贪吃蛇,一运行屏幕闪得跟迪斯科舞厅似的,眼睛都快瞎了。这篇文章就把c/c++里各种清屏方法从头到尾捋一遍,从最基础的system("cls")到终极解决方案双缓冲,手把手教你告别闪烁。
控制台程序默认是逐行往下输出的,不会自动覆盖前面的内容。你要做贪吃蛇、俄罗斯方块、实时进度条这些东西,每一帧画面都不一样,不清掉旧内容的话,文字就会堆在一起,根本没法看。
所以清屏的本质就是:擦掉旧的,画上新的。但怎么擦、怎么画,差别可太大了。
这是绝大多数新手入门时用的方法,简单到令人发指——一行代码搞定。
#include <stdlib.h> // c语言
// 或
#include <cstdlib> // c++
system("cls"); // windows
system("clear"); // linux / mac
system()函数是c标准库提供的“传令兵”,它的作用是把括号里的字符串命令交给操作系统去执行。"cls"是windows命令行里的清屏命令,"clear"是linux/mac里的清屏命令。
优点:
缺点:
什么时候用: 只适合一次性清屏(比如程序启动时清一下),或者对性能完全没要求的场景。千万别在游戏主循环里用,否则你的玩家会感谢你治好了他多年的失眠。
如果你的界面只需要刷新一行(比如进度条、计时器),根本不需要清屏。用\r就能搞定。
\r的作用是把光标移回到本行开头,不换行,然后新内容直接覆盖旧内容。
#include <iostream>
#include <windows.h> // sleep用,linux用<unistd.h>
int main() {
for (int i = 0; i <= 100; i++) {
std::cout << "\r下载进度:" << i << "%" << std::flush;
sleep(50);
}
std::cout << "\n下载完成!" << std::endl;
return 0;
}
关键点:
优点: 完全不闪烁,效率极高
缺点: 只能刷一行,多行界面搞不定
对于多行界面,可以把光标精确移动到任意位置,然后覆盖写入新内容。
#include <windows.h>
#include <iostream>
void gotoxy(int x, int y) {
handle hconsole = getstdhandle(std_output_handle);
coord pos = { (short)x, (short)y };
setconsolecursorposition(hconsole, pos);
}
int main() {
int num = 0;
// 先打印框架
std::cout << "数字:" << std::endl;
std::cout << "----------------" << std::endl;
while (true) {
gotoxy(3, 0); // 定位到"数字:"后面
std::cout << num++ << " "; // 后面的空格覆盖旧数字
sleep(100);
}
return 0;
}
getstdhandle(std_output_handle)获取标准输出设备的句柄,setconsolecursorposition把光标移动到指定坐标。coord结构体的x是列、y是行,都是从0开始。
优点:
缺点:
如果你真的需要“清空整个屏幕”的效果,但又不想用system("cls")那种闪烁的方式,可以用windows api直接操作控制台缓冲区。
#include <windows.h>
void clearscreen() {
handle hconsole = getstdhandle(std_output_handle);
coord coordscreen = { 0, 0 };
dword ccharswritten;
console_screen_buffer_info csbi;
dword dwconsize;
getconsolescreenbufferinfo(hconsole, &csbi);
dwconsize = csbi.dwsize.x * csbi.dwsize.y;
// 用空格填满整个缓冲区
fillconsoleoutputcharacter(hconsole, ' ', dwconsize, coordscreen, &ccharswritten);
getconsolescreenbufferinfo(hconsole, &csbi);
fillconsoleoutputattribute(hconsole, csbi.wattributes, dwconsize, coordscreen, &ccharswritten);
setconsolecursorposition(hconsole, coordscreen);
}
这个函数做了什么:
优点: 直接操作缓冲区,比system("cls")快得多,不闪烁
缺点: windows专用,代码比system复杂不少
在linux和mac下,可以用ansi转义序列来控制终端。
#include <stdio.h>
void clearscreen() {
printf("\033[2j\033[h");
fflush(stdout);
}
ansi转义序列不是c标准,而是终端控制的标准。好消息是,现代windows终端(windows 10以上)也支持ansi转义了,需要启用虚拟终端处理。
优点: 跨平台(现代终端都支持),不调用外部命令,效率高
缺点: 老版本windows不支持,需要额外配置
前面几种方法各有局限。system("cls")闪,\r只能刷一行,gotoxy需要自己管理每个位置,api清屏只是把cls用api重写了一遍——真正能完美解决控制台闪烁的,是双缓冲。
先搞清楚闪屏是怎么来的。控制台程序的显示结构是这样的:程序→屏幕缓冲区→显示器。当你在高速循环里不断输出大量数据时,数据到达屏幕缓冲区有先后顺序。显示器可能只拿到了部分数据就开始刷新了,于是你看到的就是“一半新一半旧”的画面,这就是闪烁的根本原因。
双缓冲的思路很简单:弄两个缓冲区,一个给显示器看(前台),一个用来画(后台) 。所有绘图操作都在后台缓冲区完成,画好之后整个切换过来。显示器看到的一直是完整的画面,没有中间状态,闪烁自然消失。
windows提供了createconsolescreenbuffer函数来创建额外的屏幕缓冲区。
#include <windows.h>
#include <iostream>
#include <string>
class doublebuffer {
private:
handle houtput; // 当前显示的缓冲区句柄
handle hbuffer; // 后台缓冲区句柄
coord buffersize;
small_rect rect;
public:
doublebuffer(int width, int height) {
houtput = getstdhandle(std_output_handle);
// 获取当前缓冲区信息
console_screen_buffer_info csbi;
getconsolescreenbufferinfo(houtput, &csbi);
// 创建新的屏幕缓冲区
hbuffer = createconsolescreenbuffer(
generic_read | generic_write,
file_share_read | file_share_write,
null,
console_textmode_buffer,
null
);
// 设置缓冲区大小
buffersize.x = width;
buffersize.y = height;
setconsolescreenbuffersize(hbuffer, buffersize);
setconsolescreenbuffersize(houtput, buffersize);
// 设置窗口大小
rect.left = 0;
rect.top = 0;
rect.right = width - 1;
rect.bottom = height - 1;
setconsolewindowinfo(houtput, true, &rect);
setconsolewindowinfo(hbuffer, true, &rect);
// 隐藏两个缓冲区的光标
console_cursor_info cursorinfo;
getconsolecursorinfo(houtput, &cursorinfo);
cursorinfo.bvisible = false;
setconsolecursorinfo(houtput, &cursorinfo);
setconsolecursorinfo(hbuffer, &cursorinfo);
// 默认显示houtput
setconsoleactivescreenbuffer(houtput);
}
~doublebuffer() {
closehandle(hbuffer);
}
// 切换到后台缓冲区进行绘制
void begindraw() {
setconsoleactivescreenbuffer(hbuffer);
}
// 切换回前台缓冲区显示
void enddraw() {
setconsoleactivescreenbuffer(houtput);
// 交换句柄,让houtput始终是"当前显示"的那个
handle temp = houtput;
houtput = hbuffer;
hbuffer = temp;
}
// 清空后台缓冲区
void clear() {
console_screen_buffer_info csbi;
getconsolescreenbufferinfo(houtput, &csbi);
dword dwconsize = csbi.dwsize.x * csbi.dwsize.y;
dword written;
coord zero = { 0, 0 };
fillconsoleoutputcharacter(houtput, ' ', dwconsize, zero, &written);
setconsolecursorposition(houtput, zero);
}
// 在后台缓冲区定位光标
void gotoxy(int x, int y) {
coord pos = { (short)x, (short)y };
setconsolecursorposition(houtput, pos);
}
// 在后台缓冲区输出
void print(const std::string& str) {
dword written;
writeconsole(houtput, str.c_str(), str.length(), &written, null);
}
};
使用示例:
int main() {
doublebuffer db(80, 25);
int frame = 0;
while (true) {
db.begindraw(); // 切换到后台缓冲区
db.clear(); // 清空后台缓冲区
// 在后台缓冲区绘制内容
db.gotoxy(10, 5);
db.print("帧数:" + std::to_string(frame++));
db.gotoxy(10, 7);
db.print("这是一个不会闪烁的控制台!");
db.enddraw(); // 切换到前台显示
sleep(50);
}
return 0;
}
核心操作:
优点:
缺点:
linux下没有createconsolescreenbuffer,但可以用ncurses库。
#include <ncurses.h>
#include <unistd.h>
int main() {
initscr(); // 初始化ncurses
curs_set(0); // 隐藏光标
noecho(); // 不显示输入的字符
int frame = 0;
while (1) {
clear(); // 清屏(在ncurses内部缓冲区)
mvprintw(5, 10, "帧数:%d", frame++);
mvprintw(7, 10, "这是一个不会闪烁的控制台!");
refresh(); // 一次性刷新到屏幕
usleep(50000);
}
endwin(); // 结束ncurses
return 0;
}
编译时需要链接ncurses库:
gcc -o program program.c -lncurses
ncurses的原理和双缓冲类似:所有绘图操作先在内部缓冲区完成,调用refresh()时才一次性输出到屏幕。
| 方法 | 平台 | 闪烁程度 | 代码复杂度 | 适用场景 |
|---|---|---|---|---|
| system("cls") | windows | ⭐⭐⭐⭐⭐ 严重闪烁 | 极低 | 一次性清屏 |
| system("clear") | linux/mac | ⭐⭐⭐⭐⭐ 严重闪烁 | 极低 | 一次性清屏 |
| \r回车覆盖 | 全平台 | ⭐ 几乎不闪 | 极低 | 单行进度条 |
| setconsolecursorposition | windows | ⭐ 几乎不闪 | 低 | 多行定点刷新 |
| windows api清屏 | windows | ⭐ 几乎不闪 | 中 | 需要真清屏的场景 |
| ansi转义序列 | 现代终端 | ⭐ 几乎不闪 | 低 | linux/现代windows |
| 双缓冲(windows api) | windows | 🌟 完全不闪 | 高 | 游戏、高频动画 |
| ncurses | linux/mac | 🌟 完全不闪 | 中 | linux终端应用 |
下面是一个完整的c++控制台程序,用双缓冲实现了一个在屏幕上弹跳的小球,完全不闪烁。
#include <windows.h>
#include <iostream>
#include <string>
// ============ 双缓冲类 ============
class doublebuffer {
private:
handle houtput;
handle hbuffer;
int width, height;
public:
doublebuffer(int w, int h) : width(w), height(h) {
houtput = getstdhandle(std_output_handle);
hbuffer = createconsolescreenbuffer(
generic_read | generic_write,
file_share_read | file_share_write,
null,
console_textmode_buffer,
null
);
coord size = { (short)w, (short)h };
setconsolescreenbuffersize(hbuffer, size);
setconsolescreenbuffersize(houtput, size);
small_rect rect = { 0, 0, (short)(w - 1), (short)(h - 1) };
setconsolewindowinfo(houtput, true, &rect);
setconsolewindowinfo(hbuffer, true, &rect);
console_cursor_info cursorinfo;
getconsolecursorinfo(houtput, &cursorinfo);
cursorinfo.bvisible = false;
setconsolecursorinfo(houtput, &cursorinfo);
setconsolecursorinfo(hbuffer, &cursorinfo);
setconsoleactivescreenbuffer(houtput);
}
~doublebuffer() {
closehandle(hbuffer);
}
void begindraw() {
setconsoleactivescreenbuffer(hbuffer);
}
void enddraw() {
setconsoleactivescreenbuffer(houtput);
handle temp = houtput;
houtput = hbuffer;
hbuffer = temp;
}
void clear() {
console_screen_buffer_info csbi;
getconsolescreenbufferinfo(houtput, &csbi);
dword size = csbi.dwsize.x * csbi.dwsize.y;
dword written;
coord zero = { 0, 0 };
fillconsoleoutputcharacter(houtput, ' ', size, zero, &written);
setconsolecursorposition(houtput, zero);
}
void gotoxy(int x, int y) {
coord pos = { (short)x, (short)y };
setconsolecursorposition(houtput, pos);
}
void print(const std::string& str) {
dword written;
writeconsole(houtput, str.c_str(), str.length(), &written, null);
}
};
// ============ 主程序 ============
int main() {
const int w = 40, h = 20;
doublebuffer db(w, h);
int ballx = 5, bally = 5;
int dx = 1, dy = 1;
int frame = 0;
while (true) {
// 更新小球位置
ballx += dx;
bally += dy;
if (ballx <= 0 || ballx >= w - 1) dx = -dx;
if (bally <= 0 || bally >= h - 1) dy = -dy;
// 绘制
db.begindraw();
db.clear();
// 画边框
for (int i = 0; i < w; i++) {
db.gotoxy(i, 0);
db.print("#");
db.gotoxy(i, h - 1);
db.print("#");
}
for (int i = 0; i < h; i++) {
db.gotoxy(0, i);
db.print("#");
db.gotoxy(w - 1, i);
db.print("#");
}
// 画小球
db.gotoxy(ballx, bally);
db.print("o");
// 显示帧数
db.gotoxy(2, h + 1);
db.print("帧数: " + std::to_string(frame++));
db.enddraw();
sleep(50);
}
return 0;
}
做控制台小游戏,清屏是绕不开的问题。我的建议是:
双缓冲虽然代码量大一些,但理解了原理之后其实就是个模板代码,复制过去改改就能用。为了玩家(和自己)的眼睛,这点功夫值得花。
到此这篇关于c/c++控制台清屏方法全攻略的文章就介绍到这了,更多相关c/c++控制台清屏内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
您想发表意见!!点此发布评论
版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。
发表评论