ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

C++图形编程入门:使用EasyX图形库快速创建可视化应用

C++图形编程入门:使用EasyX图形库快速创建可视化应用 1. 为什么选择EasyX作为你的第一个图形库如果你刚开始接触C或者已经学了一段时间语法但总觉得对着黑漆漆的控制台写程序少了点意思想做出点能“看得见”的东西比如一个小游戏、一个数据可视化图表甚至是一个简单的动画那么你大概率会卡在“图形界面”这一步。C标准库本身没有提供任何图形功能直接调用Windows API如GDI又过于复杂门槛太高。这时候EasyX就该登场了。EasyX是一个专为C/C尤其是Visual C初学者设计的图形库。它的核心价值在于将复杂的Windows图形编程接口GDI封装成了极其简单、直观的函数。你可以把它想象成给C这个“理工男”配上了一套简单易用的“画笔”和“画板”。你不需要理解什么是设备上下文DC什么是消息循环只需要调用circle()、line()、putpixel()这样的函数就能在屏幕上画出图形。这对于建立编程兴趣、验证算法效果比如排序算法的动态演示、快速开发教学演示程序或小型游戏原型来说是绝佳的工具。我最初接触EasyX是在大学用它做了第一个“会动”的程序——一个模拟行星运动的小动画。那种从命令行数字输出到可视化窗口的飞跃带来的成就感是巨大的。它完美地填补了从“学会语法”到“做出作品”之间的实践空白。虽然EasyX主要面向Windows平台且功能上不如Qt、MFC等大型框架全面但它的轻量、直接、零配置特性让它成为了无数C学习者图形编程的“初恋”。2. 搭建你的第一个EasyX绘图窗口万事开头难但EasyX的开头真的不难。整个过程的核心就是安装、创建窗口、消息循环。我们一步步来。2.1 环境准备与库安装EasyX的安装简单到令人发指。它目前主要支持Visual Studio系列IDE如VS2010~VS2022和少数配置好的Dev-C。以最常用的Visual Studio为例下载访问EasyX官网下载对应你Visual Studio版本的安装包。例如如果你用VS2022就下载“EasyX for Visual C 2022”。安装运行下载的.exe文件。安装程序会自动检测你系统上已安装的VS版本并勾选。你只需要点击“安装”即可。它本质上就是把几个头文件.h和库文件.lib复制到了VS的特定目录下。验证新建一个空白的C控制台项目在代码开头包含#include graphics.h。如果编译不报错说明安装成功。注意EasyX是基于Windows GDI的所以你的项目必须是Win32控制台应用程序不能是“空项目”或“Windows桌面应用程序”除非你特别清楚如何调整子系统。新建项目时务必选择正确的模板。2.2 初始化窗口initgraph函数详解所有绘图操作都需要一个“画布”在EasyX中这个画布就是一个窗口。创建窗口使用initgraph函数。#include graphics.h // 包含EasyX图形库头文件 #include conio.h // 用于_getch()函数等待按键 int main() { // 初始化一个640像素宽、480像素高的绘图窗口 initgraph(640, 480); // 在这里编写你的绘图代码... _getch(); // 按任意键继续 closegraph(); // 关闭图形窗口 return 0; }运行这段代码你会看到一个黑色的窗口弹出来。initgraph的原型是void initgraph( int width, // 窗口宽度 int height, // 窗口高度 int flag NULL // 初始化标志通常用默认值NULL即可 );这个函数执行了非常多底层工作它创建了一个Windows窗口初始化了图形设备接口并为你准备好了这块“内存画布”。之后所有绘图函数默认都是在这个窗口的客户区内进行操作的。一个关键细节initgraph的第三个参数flag。虽然我们通常留空NULL但它其实非常有用。比如你可以用SHOWCONSOLE标志在创建图形窗口的同时保留控制台窗口方便调试输出initgraph(640, 480, SHOWCONSOLE); // 显示控制台窗口这对于调试时打印变量值、查看日志至关重要。否则你的printf或cout输出就无处可去了。2.3 理解“双缓冲”与基本的消息循环当你开始在窗口里画东西尤其是画动画时会很快遇到“闪烁”问题。这是因为你直接在屏幕对应的“画布”上作画用户能看到每一笔的绘制过程。解决这个问题的标准技术叫“双缓冲”。双缓冲的原理在内存中创建一块和屏幕画布一样大的“缓冲区”。所有的绘图操作都先在这块内存缓冲区中进行。等一整幅画面全部画好后再一次性把内存缓冲区的内容“翻”到屏幕上。这样用户看到的就是完整的、瞬间切换的画面避免了中间过程的闪烁。幸运的是EasyX默认就开启了双缓冲。你不需要手动管理。当你调用cleardevice()清屏或者BeginBatchDraw()和EndBatchDraw()进行批量绘制时EasyX在背后帮你处理好了缓冲区的交换。这是一个对初学者极其友好的设计让你可以专注于图形逻辑本身。至于消息循环在纯EasyX程序中我们通常用一个简单的循环来保持窗口不关闭并响应一些基本操作比如按键关闭。#include graphics.h #include conio.h int main() { initgraph(640, 480, SHOWCONSOLE); // 基本的消息循环 ExMessage msg; // EasyX定义的消息结构体 while (true) { // 获取一条消息。如果当前没有消息函数会立即返回 if (peekmessage(msg, EX_KEY)) // 只监听键盘消息 { if (msg.message WM_KEYDOWN) // 如果按下了键 { if (msg.vkcode VK_ESCAPE) // 如果按的是ESC键 { break; // 退出循环结束程序 } } } // 在这里放置你的绘图和游戏逻辑 cleardevice(); // 清屏 // ... 绘制图形 ... Sleep(10); // 稍微延迟降低CPU占用率 } closegraph(); return 0; }这个结构是一个极简的游戏循环雏形处理输入 - 更新逻辑 - 绘制图形。peekmessage是非阻塞的它不会让程序卡住等待输入适合实时动画。3. 绘制基本图形从点到面有了窗口我们就可以开始画画了。EasyX提供了一系列模仿现实绘图工具的函数非常直观。3.1 设置颜色与样式set系列函数在画画前你得选好画笔的颜色和样式。EasyX使用24位RGB颜色模型通过RGB(r, g, b)宏来定义颜色每个分量取值范围是0-255。setlinecolor(RGB(255, 0, 0)); // 设置当前画线颜色为红色 setfillcolor(GREEN); // 设置当前填充颜色为绿色EasyX预定义了一些颜色常量 setbkcolor(WHITE); // 设置背景颜色为白色setlinecolor影响line,circle,rectangle等绘制边框的颜色。setfillcolor影响fillcircle,fillrectangle等填充图形的内部颜色。setbkcolor设置背景色。注意它不会立即改变窗口背景需要调用cleardevice()清屏时才会用新背景色填充。关于线型除了颜色还可以设置线条样式。setlinestyle(PS_SOLID, 5); // 设置实线5像素粗 // PS_SOLID 实线 PS_DASH 虚线 PS_DOT 点线等这里有个小坑setlinestyle的第一个参数是线型第二个参数是线宽像素。但某些线型如虚线PS_DASH在较宽的线宽下可能显示不正常这是底层GDI的限制。3.2 绘制点、线、矩形、圆这是最核心的一组函数参数命名清晰几乎不需要解释。// 1. 点 putpixel(100, 100, RED); // 在坐标(100,100)处画一个红点 // 2. 直线 line(50, 50, 200, 200); // 从点(50,50)到点(200,200)画一条直线 // 使用当前画线颜色和样式 // 3. 矩形 rectangle(100, 100, 300, 200); // 绘制左上角(100,100)右下角(300,200)的矩形边框 fillrectangle(100, 100, 300, 200); // 绘制并填充矩形使用当前填充色 solidrectangle(100, 100, 300, 200); // 绘制并用当前填充色实心填充无边框 // 4. 圆 circle(300, 300, 50); // 绘制圆心(300,300)半径50的圆边框 fillcircle(300, 300, 50); // 绘制并填充圆 solidcircle(300, 300, 50); // 绘制并实心填充圆 // 5. 椭圆 ellipse(400, 300, 500, 200); // 绘制外接矩形为(400,300)到(500,200)的椭圆边框 fillellipse(400, 300, 500, 200); // 绘制并填充椭圆坐标系统EasyX的坐标原点(0,0)在窗口的左上角。X轴向右为正Y轴向下为正。这一点和数学上的坐标系不同需要时刻记住。窗口大小就是你用initgraph设置的宽和高。一个实用的技巧居中绘制。你经常需要把一个图形画在窗口中央。int centerX getwidth() / 2; // 获取窗口宽度的一半 int centerY getheight() / 2; // 获取窗口高度的一半 int radius 50; circle(centerX, centerY, radius); // 在窗口中心画圆getwidth()和getheight()是EasyX提供的非常实用的函数用于获取当前绘图窗口的尺寸。3.3 多边形、圆弧与贝塞尔曲线基本图形满足大部分需求但有时你需要更复杂的形状。// 1. 多边形 POINT pts[] { {100, 100}, {200, 50}, {300, 100}, {250, 200}, {150, 200} }; int num sizeof(pts) / sizeof(POINT); polygon(pts, num); // 绘制多边形边框 fillpolygon(pts, num); // 绘制并填充多边形 // 2. 圆弧 arc(200, 200, 400, 400, 0, 90); // 绘制椭圆弧外接矩形(200,200)-(400,400)起始角0度终止角90度 // 角度单位是度0度指向东X轴正方向角度增加方向为逆时针。 // 3. 扇形 pie(200, 200, 400, 400, 45, 135); // 绘制并填充一个扇形 // 4. 贝塞尔曲线需要4个控制点 POINT bezierPts[] { {50, 300}, {150, 200}, {250, 400}, {350, 300} }; bezier(bezierPts, 4); // 绘制一条三阶贝塞尔曲线绘制多边形的心得POINT是Windows API中定义的结构体包含x和y成员。确保你的点数组是按顺序顺时针或逆时针排列的否则填充可能会出问题。对于凹多边形EasyX的填充算法能正确处理。4. 在图形窗口中输出文字图形程序离不开文字无论是显示分数、标签还是提示信息。EasyX的文字输出功能同样简单强大。4.1 设置字体属性settextstyle在输出文字前通常要先设置字体。settextstyle(30, 0, _T(宋体)); // 设置字体高30像素宽0表示自动计算字体为“宋体” settextcolor(BLUE); // 设置文字颜色为蓝色 setbkmode(TRANSPARENT); // 设置文字背景模式为透明非常重要settextstyle函数的参数高度字符的像素高度。宽度如果为0则根据高度自动计算宽度保持字体原比例。如果指定值则会拉伸字体。字体名使用_T()宏包裹字符串是为了兼容Unicode编码。你可以换成“黑体”、“楷体”等系统支持的字体。setbkmode(TRANSPARENT)是关键一步如果不设置文字背景会默认用当前背景色填充成一个不透明的矩形块非常难看。设置为透明后文字就只会覆盖像素背景的图形会透过来。4.2 在指定位置输出文字outtextxy这是最常用的文字输出函数。outtextxy(100, 100, _T(Hello, EasyX!)); // 在坐标(100,100)处输出字符串坐标(100,100)指定的是字符串左上角的位置。一个常见需求居中显示文字。这需要计算字符串占据的像素宽度。TCHAR str[] _T(居中文字); settextstyle(40, 0, _T(黑体)); int textWidth textwidth(str); // 获取字符串像素宽度 int textHeight textheight(str); // 获取字符串像素高度 int x (getwidth() - textWidth) / 2; int y (getheight() - textHeight) / 2; outtextxy(x, y, str);textwidth()和textheight()是EasyX提供的辅助函数根据当前字体设置计算字符串的宽高非常实用。4.3 格式化输出与数字显示outtextxy只能输出字符串。如果我想输出一个整数变量score该怎么办这就需要用到C语言的格式化输出函数sprintf_s安全版本或_stprintf_s兼容Unicode。#include stdio.h // 需要包含头文件 #include tchar.h // 用于_tprintf_s int score 95; TCHAR str[64]; // 定义一个足够大的字符数组作为缓冲区 // 方法1使用_stprintf_s (推荐兼容性更好) _stprintf_s(str, _T(得分: %d), score); outtextxy(100, 150, str); // 方法2使用sprintf_s 强制转换如果项目字符集是多字节 // sprintf_s(str, 得分: %d, score); // outtextxy(100, 150, _T(str)); // 需要转换重要提示在Visual Studio的Unicode字符集项目下直接使用sprintf_s和char数组可能会导致编译警告或乱码。使用TCHAR系列类型和函数如_stprintf_s是更稳妥的做法它能根据项目配置自动适配char或wchar_t。5. 综合案例绘制一个简单的仪表盘现在我们把前面所有的知识组合起来做一个有交互感的例子一个模拟仪表盘有一个可变的指针和数值显示。#include graphics.h #include conio.h #include math.h // 用于sin/cos计算角度 #include stdio.h #include tchar.h // 将角度转换为弧度 #define PI 3.141592653589793 double toRadians(double degrees) { return degrees * PI / 180.0; } int main() { initgraph(800, 600, SHOWCONSOLE); setbkcolor(WHITE); cleardevice(); // 仪表盘中心坐标和半径 int centerX 400; int centerY 300; int radius 200; // 仪表盘刻度范围0 ~ 100 double minValue 0.0; double maxValue 100.0; double currentValue 30.0; // 当前值可以用其他逻辑改变 // 1. 绘制仪表盘外圈 setlinecolor(BLACK); setlinestyle(PS_SOLID, 3); circle(centerX, centerY, radius); circle(centerX, centerY, radius - 10); // 2. 绘制刻度线和刻度值 setlinestyle(PS_SOLID, 1); settextcolor(BLACK); settextstyle(20, 0, _T(Arial)); setbkmode(TRANSPARENT); for (int i 0; i 10; i) { double value minValue (maxValue - minValue) * i / 10.0; // 计算刻度对应的角度假设仪表盘从-150度到150度共300度范围 double angle -150.0 300.0 * i / 10.0; double rad toRadians(angle); // 刻度线起点和终点 int startX centerX (radius - 20) * cos(rad); int startY centerY (radius - 20) * sin(rad); int endX centerX (radius - 5) * cos(rad); int endY centerY (radius - 5) * sin(rad); line(startX, startY, endX, endY); // 刻度值文字位置稍微再靠外一点 int textX centerX (radius 10) * cos(rad); int textY centerY (radius 10) * sin(rad); TCHAR valStr[8]; _stprintf_s(valStr, _T(%.0f), value); // 简单居中根据文本宽度微调 int tw textwidth(valStr); outtextxy(textX - tw/2, textY - 10, valStr); } // 3. 绘制指针根据currentValue计算角度 double pointerAngle -150.0 300.0 * (currentValue - minValue) / (maxValue - minValue); double pointerRad toRadians(pointerAngle); int pointerEndX centerX (radius - 30) * cos(pointerRad); int pointerEndY centerY (radius - 30) * sin(pointerRad); setlinecolor(RED); setlinestyle(PS_SOLID, 3); line(centerX, centerY, pointerEndX, pointerEndY); // 在指针根部画一个小圆点 setfillcolor(RED); fillcircle(centerX, centerY, 5); // 4. 在仪表盘中央显示当前数值 settextcolor(BLUE); settextstyle(30, 0, _T(黑体)); TCHAR currentStr[32]; _stprintf_s(currentStr, _T(值: %.1f), currentValue); int tw textwidth(currentStr); outtextxy(centerX - tw/2, centerY - 50, currentStr); // 5. 添加标题 settextcolor(DARKGRAY); settextstyle(25, 0, _T(楷体)); outtextxy(centerX - 60, centerY radius - 50, _T(模拟仪表盘)); _getch(); closegraph(); return 0; }这个案例涵盖了圆形绘制、角度计算、三角函数应用、循环绘制刻度、动态文本输出和居中计算。你可以尝试修改currentValue的值看看指针如何转动。更进一步你可以把它放入一个循环让currentValue根据时间或某个输入变化就变成了一个动态的仪表盘动画。6. 调试技巧与常见问题排查即使是简单的图形程序也会遇到各种奇怪的问题。这里分享几个我踩过的坑和对应的解决方法。6.1 程序一闪而过看不到窗口这是最常见的问题。原因控制台程序执行速度很快main函数执行完就退出了。解决在closegraph()之前加上一个等待用户输入的函数让程序暂停。#include conio.h // ... 你的绘图代码 ... _getch(); // 等待用户按下一个键 closegraph();或者使用更友好的消息循环模式如第2.3节所示。6.2 文字显示为方框乱码原因1字体名称不对或系统未安装该字体。settextstyle中指定的字体必须是系统中存在的。解决使用通用字体如“宋体”、“黑体”、“Arial”。对于英文字符_T(“Arial”)基本是安全的。原因2字符编码问题。在Unicode项目中使用char字符串或在多字节项目中使用wchar_t字符串。解决统一使用TCHAR类型和_T()宏来定义字符串使用_stprintf_s等通用函数。检查项目属性 - 配置属性 - 高级 - 字符集确保你的代码风格与之匹配。6.3 绘制的内容被清屏覆盖或闪烁原因绘制顺序问题或者在动画循环中cleardevice()的位置不对。解决遵循标准的“清屏 - 绘制 - 显示”顺序。在动画循环中通常这样写while (true) { cleardevice(); // 1. 清空缓冲区 // ... 绘制所有图形 ... // 2. 在缓冲区绘制新帧 FlushBatchDraw(); // 3. 将缓冲区内容刷新到屏幕如果用了批量绘制 // 或者如果没使用批量绘制EasyX会在下一次cleardevice或循环结束时自动刷新 Sleep(10); // 4. 短暂延迟 }使用BeginBatchDraw()和EndBatchDraw()/FlushBatchDraw()可以更好地控制双缓冲的刷新时机减少闪烁。6.4 运行时提示“找不到graphics.h”或链接错误原因EasyX库没有正确安装或项目配置不对。解决确认安装的EasyX版本与你的Visual Studio版本匹配。确认创建的是“Win32控制台应用程序”项目而不是“空项目”或“Windows桌面应用程序”。尝试重新运行EasyX安装程序。在极少数情况下可能需要手动在项目属性 - 链接器 - 输入 - 附加依赖项中添加EasyXw.lib对于Unicode或EasyX.lib对于多字节。6.5 如何获取鼠标和键盘的实时状态peekmessage主要用于处理窗口消息如按键、鼠标点击。对于需要实时、连续检测的状态如“是否一直按着右键”EasyX提供了更直接的函数鼠标GetMouseMsg()。这个函数会等待并返回一个鼠标消息结构体适合在循环中获取连续不断的鼠标操作。MOUSEMSG msg; while (true) { msg GetMouseMsg(); if (msg.uMsg WM_MOUSEMOVE) { // 鼠标移动到了 (msg.x, msg.y) } if (msg.uMsg WM_LBUTTONDOWN) { // 在 (msg.x, msg.y) 处按下了左键 } }键盘GetAsyncKeyState()。这是Windows API可以检测某个键当前时刻是否被按下。#include windows.h if (GetAsyncKeyState(VK_SPACE) 0x8000) { // 空格键被按下了 }注意GetAsyncKeyState的返回值是一个短整型检查最高位0x8000可以判断按键是否正被按住。掌握了创建窗口、绘制图形、输出文字和基本的交互处理你已经能用EasyX做出很多有趣的东西了。从静态的图表到动态的小游戏核心就是不断地在循环中更新数据、清除旧画面、绘制新画面。接下来你可以尝试学习如何加载和显示图片、播放音乐那将为你的程序增添更多色彩。
返回列表