注:本文为 "C 语言图形编程" 相关文章合辑。
略作重排,如有内容异常,请看原文。
C 语言图形化界面------含图形、按钮、鼠标、进度条等部件制作(带详细代码、讲解及注释)
非线性光学元件于 2020-02-15 09:42:37 发布
0. 引言
在 CSDN 上,许多关于 C 程序图形化界面的介绍存在代码繁琐难解、不便调试修改或讲解不够详细的问题。本文提供的代码简单、易于移植且容易理解,希望对急需使用 C 语言制作图形化界面的读者有所帮助。
对于不熟悉 EasyX 的读者,只需 10 分钟即可上手,而它可能为您节省 3 个小时甚至更多的时间。
关于 EasyX 的简单应用,可参考作者之前关于 C 程序可视化的博文:C语言绘图实验-CSDN博客(附后)。
本文的讲解循序渐进,读者应重点关注每个步骤的理解以及两步之间代码的变化。
1. 素材准备
-
EasyX 的下载链接如下(本文使用的版本是 2014 冬至版):EasyX Graphics Library for C++。使用 EasyX 需注意其兼容的编译器(下载的帮助文件中有说明),不同版本的 EasyX 兼容的编译器不同,但均与 Visual C++6 兼容(与字符编码有关)。本文以 Visual C++6 编译器为例编写代码。
-
EasyX 的最新英文帮助文档链接(下载 2014 冬至版会自带中文帮助文档):EasyX 文档 - Basic introduction
-
如果成功下载了 EasyX 2014 冬至版,解压后将头文件(
easyx.h
和graphics.h
)和lib
文件(amd64
)分别放在 VC 文件夹默认的include
文件夹和lib
文件夹中。右键点击 VC 程序,选择"打开文件所在位置",然后找到 MFC 文件夹。以下是两个文件夹的位置截图:
-
建议将编译的 C 文件以
.cpp
后缀保存。
2. 编程
2.1 创建界面
创建一个 480×360 的窗口,需要使用 initgraph()
函数。以下是代码示例:
c
#include <graphics.h> // 引用图形库头文件
#include <conio.h>
#include <stdio.h>
#include <windows.h> // 用到了定时函数 sleep()
#include <math.h>
int main()
{
int i;
short win_width, win_height; // 定义窗口的宽度和高度
win_width = 480;
win_height = 360;
initgraph(win_width, win_height); // 初始化窗口(黑屏)
for (i = 0; i < 256; i += 5)
{
setbkcolor(RGB(i, i, i)); // 设置背景色,原来默认黑色
cleardevice(); // 清屏(取决于背景色)
Sleep(15); // 延时 15ms
}
closegraph(); // 关闭绘图界面
return 0;
}
这段代码运行后,屏幕会逐渐变亮。这是因为背景色不断刷新为 RGB(i, i, i)。C 语言中的颜色使用十六进制表示,RGB
函数可以将 0~255 范围内的三个整数三原色转换为十六进制。cleardevice()
函数用于清屏,通常只在初始化时出现。Sleep()
是毫秒级延迟,界面变亮时间不一定是准确的 15ms×255/5=0.765s,因为其他语句也需要执行时间。closegraph()
用于关闭绘图界面。如果初始化了绘图界面但未在主函数结束前关闭它,可能会引发一些莫名其妙的错误,因此该函数必不可少。
2.2 创建按钮
在界面中创建按钮需要绘制矩形和打印文字。以下是代码示例:
c
#include <graphics.h> // 引用图形库头文件
#include <conio.h>
#include <stdio.h>
#include <windows.h> // 用到了定时函数 sleep()
#include <math.h>
int r1[] = {30, 20, 130, 60}; // 输入按钮的矩形参数
int r2[] = {170, 20, 220, 60}; // 运行按钮的矩形参数
int r3[] = {260, 20, 310, 60}; // 退出按钮的矩形参数
int main()
{
int i;
short win_width, win_height; // 定义窗口的宽度和高度
win_width = 480;
win_height = 360;
initgraph(win_width, win_height); // 初始化窗口(黑屏)
for (i = 0; i < 256; i += 5)
{
setbkcolor(RGB(i, i, i)); // 设置背景色,原来默认黑色
cleardevice(); // 清屏(取决于背景色)
Sleep(15); // 延时 15ms
}
RECT R1 = {r1[0], r1[1], r1[2], r1[3]}; // 矩形指针 R1
RECT R2 = {r2[0], r2[1], r2[2], r2[3]}; // 矩形指针 R2
RECT R3 = {r3[0], r3[1], r3[2], r3[3]}; // 矩形指针 R3
LOGFONT f; // 字体样式指针
gettextstyle(&f); // 获取字体样式
_tcscpy(f.lfFaceName, _T("宋体")); // 设置字体为宋体
f.lfQuality = ANTIALIASED_QUALITY; // 设置输出效果为抗锯齿
settextstyle(&f); // 设置字体样式
settextcolor(BLACK); // BLACK 在 graphic.h 头文件中被定义为黑色的颜色常量
drawtext("输入参数", &R1, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 在矩形区域 R1 内输入文字,水平居中,垂直居中,单行显示
drawtext("运行", &R2, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 在矩形区域 R2 内输入文字,水平居中,垂直居中,单行显示
drawtext("退出", &R3, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 在矩形区域 R3 内输入文字,水平居中,垂直居中,单行显示
setlinecolor(BLACK);
rectangle(r1[0], r1[1], r1[2], r1[3]);
rectangle(r2[0], r2[1], r2[2], r2[3]);
rectangle(r3[0], r3[1], r3[2], r3[3]);
system("pause"); // 暂停,为了显示
closegraph();
return 0;
}

矩形指针 RECT
使用句柄定义,不可中途再次赋值。其格式为 RECT r = {X1, Y1, X2, Y2}
,其中 X1 和 X2 分别是矩形的左边和右边的横坐标,Y1 和 Y2 分别是矩形的上边和下边的纵坐标。DT_CENTER | DT_VCENTER | DT_SINGLELINE
是描述填充格式的常量。drawtext
函数用于在矩形区域内书写文字,无需再计算文字的坐标和设置大小,使用起来非常方便。LOGFONT
是字体样式指针,通过 gettextstyle
函数获取当前字体类型,再通过 settextstyle
函数加以设置。这里仅修改了字体名称和显示质量,还可以修改斜体、下划线等属性,更详细的内容请参考帮助文档。
2.3 鼠标操作
2.3.1 单击特效
鼠标是输入设备,只要发生以下事件,就会暂存于鼠标消息列表中,操作系统会依次响应列表中的鼠标消息事件。常用的鼠标事件如下:
WM_MOUSEMOVE
:鼠标移动WM_MOUSEWHEEL
:鼠标滚轮滚动WM_LBUTTONDOWN
:鼠标左键按下WM_LBUTTONUP
:鼠标左键弹起WM_LBUTTONDBLCLK
:鼠标左键双击WM_RBUTTONDOWN
:鼠标右键按下WM_RBUTTONUP
:鼠标右键弹起WM_RBUTTONDBLCLK
:鼠标右键双击WM_MBUTTONDOWN
:鼠标中键按下WM_MBUTTONUP
:鼠标中键弹起WM_MBUTTONDBLCLK
:鼠标中键双击
以下是实现鼠标左键单击特效的代码示例:
c
#include <graphics.h> // 引用图形库头文件
#include <conio.h>
#include <stdio.h>
#include <windows.h> // 用到了定时函数 sleep()
#include <math.h>
int r1[] = {30, 20, 130, 60}; // 输入按钮的矩形参数
int r2[] = {170, 20, 220, 60}; // 运行按钮的矩形参数
int r3[] = {260, 20, 310, 60}; // 退出按钮的矩形参数
int main()
{
int i;
short win_width, win_height; // 定义窗口的宽度和高度
win_width = 480;
win_height = 360;
initgraph(win_width, win_height); // 初始化窗口(黑屏)
for (i = 0; i < 256; i += 5)
{
setbkcolor(RGB(i, i, i)); // 设置背景色,原来默认黑色
cleardevice(); // 清屏(取决于背景色)
Sleep(15); // 延时 15ms
}
RECT R1 = {r1[0], r1[1], r1[2], r1[3]}; // 按钮 1 的矩形区域
RECT R2 = {r2[0], r2[1], r2[2], r2[3]}; // 按钮 2 的矩形区域
RECT R3 = {r3[0], r3[1], r3[2], r3[3]}; // 按钮 3 的矩形区域
LOGFONT f;
gettextstyle(&f); // 获取字体样式
_tcscpy(f.lfFaceName, _T("宋体")); // 设置字体为宋体
f.lfQuality = ANTIALIASED_QUALITY; // 设置输出效果为抗锯齿
settextstyle(&f); // 设置字体样式
settextcolor(BLACK); // BLACK 在 graphic.h 头文件中被定义为黑色的颜色常量
drawtext("输入参数", &R1, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 在矩形区域 R1 内输入文字,水平居中,垂直居中,单行显示
drawtext("运行", &R2, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 在矩形区域 R2 内输入文字,水平居中,垂直居中,单行显示
drawtext("退出", &R3, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 在矩形区域 R3 内输入文字,水平居中,垂直居中,单行显示
setlinecolor(BLACK);
rectangle(r1[0], r1[1], r1[2], r1[3]);
rectangle(r2[0], r2[1], r2[2], r2[3]);
rectangle(r3[0], r3[1], r3[2], r3[3]);
MOUSEMSG m; // 鼠标指针
setrop2(R2_NOTXORPEN); // 二元光栅------NOT(屏幕颜色 XOR 当前颜色)
while (true)
{
m = GetMouseMsg(); // 获取一条鼠标消息
if (m.uMsg == WM_LBUTTONDOWN)
{
for (i = 0; i <= 10; i++)
{
setlinecolor(RGB(25 * i, 25 * i, 25 * i)); // 设置圆颜色
circle(m.x, m.y, 2 * i);
Sleep(25); // 停顿 25ms
circle(m.x, m.y, 2 * i); // 抹去刚刚画的圆
}
FlushMouseMsgBuff(); // 清空鼠标消息缓存区
}
}
system("pause"); // 暂停,为了显示
closegraph();
return 0;
}

每次点击鼠标左键时,鼠标点击处会出现一个逐渐扩大并淡出的圆。当循环体内 Sleep
的时间大于 20ms 时,视觉效果会更明显。每次响应鼠标左键单击事件后,都会调用一次清空鼠标消息缓存区的函数 FlushMouseMsgBuff()
。如果没有这个函数,快速连续单击鼠标左键多次时,特效会重复播放,即使停止单击,程序仍会继续播放单击特效,因为鼠标消息队列中的消息尚未处理完毕。
这里需要解释的是二元光栅设置函数 setrop2()
。二元光栅是混合背景色和当前颜色的模式。我们采用的是同或(NOT XOR)的方式,若底色为白色(1),则当前颜色不变;若底色是黑色(0),则当前颜色反色。采用这种方式的原因是,我们在第二次抹去原来的圆时不能使用白色,否则如果背景色原本为黑色(比如按钮和文字),也会被抹成白色。而背景色与任意一个颜色同或两次都为其本身,即可起到还原背景色的效果。这里的背景色与 cleardevice()
前面的背景色不同,它是指执行这一条绘画指令之前屏幕上的颜色。
2.3.2 光标感应
当鼠标移到按钮上时,按钮会有所变化,移开按钮时又会恢复原样。这里采用简单的填充颜色方法,即按钮变色。需要解决的问题是按钮变色后按钮的文字不能被覆盖,因此仍需使用二元光栅。为了方便起见,将三个按钮的数组合并为一个二维数组,在鼠标事件中更容易使用和分配任务。以下是代码示例:
c
#include <graphics.h> // 引用图形库头文件
#include <conio.h>
#include <stdio.h>
#include <windows.h> // 用到了定时函数 sleep()
#include <math.h>
int r[3][4] = {{30, 20, 130, 60}, {170, 20, 220, 60}, {260, 20, 310, 60}}; // 三个按钮的二维数组
int button_judge(int x, int y)
{
if (x > r[0][0] && x < r[0][2] && y > r[0][1] && y < r[0][3]) return 1;
if (x > r[1][0] && x < r[1][2] && y > r[1][1] && y < r[1][3]) return 2;
if (x > r[2][0] && x < r[2][2] && y > r[2][1] && y < r[2][3]) return 3;
return 0;
}
int main()
{
int i, event = 0;
short win_width, win_height; // 定义窗口的宽度和高度
win_width = 480;
win_height = 360;
initgraph(win_width, win_height); // 初始化窗口(黑屏)
for (i = 0; i < 256; i += 5)
{
setbkcolor(RGB(i, i, i)); // 设置背景色,原来默认黑色
cleardevice(); // 清屏(取决于背景色)
Sleep(15); // 延时 15ms
}
RECT R1 = {r[0][0], r[0][1], r[0][2], r[0][3]};
RECT R2 = {r[1][0], r[1][1], r[1][2], r[1][3]};
RECT R3 = {r[2][0], r[2][1], r[2][2], r[2][3]};
LOGFONT f;
gettextstyle(&f); // 获取字体样式
_tcscpy(f.lfFaceName, _T("宋体")); // 设置字体为宋体
f.lfQuality = ANTIALIASED_QUALITY; // 设置输出效果为抗锯齿
settextstyle(&f); // 设置字体样式
settextcolor(BLACK); // BLACK 在 graphic.h 头文件中被定义为黑色的颜色常量
drawtext("输入参数", &R1, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 在矩形区域 R1 内输入文字,水平居中,垂直居中,单行显示
drawtext("运行", &R2, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 在矩形区域 R2 内输入文字,水平居中,垂直居中,单行显示
drawtext("退出", &R3, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 在矩形区域 R3 内输入文字,水平居中,垂直居中,单行显示
setlinecolor(BLACK);
rectangle(r[0][0], r[0][1], r[0][2], r[0][3]);
rectangle(r[1][0], r[1][1], r[1][2], r[1][3]);
rectangle(r[2][0], r[2][1], r[2][2], r[2][3]);
MOUSEMSG m; // 鼠标指针
while (true)
{
m = GetMouseMsg(); // 获取一条鼠标消息
switch (m.uMsg)
{
case WM_MOUSEMOVE:
setrop2(R2_XORPEN);
setlinecolor(LIGHTCYAN); // 线条颜色为亮青色
setlinestyle(PS_SOLID, 3); // 设置画线样式为实线,宽度为 3
setfillcolor(WHITE); // 填充颜色为白色
if (button_judge(m.x, m.y) != 0)
{
if (event != button_judge(m.x, m.y))
{
event = button_judge(m.x, m.y); // 记录这一次触发的按钮
fillrectangle(r[event - 1][0], r[event - 1][1], r[event - 1][2], r[event - 1][3]); // 有框填充矩形(X1, Y1, X2, Y2)
}
}
else
{
if (event != 0) // 上次触发的按钮未被修正为原来的颜色
{
fillrectangle(r[event - 1][0], r[event - 1][1], r[event - 1][2], r[event - 1][3]); // 两次同或为原来颜色
event = 0;
}
}
break;
case WM_LBUTTONDOWN:
setrop2(R2_NOTXORPEN); // 二元光栅------NOT(屏幕颜色 XOR 当前颜色)
for (i = 0; i <= 10; i++)
{
setlinecolor(RGB(25 * i, 25 * i, 25 * i)); // 设置圆颜色
circle(m.x, m.y, 2 * i);
Sleep(30); // 停顿 30ms
circle(m.x, m.y, 2 * i); // 抹去刚刚画的圆
}
break;
FlushMouseMsgBuff(); // 清空鼠标消息缓存区
}
}
system("pause"); // 暂停,为了显示
return 0;
}

在鼠标移动事件(case WM_MOUSEMOVE
)中,使用了屏幕颜色与当前颜色异或的方式。fillrectangle
函数用于绘制一个有框填充矩形,其大小与原按钮一致。由于线条颜色为亮青色,填充颜色为白色(1),白色填充颜色与屏幕颜色异或后,取的是屏幕颜色的反色。按钮的边框是黑色(0),它与亮青色异或后,会保留原来的亮青色。与同或一样,异或两次等于没有执行操作,因此可以还原到原屏幕画布的颜色。
2.3.3 进度条
涉及进度条时,通常会结合一个简单的程序来展示进度条的变化。这里设计了一个简单的弹性球轨迹作图程序。假设球的半径为 ( R ),初始高度为 ( h_0 ),初速度为 0(自由落体),非弹性碰撞时能量损失率为 ( \alpha )。计算部分子函数如下:
c
int simulation()
{
float dt = 0.01; // 仿真间隔 10ms
long int N = (long int)(sim_t / dt); // 迭代次数
float *h = (float *)calloc(N, sizeof(float)); // 高度
float *v = (float *)calloc(N, sizeof(float)); // 速度(竖直方向)
long int i; // 迭代变量
for (i = 1; i < N; i++)
{
if (h[i - 1] > R) // 未发生碰撞
{
v[i] = v[i - 1] - 9.8 * dt; // 速度计算
}
else // 发生碰撞,动能损失 \( \alpha \),速度损失 \( \sqrt{\alpha} \)
{
v[i] = -sqrt(alpha) * v[i - 1];
}
}
free(h);
free(v); // 释放内存
return 0;
}
接下来,需要定义绘图网格的函数:
c
void init_figure()
{
int i;
setrop2(R2_COPYPEN); // 当前颜色
setlinecolor(BLACK);
setlinestyle(PS_SOLID); // 实线
rectangle(30, 100, 420, 330); // 外框线
setlinestyle(PS_DOT); // 点线
for (i = 30 + 39; i < 420; i += 39)
{
line(i, 100, i, 330); // 竖直辅助线
}
for (i = 100 + 23; i < 330; i += 23)
{
line(30, i, 420, i); // 水平辅助线
}
}
使用 rectangle
函数绘制网格外框架,使用 line
函数依次画出辅助线。目标是将高度 ( h ) 的坐标转换到网格上,绘制出球心的轨迹。以下是改进后的 simulation
函数代码:
c
int simulation()
{
char t[3]; // 百分值的字符
char *out_text; // 带百分号的百分字符
float dt = 0.01; // 仿真间隔 10ms
float dy = 230 / h0; // 单位纵坐标
long int N = (long int)(sim_t / dt); // 迭代次数
float *h = (float *)calloc(N, sizeof(float)); // 高度
float *v = (float *)calloc(N, sizeof(float)); // 速度(竖直方向)
long int i; // 迭代变量
float process_duty; // 进度
RECT r = {370, 35, 400, 65}; // 百分值显示区域的矩形指针
init_figure(); // 初始化图像网格
setrop2(R2_COPYPEN); // 当前颜色
setfillcolor(WHITE);
setlinecolor(WHITE);
fillrectangle(354, 19, 411, 81); // 覆盖原进度条区域
setlinestyle(PS_NULL); // 无线条
setbkmode(TRANSPARENT); // 设置文字填充背景为透明
// 计算步骤
h[0] = h0;
v[0] = 0;
BeginBatchDraw(); // 开始缓存区
for (i = 1; i < N; i++)
{
if (h[i - 1] > R) // 未发生碰撞
{
v[i] = v[i - 1] - 9.8 * dt; // 速度计算
}
else // 发生碰撞,动能损失 \( \alpha \),速度损失 \( \sqrt{\alpha} \)
{
v[i] = -sqrt(alpha) * v[i - 1];
}
setfillcolor(WHITE);
setlinecolor(WHITE);
fillrectangle(354, 19, 416, 81); // 覆盖原进度条区域
h[i] = h[i - 1] + v[i] * dt; // 高度计算
process_duty = (i + 1) / (float)(N);
setlinestyle(PS_SOLID);
putpixel(30 + (int)(process_duty * 390), 330 - (int)(h[i] * dy), RED); // 画点 putpixel(X, Y, color*)
setfillcolor(BLUE);
setlinestyle(PS_NULL);
fillpie(355, 20, 415, 80, 0, process_duty * 2 * PI); // 绘制环形进度条
setfillcolor(WHITE);
fillcircle(385, 50, 20); // 覆盖中心部分
sprintf(t, "%d", (int)(process_duty * 100.0)); // 整型转换为字符串
out_text = strcat(t, "%"); // 添加一个百分号
drawtext(out_text, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 显示进度百分比
Sleep(dt * 1000); // 延时
FlushBatchDraw(); // 刷新缓存区
}
EndBatchDraw(); // 结束缓存区
free(h);
free(v);
return 0;
}
这里使用了 putpixel
函数绘制球心轨迹,fillpie
函数绘制环形进度条,fillcircle
函数覆盖中心部分以形成环形效果。FlushBatchDraw
函数用于刷新缓存区,与 BeginBatchDraw
和 EndBatchDraw
一起使用,可以批量绘图后再刷新画板。
3. 完整代码及效果
以下是完整的代码示例,包含按钮、鼠标操作、进度条和弹性球轨迹作图功能:
c
#include <graphics.h> // 引用图形库头文件
#include <conio.h>
#include <stdio.h>
#include <windows.h> // 用到了定时函数 sleep()
#include <math.h>
#include <string.h>
#define PI 3.1416
int r[3][4] = {{30, 20, 130, 60}, {170, 20, 220, 60}, {260, 20, 310, 60}}; // 三个按钮的二维数组
float alpha, R, h0, sim_t; // 碰撞时的能量损失率,球的半径、初始高度、仿真时间
// 按钮判断函数
int button_judge(int x, int y)
{
if (x > r[0][0] && x < r[0][2] && y > r[0][1] && y < r[0][3]) return 1;
if (x > r[1][0] && x < r[1][2] && y > r[1][1] && y < r[1][3]) return 2;
if (x > r[2][0] && x < r[2][2] && y > r[2][1] && y < r[2][3]) return 3;
return 0;
}
// 初始化图像
void init_figure()
{
int i;
setrop2(R2_COPYPEN); // 当前颜色
setlinecolor(BLACK);
setlinestyle(PS_SOLID); // 实线
rectangle(30, 100, 420, 330); // 外框线
setlinestyle(PS_DOT); // 点线
for (i = 30 + 39; i < 420; i += 39)
{
line(i, 100, i, 330); // 竖直辅助线
}
for (i = 100 + 23; i < 330; i += 23)
{
line(30, i, 420, i); // 水平辅助线
}
}
// 仿真运行
int simulation()
{
char t[3]; // 百分值的字符
char *out_text;
float dt = 0.01; // 仿真间隔 10ms
float dy = 230 / h0; // 单位纵坐标
long int N = (long int)(sim_t / dt); // 迭代次数
float *h = (float *)calloc(N, sizeof(float)); // 高度
float *v = (float *)calloc(N, sizeof(float)); // 速度(竖直方向)
long int i; // 迭代变量
float process_duty; // 进度
RECT r = {370, 35, 400, 65}; // 百分值显示区域的矩形指针
init_figure(); // 初始化图像网格
setrop2(R2_COPYPEN); // 当前颜色
setfillcolor(WHITE);
setlinecolor(WHITE);
fillrectangle(354, 19, 411, 81); // 覆盖原进度条区域
setlinestyle(PS_NULL); // 无线条
setbkmode(TRANSPARENT); // 设置文字填充背景为透明
// 计算步骤
h[0] = h0;
v[0] = 0;
BeginBatchDraw(); // 开始缓存区
for (i = 1; i < N; i++)
{
if (h[i - 1] > R) // 未发生碰撞
{
v[i] = v[i - 1] - 9.8 * dt; // 速度计算
}
else // 发生碰撞,动能损失 \( \alpha \),速度损失 \( \sqrt{\alpha} \)
{
v[i] = -sqrt(alpha) * v[i - 1];
}
setfillcolor(WHITE);
setlinecolor(WHITE);
fillrectangle(354, 19, 416, 81); // 覆盖原进度条区域
h[i] = h[i - 1] + v[i] * dt; // 高度计算
process_duty = (i + 1) / (float)(N);
setlinestyle(PS_SOLID);
putpixel(30 + (int)(process_duty * 390), 330 - (int)(h[i] * dy), RED); // 画点 putpixel(X, Y, color*)
setfillcolor(BLUE);
setlinestyle(PS_NULL);
fillpie(355, 20, 415, 80, 0, process_duty * 2 * PI); // 绘制环形进度条
setfillcolor(WHITE);
fillcircle(385, 50, 20); // 覆盖中心部分
sprintf(t, "%d", (int)(process_duty * 100.0)); // 整型转换为字符串
out_text = strcat(t, "%"); // 添加一个百分号
drawtext(out_text, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 显示进度百分比
Sleep(dt * 1000); // 延时
FlushBatchDraw(); // 刷新缓存区
}
EndBatchDraw(); // 结束缓存区
free(h);
free(v);
return 0;
}
int main()
{
int i, event = 0;
char s[30]; // 输入字符串变量
short win_width, win_height; // 定义窗口的宽度和高度
win_width = 480;
win_height = 360;
initgraph(win_width, win_height); // 初始化窗口(黑屏)
for (i = 0; i < 256; i += 5)
{
setbkcolor(RGB(i, i, i)); // 设置背景色,原来默认黑色
cleardevice(); // 清屏(取决于背景色)
Sleep(30); // 延时 30ms
}
RECT R1 = {r[0][0], r[0][1], r[0][2], r[0][3]};
RECT R2 = {r[1][0], r[1][1], r[1][2], r[1][3]};
RECT R3 = {r[2][0], r[2][1], r[2][2], r[2][3]};
LOGFONT f; // 字体样式指针
gettextstyle(&f); // 获取字体样式
_tcscpy(f.lfFaceName, _T("宋体")); // 设置字体为宋体
f.lfQuality = ANTIALIASED_QUALITY; // 设置输出效果为抗锯齿
settextstyle(&f); // 设置字体样式
settextcolor(BLACK); // BLACK 在 graphic.h 头文件中被定义为黑色的颜色常量
drawtext("输入参数", &R1, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 在矩形区域 R1 内输入文字,水平居中,垂直居中,单行显示
drawtext("运行", &R2, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 在矩形区域 R2 内输入文字,水平居中,垂直居中,单行显示
drawtext("退出", &R3, DT_CENTER | DT_VCENTER | DT_SINGLELINE); // 在矩形区域 R3 内输入文字,水平居中,垂直居中,单行显示
setlinecolor(BLACK);
rectangle(r[0][0], r[0][1], r[0][2], r[0][3]);
rectangle(r[1][0], r[1][1], r[1][2], r[1][3]);
rectangle(r[2][0], r[2][1], r[2][2], r[2][3]);
MOUSEMSG m; // 鼠标指针
while (true)
{
m = GetMouseMsg(); // 获取一条鼠标消息
switch (m.uMsg)
{
case WM_MOUSEMOVE:
setrop2(R2_XORPEN);
setlinecolor(LIGHTCYAN); // 线条颜色为亮青色
setlinestyle(PS_SOLID, 3); // 设置画线样式为实线,宽度为 3
setfillcolor(WHITE); // 填充颜色为白色
if (button_judge(m.x, m.y) != 0)
{
if (event != button_judge(m.x, m.y))
{
event = button_judge(m.x, m.y); // 记录这一次触发的按钮
fillrectangle(r[event - 1][0], r[event - 1][1], r[event - 1][2], r[event - 1][3]); // 有框填充矩形(X1, Y1, X2, Y2)
}
}
else
{
if (event != 0) // 上次触发的按钮未被修正为原来的颜色
{
fillrectangle(r[event - 1][0], r[event - 1][1], r[event - 1][2], r[event - 1][3]); // 两次同或为原来颜色
event = 0;
}
}
break;
case WM_LBUTTONDOWN:
setrop2(R2_NOTXORPEN); // 二元光栅------NOT(屏幕颜色 XOR 当前颜色)
for (i = 0; i <= 10; i++)
{
setlinecolor(RGB(25 * i, 25 * i, 25 * i)); // 设置圆颜色
circle(m.x, m.y, 2 * i);
Sleep(20); // 停顿 20ms
circle(m.x, m.y, 2 * i); // 抹去刚刚画的圆
}
// 按照按钮判断左键单击后的操作
switch (button_judge(m.x, m.y))
{
case 1:
InputBox(s, 30, "请输入碰撞时的能量损失率、球的半径、初始高度、仿真时间");
sscanf(s, "%f %f %f %f", &alpha, &R, &h0, &sim_t); // 将输入字符串依次扫描到全局变量中
FlushMouseMsgBuffer(); // 单击事件后清空鼠标消息
break;
case 2:
simulation(); // 仿真运行
FlushMouseMsgBuffer(); // 单击事件后清空鼠标消息
break;
case 3:
closegraph(); // 关闭绘图环境
exit(0); // 正常退出
default:
FlushMouseMsgBuffer(); // 单击事件后清空鼠标消息
break;
}
break;
}
}
return 0;
}


希望本文对您有所帮助,谢谢阅读。
C 语言实现动画控制
非线性光学元件于 2018-12-22 20:47:28 发布
原材料
下载 EasyX 2014 冬至版,将 lib
文件放在编译器默认的 lib
文件夹中,h
头文件放在编译器默认的 include
文件夹中即可。下载链接:EasyX Graphics Library for C++
说明
C 语言可以使用系统内部的定时函数 sleep
和 usleep
进行定时(需要 windows.h
头文件),但绘图窗口需要额外的图形库支持。EasyX 提供了绘图功能,可以为您的 C 编译器带来革命性的变化。
一场革命
EasyX 的压缩包中包含一个帮助文档,虽然内容丰富但不太方便查阅。希望正在使用 EasyX 的开发者能多分享一些资源。以下是一个沿着指定半径依次绘制 12 个不同颜色的圆并依次擦除的小动画程序。代码中对不太容易理解的部分加了注释,通过图形画法学习 C 语言语法,既生动又简单,可以快速跨越语法障碍。
c
#include <graphics.h> // 引用图形库头文件
#include <conio.h>
#include <stdio.h>
#include <windows.h> // 用到了定时函数 sleep()
#include <math.h>
#define PI 3.14159265 // 画圆必备
int a[] = {0, 0xAA0000, 0x00AA00, 0xAAAA00, 0x0000AA, 0xAA00AA, 0x0055AA, 0xAAAAAA, 0x555555, 0xFF5555, 0x55FF55, 0xFFFF55, 0x5555FF, 0xFF55FF, 0x55FFFF, 0xFFFFFF}; // a[] 是颜色数组
// a 数组存放的颜色依次为
/* |0:黑色 |1:蓝色 |2:绿色 |3:青色 |4:红色
|5:紫色 |6:棕色 |7:浅灰 |8:深灰 |9:亮蓝
|10:亮绿 |11:亮青 |12:亮红 |13:亮紫 |14:黄色 |15:白色
*/
int main()
{
system("color 0B"); // 设置字体为亮蓝色,纯粹为了好看
short x, y; // 圆心坐标
int R; // 旋转半径
int color[6] = {1, 2, 3, 4, 5, 6}; // 指定圆的颜色
int i = 0;
char t;
printf("C 语言绘图实验:\n");
printf("请选择画布大小(以空格分隔):\n");
scanf("%d %d", &x, &y);
initgraph(x, y, SHOWCONSOLE); // 创建绘图窗口,大小为 640x480 像素
printf("请输入旋转半径: ");
scanf("%d", &R);
printf("请选择 6 种圆的颜色:\n");
printf("|0:黑色\n|1:蓝色\t|2:绿色\t|3:青色\t|4:红色\t|5:紫色\n|6:棕色\t|7:浅灰\t|8:深灰\t|9:亮蓝\t|10:亮绿\n|11:亮青\t|12:亮红\t|13:亮紫\t|14:黄色\t|15:白色\n");
scanf("%d %d %d %d %d %d", &color[0], &color[1], &color[2], &color[3], &color[4], &color[5]); // 录入 6 种不同的颜色
printf("\r按任意键继续:\n");
while (_getch()) // _getch() 是按下任意键即返回非零值的函数,与 getchar() 不同,不经过标准输入流的缓存区
{
for (i = 0; i < 12; i++)
{
setlinecolor(RGB(0, 0, 0)); // 设置当前线条颜色
setfillcolor(a[color[i % 6]]); // 设置当前填充颜色
fillcircle(x / 2 + R * cos(i * PI / 6), y / 2 + R * sin(i * PI / 6), R * (PI / 12) * 0.9); // 绘制填充圆
Sleep(300); // 延时 300ms
}
for (i = 0; i < 12; i++)
{
setlinecolor(RGB(0, 0, 0)); // 设置当前线条颜色
setfillcolor(a[0]); // 背景色(黑色)覆盖掉原来的图形
fillcircle(x / 2 + R * cos(i * PI / 6), y / 2 + R * sin(i * PI / 6), R * (PI / 12) * 0.9);
Sleep(300); // 延时 300ms
}
}
return 0;
}
以下是程序运行主界面:

以下是绘图界面:

C 语言写字符动画
Z.IA 已于 2024-12-01 14:26:02 修改
用 C 在控制台写了一个动画,代码没做优化,十分简单粗暴。
新版本 win 系统需使用老版本 cmd。
以管理员身份运行 exe 文件即可。
cpp
#include <stdio.h>
#include <windows.h>
void color (int x)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),x);
}
int main()
{
int i,j,q=28,xxl=22,h=27,m=33,cc=2,xxx=1;
char s[68][53];
char z[140][53];
char zx[148][100];
for(i=0;i<35;i++)
{
for(j=0;j<28;j++)
zx[i][j]=' ';
for(j=28;j<=35;j++)
zx[i][j]='O';
for(j=36;j<100;j++)
zx[i][j]=' ';
}
for(i=35;i<134;i++)
{
for(j=0;j<h;j++)
zx[i][j]=' ';
for(j=h;j<h+4;j++)
zx[i][j]='O';
for(j=h+4;j<=m;j++)
zx[i][j]=' ';
for(j=m;j<m+4;j++)
zx[i][j]='O';
for(j=m+4;j<100;j++)
zx[i][j]=' ';
if(i<57)
{
h--;
m++;
}
if(i>110)
{
h++;
m--;
}
}
for(i=134;i<148;i++)
{
for(j=0;j<28;j++)
zx[i][j]=' ';
for(j=28;j<=35;j++)
zx[i][j]='O';
for(j=36;j<100;j++)
zx[i][j]=' ';
}
for(i=48;i<=51;i++)
for(j=31;j<=35;j++)
zx[i][j]='*';
for(i=129;i<=133;i++)
{
for(j=15;j<=17;j++)
zx[i][j]='*';
for(j=46;j<=48;j++)
zx[i][j]='*';
}
for(i=0;i<40;i++) //直行
{
for(j=0;j<28;j++)
z[i][j]=' ';
for(j=28;j<36;j++)
z[i][j]='O';
for(j=36;j<53;j++)
z[i][j]=' ';
}
for(i=40;i<61;i++) //拐弯+回;
{
for(j=0;j<q;j++)
z[i][j]=' ';
for(j=q;j<q+8;j++)
z[i][j]='O';
for(j=q+8;j<60;j++)
z[i][j]=' ';
if(i<53) //右移长度控制
q-=2;
if(i>53&&i<60) //回长度控制
q++;
}
for(i=61;i<101;i++) //直行
{
for(j=0;j<8;j++)
z[i][j]=' ';
for(j=8;j<16;j++)
z[i][j]='O';
for(j=16;j<53;j++)
z[i][j]=' ';
}
for(i=101;i<124;i++) //拐弯+回;
{
for(j=0;j<q;j++)
z[i][j]=' ';
for(j=q;j<q+8;j++)
z[i][j]='O';
for(j=q+8;j<60;j++)
z[i][j]=' ';
if(i<114) //右移长度控制
q+=2;
if(i>114&&i<121) //回长度控制
q--;
}
for(i=124;i<140;i++)
{
for(j=0;j<28;j++)
z[i][j]=' ';
for(j=28;j<=35;j++)
z[i][j]='O';
for(j=36;j<100;j++)
z[i][j]=' ';
}
for(i=54;i<=57;i++)
z[i][30]=z[i][31]=z[i][32]='*';
for(i=116;i<=119;i++)
z[i][14]=z[i][15]=z[i][16]='*';
for(i=0;i<40;i++) //直行
{
for(j=0;j<22;j++)
s[i][j]=' ';
for(j=22;j<29;j++)
s[i][j]='O';
for(j=29;j<53;j++)
s[i][j]=' ';
}
for(i=40;i<68;i++) //拐弯+回;
{
for(j=0;j<xxl;j++)
s[i][j]=' ';
for(j=xxl;j<xxl+8;j++)
s[i][j]='O';
for(j=xxl+8;j<53;j++)
s[i][j]=' ';
if(i<53) //右移长度控制
xxl++;
if(i>53&&i<60) //回长度控制
xxl--;
}
int k,c=28,begin=28,end=39;
q=39;
char x[104][40];
for(i=0;i<=10;i++)
{
for(j=0;j<q;j++)
x[i][j]=' ';
for(j=q;j<=39;j++)
x[i][j]='O';
if(q>27)
q--;
}
for(i=90;i<=101;i++)
{
for(j=0;j<=c;j++)
x[i][j]=' ';
for(j=c+1;j<=39;j++)
x[i][j]='O';
if(c<39)
c++;
}
for (i=11;i<=50;i++)
{
for (j=0;j<begin;j++)
x[i][j]=' ';
for (j=begin;j<=end;j++)
x[i][j]='O';
for (j=end+1;j<=39;j++)
x[i][j]=' ';
if(begin>0)
begin--;
if(end>0)
end--;
}
end++;
for (i=51;i<=89;i++)
{
for (j=0;j<begin;j++)
x[i][j]=' ';
for (j=begin;j<=end;j++)
x[i][j]='O';
for (j=end+1;j<=39;j++)
x[i][j]=' ';
if(end>=11)
begin++;
if(end<39)
end++;
}
//扭动组赋值
char kd[100][40];
for(i=0;i<100;i++)
{
for(j=0;j<38;j++)
kd[i][j]=' ';
kd[i][39]='O';
}
int p=1,g=1,xs=3;
char wy[200][120];
for(i=0;i<200;i++)
for(j=0;j<120;j++)
wy[i][j]=' ';
for(i=0;i<40;i++)
{
for(j=0;j<8;j++)
wy[i][j]=' ';
for(j=8;j<16;j++)
wy[i][j]='O';
for(j=16;j<100;j++)
wy[i][j]=' ';
}
/*直行赋值*/
j=16;
for(i=40;i<76;i++)
{
wy[i][j]='O';
if((i-39)%4==0&&i>=43)
g+=p;
j+=g;
if(i==55)
p=-p;
}
j=15;
p=1;
g=1;
for(i=42;i<78;i++)
{
wy[i][j]='O';
if((i-41)%4==0&&i>=45)
g+=p;
j+=g;
if(i==57)
p=-p;
}
j=14;
p=1;
g=1;
for(i=44;i<80;i++)
{
wy[i][j]='O';
if((i-43)%4==0&&i>=47)
g+=p;
j+=g;
if(i==59)
p=-p;
}
j=13;
p=1;
g=1;
for(i=46;i<82;i++)
{
wy[i][j]='O';
if((i-45)%4==0&&i>=49)
g+=p;
j+=g;
if(i==61)
p=-p;
}
j=12;
p=1;
g=1;
for(i=48;i<84;i++)
{
wy[i][j]='O';
if((i-47)%4==0&&i>=51)
g+=p;
j+=g;
if(i==63)
p=-p;
}
// 第5组;
j=11;
p=1;
g=1;
for(i=50;i<86;i++)
{
wy[i][j]='O';
if((i-49)%4==0&&i>=53)
g+=p;
j+=g;
if(i==65)
p=-p;
}
j=10;
p=1;
g=1;
for(i=52;i<88;i++)
{
wy[i][j]='O';
if((i-51)%4==0&&i>=55)
g+=p;
j+=g;
if(i==67)
p=-p;
}
j=9;
p=1;
g=1;
for(i=54;i<90;i++)
{
wy[i][j]='O';
if((i-53)%4==0&&i>=57)
g+=p;
j+=g;
if(i==69)
p=-p;
}
p=54;
for(j=8;j<16;j++)
{
for(i=40;i<p;i++)
wy[i][j]='O';
p-=2;
}
//补充 O
p=75;
for(j=116;j>=110;j--)
{
for(i=89;i>p;i--)
wy[i][j]='O';
p+=2;
}
for(i=90;i<130;i++)
{
for(j=109;j<117;j++)
wy[i][j]='O';
}
j=108;
p=-1;
g=-1;
for(i=130;i<166;i++)
{
wy[i][j]='O';
if((i-129)%4==0&&i>=133)
g+=p;
j+=g;
if(i==145)
p=-p;
}
j=109;
p=-1;
g=-1;
for(i=132;i<168;i++)
{
wy[i][j]='O';
if((i-131)%4==0&&i>=135)
g+=p;
j+=g;
if(i==147)
p=-p;
}
j=110;
p=-1;
g=-1;
for(i=134;i<170;i++)
{
wy[i][j]='O';
if((i-133)%4==0&&i>=137)
g+=p;
j+=g;
if(i==149)
p=-p;
}
j=111;
p=-1;
g=-1;
for(i=136;i<172;i++)
{
wy[i][j]='O';
if((i-135)%4==0&&i>=139)
g+=p;
j+=g;
if(i==151)
p=-p;
}
j=112;
p=-1;
g=-1;
for(i=138;i<174;i++)
{
wy[i][j]='O';
if((i-137)%4==0&&i>=141)
g+=p;
j+=g;
if(i==153)
p=-p;
}
j=113;
p=-1;
g=-1;
for(i=140;i<176;i++)
{
wy[i][j]='O';
if((i-139)%4==0&&i>=143)
g+=p;
j+=g;
if(i==155)
p=-p;
}
j=114;
p=-1;
g=-1;
for(i=142;i<178;i++)
{
wy[i][j]='O';
if((i-141)%4==0&&i>=145)
g+=p;
j+=g;
if(i==157)
p=-p;
}
j=115;
p=-1;
g=-1;
for(i=144;i<180;i++)
{
wy[i][j]='O';
if((i-143)%4==0&&i>=147)
g+=p;
j+=g;
if(i==159)
p=-p;
}
p=165;
for(j=8;j<16;j++)
{
for(i=179;i>p;i--)
wy[i][j]='O';
p+=2;
}
p=144;
for(j=116;j>=110;j--)
{
for(i=130;i<p;i++)
wy[i][j]='O';
p-=2;
}
for(i=180;i<200;i++)
{
for(j=0;j<8;j++)
wy[i][j]=' ';
for(j=8;j<16;j++)
wy[i][j]='O';
for(j=16;j<100;j++)
wy[i][j]=' ';
}
//菱形部分
char a[50][54];
k=15;
begin=24;
end=27;
for(i=0;i<=49;i++)
for(j=24;j<=27;j++)
a[i][j]='O';
for(i=0;i<=24;i++)
{
for(j=0;j<begin;j++)
a[i][j]=' ';
for(j=begin;j<24;j++)
a[i][j]='i';
for(j=28;j<=end;j++)
a[i][j]='i';
for(j=end+1;j<=51;j++)
a[i][j]=' ';
begin-=1;end+=1;
}
begin+=1;end-=1;
for(i=25;i<=49;i++)
{
for(j=0;j<begin;j++)
a[i][j]=' ';
for(j=begin;j<24;j++)
a[i][j]='i';
for(j=28;j<=end;j++)
a[i][j]='i';
for(j=end+1;j<=51;j++)
a[i][j]=' ';
begin+=1;end-=1;
}
system("color f3");
for(i=0;i<100;i++)
{
for(j=0;j<40;j++)
printf("%c",kd[i][j]);
printf("\n\n");
Sleep(2);
}
int yans=2;
system("color f3");
while(yans--)
{
for(i=0;i<=100;i++)
{
for (j=0;j<=39;j++)
printf("%c",x[i][j]);
if(i<=100)
printf("\n\n");
Sleep(1);
}
}
k=3;
while (k--)
{
for(i=0;i<=49;i++)
{
for(j=0;j<24;j++)
{
color(3);
printf("%c",a[i][j]);
}
for(j=24;j<=27;j++)
{
color(7);
printf("%c",a[i][j]);
}
for(j=28;j<=51;j++)
{
color(3);
printf("%c",a[i][j]);
}
printf("\n\n");
Sleep(1);
}
}
color(7);
for(i=47;i<=50;i++)
s[i][8]=s[i][9]=s[i][10]='*';
for(i=0;i<47;i++)
{
for(j=0;j<53;j++)
printf("%c",s[i][j]);
printf("\n\n");
Sleep(1);
}
for(i=47;i<=50;i++)
{
for(j=0;j<=10;j++)
{
color(4);
printf("%c",s[i][j]);
}
for(j=11;j<53;j++)
{
color(7);
printf("%c",s[i][j]);
}
printf("\n\n");
}
for(i=51;i<68;i++)
{
for(j=0;j<53;j++)
printf("%c",s[i][j]);
printf("\n\n");
Sleep(1);
}
while(cc--)
{
for(i=0;i<54;i++)
{
for(j=0;j<53;j++)
printf("%c",z[i][j]);
printf("\n\n");
Sleep(1);
}
for(i=54;i<=57;i++)
{
for(j=0;j<=29;j++)
{
color(7);
printf("%c",z[i][j]);
}
for(j=30;j<33;j++)
{
color(4);
printf("%c",z[i][j]);
}
printf("\n\n");
Sleep(1);
}
color(7);
for(i=58;i<116;i++)
{
for(j=0;j<53;j++)
printf("%c",z[i][j]);
printf("\n\n");
Sleep(1);
}
for(i=116;i<=119;i++)
{
for(j=0;j<=16;j++)
{
color(4);
printf("%c",z[i][j]);
}
for(j=17;j<53;j++)
{
color(7);
printf("%c",z[i][j]);
}
printf("\n\n");
Sleep(1);
}
for(i=120;i<140;i++)
{
for(j=0;j<53;j++)
printf("%c",z[i][j]);
printf("\n\n");
Sleep(1);
}
}
while(xxx--)
{
for(i=0;i<48;i++)
{
for(j=0;j<100;j++)
printf("%c",zx[i][j]);
printf("\n\n");
Sleep(1);
}
for(i=48;i<52;i++)
{
for(j=0;j<31;j++)
printf("%c",zx[i][j]);
color(4);
for(j=31;j<=35;j++)
printf("%c",zx[i][j]);
color(7);
for(j=36;j<100;j++)
printf("%c",zx[i][j]);
printf("\n\n");
Sleep(1);
}
for(i=52;i<129;i++)
{
for(j=0;j<100;j++)
printf("%c",zx[i][j]);
printf("\n\n");
Sleep(1);
}
for(i=129;i<134;i++)
{
color(4);
for(j=0;j<=17;j++)
printf("%c",zx[i][j]);
color(7);
for(j=18;j<46;j++)
printf("%c",zx[i][j]);
color(4);
for(j=46;j<100;j++)
printf("%c",zx[i][j]);
printf("\n\n");
Sleep(1);
}
color(7);
for(i=134;i<148;i++)
{
for(j=0;j<100;j++)
printf("%c",zx[i][j]);
printf("\n\n");
Sleep(1);
}
}
for(i=0;i<54;i++)
{
for(j=0;j<53;j++)
printf("%c",z[i][j]);
printf("\n\n");
Sleep(1);
}
for(i=54;i<=57;i++)
{
for(j=0;j<=29;j++)
{
color(7);
printf("%c",z[i][j]);
}
for(j=30;j<33;j++)
{
color(4);
printf("%c",z[i][j]);
}
printf("\n\n");
Sleep(1);
}
color(7);
for(i=58;i<102;i++)
{
for(j=0;j<53;j++)
printf("%c",z[i][j]);
printf("\n\n");
Sleep(1);
}
while(xs--)
{
for(i=0;i<200;i++)
{
for(j=0;j<120;j++)
{
color(xs+2);
printf("%c",wy[i][j]);
}
printf("\n\n");
Sleep(2);
}
}
}
via:
-
C语言图形化界面------含图形、按钮、鼠标、进度条等部件制作(带详细代码、讲解及注释)-CSDN博客
https://blog.csdn.net/weixin_44044411/article/details/104276757 -
C 语言绘图实验-CSDN博客
https://blog.csdn.net/weixin_44044411/article/details/85217818 -
C 语言写字符动画_c语言字符动画-CSDN博客
https://blog.csdn.net/qq_60682749/article/details/124206299 -
关于C语言实现easyX一帧一帧播放动态图(超详细)_如何用easyx实现简单的动画-CSDN博客
https://blog.csdn.net/loneth/article/details/126788379 -
C语言图形编程(easyX简明教程) - C语言网
https://www.dotcpp.com/course/easyx/ -
动画原理 浅析 - Jenaral - 博客园
https://www.cnblogs.com/Jenaral/p/5681815.html -
控制台动态绘制字符艺术-CSDN博客
https://blog.csdn.net/dandelionLYY/article/details/86744041