双缓冲解决控制台应用程序输出“闪屏”(C/C++,Windows)
使用 C 语言编写游戏的小伙伴们想必起初都要遇到这样的问题,在不断清屏输出数据的过程中,控制台中的输出内容会不断地闪屏。出现这个问题的原因是程序对数据处理花掉的时间影响到了数据显示,或许你可以使用局部覆盖更新方法(减少更新数据量)来缓解闪屏,但是这种方法并不适用于所有场合,尤其是更新数据本身就非常大的场合。
本文将讲述解决控制台应用程序输出闪屏的终级解决方法——双缓冲。
下面的代码演示了在高速不断清屏输出数据的过程的闪屏问题,特邀您一试:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#include <stdio.h>int main(){ while (1) { for (char c='a'; c<'z'; c++) { system("cls"); for (int i=0; i<800; i++) { printf("%c",c); } } }} |
本例代码将使用两个 Win32 API 函数,GetStdHandle、SetConsoleCursorPosition,
| 图例 | 名称 | 说明 |
![]() |
HANDLE GetStdHandle( _In_ DWORD nStdHandle ); |
获取标准设备句柄 nStdHandle 标准设备,可取值: STD_INPUT_HANDLE (DWORD)-10,输入设备 STD_OUTPUT_HANDLE (DWORD)-11,输出设备 STD_ERROR_HANDLE (DWORD)-12,错误设备 调用返回: 成功,返回设备句柄(HANDLE); 失败,返回 INVALID_HANDLE_VALUE; 如果没有标准设备,返回 NULL。 |
![]() |
BOOL SetConsoleCursorPosition( _In_ HANDLE hConsoleOutput, _In_ COORD dwCursorPosition ); |
设置控制台光标位置 hConsoleOutput 控制台输出设备句柄 dwCursorPosition 光标位置 |
函数参数中使用到 COORD 结构体:
| 图例 | 名称 | 说明 |
![]() |
X SHORT X; |
水平坐标或列值,从 0 开始 |
![]() |
Y SHORT X; |
垂直坐标或行值,从 0 开始 |
示例代码:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#include <stdio.h>#include <Windows.h>int main(){ HANDLE hOutput; COORD coord={0,0}; hOutput=GetStdHandle(STD_OUTPUT_HANDLE); while (1) { for (char c='a'; c<'z'; c++) { SetConsoleCursorPosition(hOutput, coord); for (int i=0; i<800; i++) { printf("%c",c); } } }} |
首先,要说明的是,只有“显示缓存区”里面的数据才会被显示。默认的控制台应用程序的显示结构是这样的:

在输出大量数据的时候,由于数据经过处理需要时间,导致数据到达显示缓存区时出现了先后顺序。即是说显示器在显示数据时,可能只有部分显示数据到达了显示缓存区,而其他数据还没有到达,从而使图像按部分呈现最终显示完整。这是更新大量显示数据出现闪屏的根本原因。
在图形处理编程过程中,双缓冲是基本技术之一,它是解决闪屏的有效解决方案。尤其在游戏编程领域,双缓冲技术得到了广泛地应用。
如此看来,看似揪心的问题,其实我们只需要多一个缓冲区就可以完全解决这个问题。如果应用了双缓冲技术,那么这个控制台程序的结构将会有点变化:

由于默认的缓冲区有标准输入输出流的支持,所以为了输入输出的方便,我们将默认的显示缓冲区作为后台缓冲区,而将新建的显示缓冲区作为活动的屏幕显示。基本过程是,先将要显示的数据传输到默认缓冲区,等到数据全部写入后,再一次性填充到新建的显示缓存区。
为了实现这个过程,我们还需要调用几个 Win32 API(CreateConsoleScreenBuffer、SetConsoleActiveScreenBuffer、SetConsoleCursorInfo、ReadConsoleOutputCharacterA、WriteConsoleOutputCharacterA),
| 图例 | 名称 | 说明 |
![]() |
HANDLE WINAPICreateConsoleScreenBuffer( _In_ DWORD dwDesiredAccess, _In_ DWORD dwShareMode, _In_opt_ const SECURITY_ATTRIBUTES *lpSecurityAttributes, _In_ DWORD dwFlags, _Reserved_ LPVOIDlpScreenBufferData ); |
创建控制台显示缓冲 dwDesiredAccess,控制台缓冲安全与访问权限,可取值: GENERIC_READ (0x80000000L),读权限 GENERIC_WRITE (0x40000000L),写权限 dwShareMode,共享模式,可取值: FILE_SHARE_READ,读共享 FILE_SHARE_WRITE,写共享 lpSecurityAttributes,安全属性,NULL dwFlags,缓冲区类型,仅可选:CONSOLE_TEXTMODE_BUFFER,控制台文本模式缓冲 lpScreenBufferData,保留,NULL |
![]() |
BOOL WINAPISetConsoleActiveScreenBuffer( _In_ HANDLE hConsoleOutput ); |
设置控制台活动显示缓冲 hConsoleOutput,控制台输出设备句柄 |
![]() |
BOOL WINAPISetConsoleCursorInfo( _In_ HANDLE hConsoleOutput, _In_ const CONSOLE_CURSOR_INFO *lpConsoleCursorInfo ); |
设置控制台光标信息 hConsoleOutput,控制台输出设备句柄 lpConsoleCursorInfo,光标信息(大小、可见性) |
![]() |
BOOL WINAPIReadConsoleOutputCharacterA( _In_ HANDLE hConsoleOutput, _Out_ LPTSTR lpCharacter, _In_ DWORD nLength, _In_ COORD dwReadCoord, _Out_ LPDWORDlpNumbersOfCharsRead ); |
读取控制台输出到字符数组 hConsoleOutput,控制台输出设备句柄 lpCharacter,保存的字符数组指针 nLength,读取长度dwReadCoord,读取起始坐标lpNumbersOfCharsRead,实际读取长度 |
![]() |
BOOL WINAPIWriteConsoleOutputCharacterA( _In_ HANDLE hConsoleOutput, _In_ LPTSTR lpCharacter, _In_ DWORD nLength, _In_ COORD dwWriteCoord, _Out_ LPDWORDlpNumberOfCharsWritten ); |
写入字符数组到控制台输出 hConsoleOutput,控制台输出设备句柄 lpCharacter,写入的字符数组指针 nLength,写入长度dwWriteCoord,写入起始坐标lpNumberOfCharsWritten,实际写入长度 |
函数参数中使用到 CONSOLE_CURSOR_INFO 结构体:
| 图例 | 名称 | 说明 |
![]() |
dwSize DWORD dwSize; |
光标大小,在范围 1 到 100 中取值。 |
![]() |
bVisible BOOL bVisible; |
可见性,可取值: FALSE,0,不可见;TRUE,1,可见。 |
示例代码:
|
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
|
#include <stdio.h>#include <Windows.h>int main(){ //获取默认标准显示缓冲区句柄 HANDLE hOutput; COORD coord={0,0}; hOutput=GetStdHandle(STD_OUTPUT_HANDLE); //创建新的缓冲区 HANDLE hOutBuf = CreateConsoleScreenBuffer( GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CONSOLE_TEXTMODE_BUFFER, NULL ); //设置新的缓冲区为活动显示缓冲 SetConsoleActiveScreenBuffer(hOutBuf); //隐藏两个缓冲区的光标 CONSOLE_CURSOR_INFO cci; cci.bVisible=0; cci.dwSize=1; SetConsoleCursorInfo(hOutput, &cci); SetConsoleCursorInfo(hOutBuf, &cci); //双缓冲处理显示 DWORD bytes=0; char data[800]; while (1) { for (char c='a'; c<'z'; c++) { system("cls"); for (int i=0; i<800; i++) { printf("%c",c); } ReadConsoleOutputCharacterA(hOutput, data, 800, coord, &bytes); WriteConsoleOutputCharacterA(hOutBuf, data, 800, coord, &bytes); } } return 0;} |
双缓冲解决控制台应用程序输出“闪屏”(C/C++,Windows)的更多相关文章
- MFC双缓冲解决图象闪烁[转]
转载网上找到的一篇双缓冲的文章,很好用.http://www.cnblogs.com/piggger/archive/2009/05/02/1447917.html__________________ ...
- VC++绘图时,利用双缓冲解决屏幕闪烁 转载
最近做中国象棋,绘制界面时遇到些问题,绘图过程中屏幕闪烁,估计都会想到利用双缓冲来解决问题,但查了下网上双缓冲的资料,发现基本是MFC的,转化为VC++后,大概代码如下: void DrawBmp(H ...
- C# 控制台应用程序输出颜色字体[更正版]
首先感谢院子里的“yanxinchen”,之前的方法是通过c#调用系统api实现的,相比之下我的有点画蛇添足了,哈哈. 最佳解决方案的代码: static void Main(string[] arg ...
- vue cavnas绘制矩形,并解决由clearRec带来的闪屏问题
起因:在cavnas绘制矩形时 鼠标移动一直在监测中,所以鼠标移动的轨迹会留下一个个的矩形框, 要想清除矩形框官方给出了ctx.clearRect() 但是这样是把整个画布给清空了,因此需要不断 向画 ...
- JQuery Mobile - 解决切换页面时,闪屏,白屏等问题
在点击链接,切换页面时候,总是闪屏,感觉很别扭,看起来不舒服,怎么解决这个问题?方法很简单,就是在每个页面的meta标签内定义user-scalable的属性为 no! <meta name=& ...
- C# 控制台应用程序输出颜色字体
最佳解决方案的代码: static void Main(string[] args) { Console.ForegroundColor = ConsoleColor.Green; Console.W ...
- Delphi 使用双缓冲解决图片切换时的闪烁问题 good
var TempCanvas: TCanvas; BufDC: HDC; BufBitmap: HBITMAP; begin // 创建一个与显示设备兼容的内存设备 BufDC := CreateCo ...
- vscode环境配置(三)——解决控制台终端中文输出乱码
由于系统终端默认编码为GBK,所以需要修改为UTF-8 方法一 打开cmd输入chcp查看编码格式,查看以及修改如下图所示: 方法二
- MFC框架下Opengl窗口闪屏问题解决方案
转自https://blog.csdn.net/niusiqiang/article/details/43116153 虽然启用了双缓冲,但是仍然会出闪屏的情况,这是由于OpenGL自己有刷新背景的函 ...
随机推荐
- lldb和gdb命令映射
http://note.youdao.com/noteshare?id=45b6171a4a846f6b95db7d8211fbfb9c
- 防止apk反编译的技术分析浅谈--内存修改器篇
声明: 1.本帖转载自http://jingyan.baidu.com/article/a24b33cd509eb719fe002b94.html,仅供自用,勿喷 Apk反编译修改器有很多.拿其中的比 ...
- python 深、浅拷贝
Python的数据结构总体分为两类: 1.字符串和数字 2.列表.元组.字典等 一.字符串和数字 对于字符串和数字而言,赋值(=).浅拷贝(copy)和深拷贝(deepcopy)其实都没有意义,因为它 ...
- 前端PHP入门-021-重点日期函数之日期验证函数
checkdate可以判断一个输出的日期是否有效. 在实际的工作中,我们需要经常用于检测常用于用户提交表单的数据验证. 函数的语法格式如下: bool checkdate ( int month,in ...
- 封装-python
六 封装 从封装本身的意思去理解,封装就好像是拿来一个麻袋,把小猫,小狗,小王八,还有alex一起装进麻袋,然后把麻袋封上口子.但其实这种理解相当片面 首先我们要了解 回到顶部 6.1 要封装什么 你 ...
- 莫队 Codeforces Round #340 (Div. 2) E
题目大意:给你一个长度为n的序列,有m个询问,每次询问一个区间[L,R],表示这个区间内,有多少的a[i]^a[i+1].....^a[j]=k. 思路:莫队去搞就好了 我们定义pre[i]=a[1] ...
- 搜索:DLX算法
精确覆盖问题:在一个0-1矩阵中,选定部分行,使得每一列都有且只有一个1.求解一种选法 舞蹈链(Dance Link),也就是一个循环十字链表,可以快速的删掉和恢复某行某列 结合了舞蹈链的搜索就称作D ...
- MySQL异常总结
1.Packets larger than max_allowed_packet are not allowed MySQL的一个系统参数:max_allowed_packet,其默认值为104857 ...
- 巧妙利用JQuery和Servlet来实现跨域请求
在网上看到很多的JQuery跨域请求的文章,比较有意思.这里我发表一个Servlet与JQuery配置实现跨域的代码,供大家参考.不足之处请指教 原理:JavaScript的Ajax不可以跨域,但是可 ...
- 【BZOJ】2693: jzptab 莫比乌斯反演
[题意]2154: Crash的数字表格 莫比乌斯反演,多组询问,T<=10000. [算法]数论(莫比乌斯反演) [题解]由上一题, $ans=\sum_{g\leq min(n,m)}g\s ...

