简介:为 Visual C++ 6.0 及兼容环境准备的 graphics.h 头文件与 Graphics.lib 静态库资源包,主要面向需要在 C/C++ 控制台工程中调用绘图函数(如画线、画圆、区域填充)的开发者。压缩包内共 4 个文件,包含 Graphics.h、Graphics.lib 两个头文件和两个链接库文件,包体仅 46KB,轻量且便于直接加入传统项目。头文件提供绘图接口的函数声明,库文件供链接器完成图形功能实现,二者配合使用即可恢复经典 Graphics 编程模式。将文件按说明分别放入 VC98 的 Include 与 Lib 目录后,可免去手工配置依赖的环节,快速适配早期教材或课程设计中的绘图代码。已有 9183 人学习或下载,表明这份补丁在维护旧版 Visual Studio 工程、考试复习与图形学入门场景中具有较高实用价值。下载后可直接获得完整头文件与库文件,适合初学者解决 graphics.h 缺失报错,也能为需要复现老项目的开发者提供即用型支持。
1. graphics.h 还在被搜索:一个比多数读者年龄还大的图形接口
graphics.h 可能是很多人第一次接触图形编程时遇到的头文件:在网络里搜“C语言画图”,十有八九会被指到它,结果下载、配置、编译一路下来,先撞上 “No such file”,再撞上 “undefined reference”。它的真实身份是一套三十多年前图形库的接口声明,不属于 C/C++ 标准库,因此任何现代编译器默认都不带;但它的画直线、圆、矩形、填充、文字这套 API,至今仍出现在不少高校的 C 语言实践和课程设计里。这篇笔记适合两类人:被要求用 graphics.h 做课设的初学者,以及拿到一份老代码却在新环境里编译不过、想快速移植排错的在职开发者。下文按“环境配置、函数拆解、踩坑排查、组合实战、验证技巧”展开。
2. 环境配置:让 graphics.h 在现代编译器下跑起来的三种路线
2.1 路线一:Turbo C++ 3.0 里原汁原味的老环境
graphics.h 出身于上世纪八十年代末的 Turbo C / Turbo C++ 工具链,配套的还有 EGAVGA.BGI 驱动文件和图形库实现。如果你的课程设计明确要求 Turbo C++ 3.0,那反而是最省事也最“还原”的路线:这套环境自带头文件和库文件,不需要额外下载任何兼容层。常见做法是先把环境安装到一个路径干净的目录,最好别带空格,然后打开主菜单里的 Options → Directories,把 include 和 lib 两个路径分别指到系统目录,确认 graphics.h 所在的 include 目录被加进搜索路径,图形库文件被加进 lib 搜索路径。
装好之后,不要急着写画图逻辑,先跑通一个最小初始化程序:
#include <graphics.h> #include <conio.h> int main() { int gd = DETECT, gm; initgraph(&gd, &gm, ""); outtextxy(100, 100, "hello graphics.h"); getch(); closegraph(); return 0; }这段代码里,initgraph 是进入图形模式的大门,第三个参数是 BGI 驱动目录。Turbo C 下通常要写成类似 “C:\TC\BGI” 的绝对路径,或者保证 EGAVGA.BGI 就在当前工作目录,传空字符串也能碰到。如果你拿到一个别人给的程序,里头第三个参数指向某个具体路径,但你的环境里没有那个目录,initgraph 就会失败,返回值非 0,图形窗口根本出不来。第一次跑通后别急着加功能,多换几个路径试几次,确认驱动文件能跟随程序一起走。
在 Turbo C 里编译用的是 Alt+F9,运行是 Ctrl+F9,要回看图形屏幕按 Alt+F5,别按成 Alt+F4。很多初学者在这套老工具链上还有个认知负担:它是 16 位时代的程序,在较新系统上运行偶尔会闪退或花屏,这时可以在系统属性里打开兼容模式再跑一次。我的态度很明确:老环境适合跑考试题、跑原汁原味的教学代码,但别拿它做超大工程,一旦工程文件超过几十个文件,管理起来真的很痛苦。
注意:Turbo C 的 BGI 路径里如果带空格(比如装在 Program Files 下),老程序可能找不到驱动,优先装在没有空格的目录,这个习惯能省掉一半初始化失败问题。
2.2 路线二:WinBGIm 把 graphics.h 接到现代编译器上
今天绝大多数人并不会去装 Turbo C,而是用 Visual Studio 或 Code::Blocks 这类新工具链,此时最实用的方案是 WinBGIm——它把 graphics.h 的接口重新实现到 Windows GDI 上,让你在新编译器里继续用同一套函数名。安装过程说穿了就是把两个东西放到编译器能找到的位置:graphics.h(以及它依赖的 winsize.h)放进 include 目录,libbgi.a 或对应静态库放进 lib 目录。听起来简单,但这一步非常容易翻车,因为光放文件是不够的,编译器链接时根本不会自动带上这个图形库。
编译命令必须显式给出库名:
gcc main.c -o main -lbgi -lgdi32 -lcomdlg32 -luuid -loleaut32 -lole32其中 -lbgi 是 WinBGIm 提供的兼容层本体,后面四个是 Windows 系统库:gdi32 负责最基础的画图操作,comdlg32 负责颜色和文件对话框,uuid、oleaut32、ole32 是兼容层内部初始化 COM 环境时依赖的系统库。少了任意一个,你大概率会在链接阶段看到一堆 unresolved external symbol。我一般会把这组参数直接写进项目的编译说明里,而不是指望 IDE 记住,因为 IDE 换个版本、换台机器,这些配置就全丢了。
有一个容易混淆的细节:WinBGIm 的 initgraph 第三个传路径的参数已经被忽略,所以传 "" 和传 "C:\BGI" 效果完全一样。如果你是从 Turbo C 老代码转过来,看到别人的代码里写着一长串路径,大可以删掉,不影响行为。反过来,如果你把 WinBGIm 的代码拿回 Turbo C 跑,第三个参数反而是关键,必须指向驱动目录。这套双向反着踩的坑,我在排错时见过不止一次。
2.3 路线三:Dev-C++ 环境下的快速配置
Dev-C++ 在学校里仍然很常见,但不同版本差异很大。有的发行版直接把 WinBGIm 内置了,新建项目就能 include <graphics.h>;有的则是纯粹的 MinGW 环境,什么都不带。拿到一个新环境,我一般会先写一个只包含头文件和空 main 函数的文件,编译一次探路。如果头文件都找不到,就按 2.2 节的方式把 WinBGIm 的两个文件加进对应目录,再在编译器选项里把链接库参数填进“链接器”一栏。
Dev-C++ 的坑在于图形窗口和控制台共用同一个进程,程序退出时图形窗口可能来不及显示就被关闭。很多人在这个环境里第一次跑 initgraph 成功,却什么都没看到,以为代码有问题,实际上只是窗口闪现了一下就没了。碰到这种情况,先用一个空循环把窗口保持几秒,确认绘图逻辑没问题之后,再改成正常的等待按键退出。调试图形程序,优先级永远是“先看到东西,再谈交互”。
3. 核心 API 拆解:从 initgraph 到填充函数的参数边界
3.1 initgraph 与图形模式:驱动检测、窗口尺寸与失败判断
initgraph 是进入图形模式的第一道门,声明是:
int initgraph(int *gdriver, int *gmode, const char *path);gdriver 传 DETECT 时,库会自动检测显卡并选择合适的模式和分辨率,同时把实际使用的驱动号回填到 gdriver、模式号回填到 gmode。很多初学者在这里有个误读:以为 gdriver 和 gmode 只是输出参数,随便定义两个整数变量传进去就行。实际上它们既是输入也是输出。在 Turbo C 下,path 必须指向 BGI 驱动文件(EGAVGA.BGI 等)所在目录;在 WinBGIm 下,这个路径被忽略,传 "" 即可。
我见到最多的翻车现场是:在 WinBGIm 里习惯了第三个参数传 "",回到 Turbo C 里也照抄,结果驱动文件又不在当前目录,初始化直接失败。判断失败有两个标志:一是函数返回值不为 0,二是 gdriver 被回填成错误码。常见做法是初始化后立刻判断返回值,不要把 initgraph 的返回值丢掉。另外,graphics.h 的坐标系原点在窗口左上角,x 轴向右,y 轴向下,不是数学课上习惯的笛卡尔坐标系。这个细节决定了后面所有图元的摆放位置,很多动态图形的方向“反转”问题都出在这里。
注意:graphics.h 的 y 轴向下,和数学坐标系相反。如果你画一条“向上”的线,传入的两个 y 值反而是递减的;做圆周运动时,屏幕上的运动方向也会和直觉相反。
3.2 基本图元:直线、矩形、圆与椭圆的坐标约定
graphics.h 提供了一组非常朴素的图元函数,参数基本都是像素坐标。画直线用 line(x1, y1, x2, y2),线条颜色由当前的 setcolor 决定;矩形用 rectangle(left, top, right, bottom),这是空心矩形,只描边不填充;想画实心矩形用 bar(left, top, right, bottom);圆用 circle(x, y, radius),radius 是像素半径;椭圆用 ellipse(x, y, start_angle, end_angle, xradius, yradius),角度是度单位的起止角,0 到 360 就是完整椭圆。
#include <graphics.h> #include <conio.h> int main() { int gd = DETECT, gm; initgraph(&gd, &gm, ""); setcolor(RED); line(50, 50, 200, 50); /* 两点定一条直线 */ setcolor(GREEN); rectangle(50, 80, 200, 200); /* 左上角与右下角 */ setcolor(YELLOW); circle(320, 150, 60); /* 圆心加像素半径 */ setcolor(CYAN); ellipse(500, 150, 0, 360, 80, 40); /* 完整椭圆 */ getch(); closegraph(); return 0; }这段代码的意图很简单:用四种不同颜色的图元占住屏幕四个位置,验证坐标方向。我一般会让初学者原样跑一遍,再改几个坐标值,观察图形往哪个方向移动,把 y 轴向下的“恶心感”消除掉。还有一点值得注意:line、circle 这类函数的坐标可以超出窗口边界,超出部分会被库自动裁剪,不会报错。但如果你做碰撞检测或边界判断,得自己写代码限制范围,因为库不会告诉你哪部分被裁掉了。
3.3 颜色系统与填充:setcolor、setfillstyle、floodfill 的行为差异
graphics.h 的颜色是 16 色索引,从 BLACK 到 WHITE 一共 16 个枚举。setcolor 管的是画笔颜色,也就是线和描边的颜色;setbkcolor 管背景色,但改完背景色之后,必须再调一次 cleardevice() 才能真正把屏幕刷成新颜色。很多人在这里踩坑:设完背景色发现屏幕没变,就开始怀疑函数坏了,其实只是没有清屏。
填充的逻辑稍微绕一点。setfillstyle(pattern, color) 只设置填充样式和填充色,并不会立刻画什么;真正触发填充的是 floodfill,或者 bar 这类自带填充语义的函数。floodfill(x, y, border) 从坐标点向四周扩散填充,遇到颜色等于 border 的像素就停下来,所以第三个参数必须是边界线条的颜色,不是填充色。如果起始点已经落在边界色上,填充会直接失败;如果边界不封闭,填充会漏出去填满整个窗口甚至更糟。bar 则不需要边界,直接以当前 fillstyle 画一个实心矩形,适合做柱体和色块背景。
setfillstyle(SOLID_FILL, RED); bar(50, 220, 200, 260); /* 实心矩形,立刻显示 */ setfillstyle(HATCH_FILL, BLUE); floodfill(100, 100, GREEN); /* 从内部点开始,遇绿色边界停止 */文字输出是另一个常被忽略的功能。outtextxy(x, y, "text") 在指定像素位置输出文本,settextstyle(font, direction, size) 设置字体、方向(HORIZ_DIR 水平、VERT_DIR 垂直)和字体大小。这里要提前打好预防针:graphics.h 的年代太早,默认字体对中文支持很差,硬输出 UTF-8 中文就是乱码或半个字符。编码问题的详细处理放在第 4.4 节展开,这里只需记住,调试文字输出时先试英文,排除编码干扰后再处理中文。
4. 避坑与排查:graphics.h 移植失败的五个典型现场
4.1 编译报错 graphics.h: No such file
现象:新环境里第一行 include 就红,编译器直接提示找不到 graphics.h 文件。
原因:graphics.h 不是标准头文件,你的工具链 include 目录里根本没有它。这个问题在换了编译器、换了机器、或者换了项目目录结构之后最容易出现。
解决:先确认工具链的 include 目录在哪,把头文件放进去,或者编译时用 -I 参数指定头文件所在目录。Turbo C 下则检查 Options → Directories 的 include 配置是否正确。别把文件只放在源码目录就不管了,编译器的搜索路径未必包含当前目录,尤其是使用 IDE 时,当前目录常常不是项目文件所在目录。
提示:如果你用的是 IDE 而不是命令行,编译时找不到头文件但命令行能编译通过,基本可以判断是 IDE 的 include 路径没加进去,去工程属性里补路径。
4.2 链接阶段报错 undefined reference to initgraph 这类符号
现象:头文件能 include 了,也能编译,但链接时报出一串 undefined reference toinitgraph之类的错误。
原因:头文件只是声明,图形库的实现还在某个 .a 或 .lib 文件里,链接器根本没找到图形库。WinBGIm 下就是忘了第 2.2 节那一串 -l 参数;在 Visual Studio 下还可能是 libbgi.a 和 MSVC 工具链不兼容,因为 VS 默认不认 GNU 风格的 .a 库。
解决:把第 2.2 节的链接参数完整加上;用 MSVC 时要么换 MinGW 工具链,要么把 .a 转成 .lib。我自己的排查顺序是:先看报错里有没有 bgi 字样,有则说明头文件到位但库没链上;没有则回头看 include 配置。这类问题多半集中在“换了 IDE 但没换链接参数”,所以我强烈建议在每个项目根目录放一个编译脚本,而不是依赖 IDE 的图形化设置。
4.3 窗口一闪而过,看不到图形
现象:编译、链接、运行都“成功”,但图形窗口刚打开就没了,屏幕上什么都没看清。
原因:程序执行完 initgraph 和绘图后直接走到 closegraph 退出,整个生命周期不到几十毫秒。这是老库交互模式的遗留问题,它不像现代 GUI 框架有消息循环在后台撑住窗口。
解决:在 closegraph 之前加 getch(),让程序等待一次按键再退出。不要用 system("pause"),它会把控制台和图形窗口混在一起等待,行为不稳定,在某些环境下反而会让图形窗口先被关闭。如果你只是想预览效果不想按键,也可以用一个空循环自旋几秒,但这时程序不会主动退出,调试完记得关掉。我一般用 getch(),因为它同时是 conio.h 的标准等待,跨编译器都支持。
4.4 中文乱码与 printf 混用冲突
现象:outtextxy 输出的中文是乱码,或者图形窗口没问题,但控制台输出和图形窗口交错在一起,画面乱成一团。
原因:outtextxy 接收的是单字节字符,Windows 下中文按 GBK 编码才正常,而现代编辑器默认把源文件存成 UTF-8。两者一碰,中文字符就被拆成了两段乱码。printf 输出到控制台,outtextxy 输出到图形窗口,两个通道互不相通,混用时会干扰你的观察。
解决:把源码文件另存为 GBK 编码再编译,这是最省事的方案;或者干脆用英文界面,减少编码纠纷。如果项目确实要求中文,建议在代码里封装一个统一输出函数,把 UTF-8 字符串转成 GBK 后再交给 outtextxy,避免在每个调用点处理编码。我个人的习惯是:教学 Demo 一律用英文,真要做产品化再谈多语言,省下的时间足够多画三个图了。
4.5 floodfill 填充失败:虚线与不封闭的边界
现象:floodfill 之后颜色不是填不满,就是填满整个窗口,图形直接毁容。
原因:填充起点落在边界像素上;或者边界颜色与传入的 border 参数不一致;更常见的是边界用 DOTTED_LINE 或 DASHED_LINE 画成了虚线,物理上存在缺口,填充液会从缺口漏出去。
解决:填充起点选在区域中心偏一点的位置,确保不落在边界;边界统一用 SOLID_LINE,并且让 setcolor 的颜色和 floodfill 第三个参数严格一致。理解这个函数有个生动的办法:它像一滴墨水从起点扩散,边界必须是一堵连续的颜色墙,任何一处断口都会让墨水漏到外面。我自己调这类问题时,会把边界临时设成红色,填充色用蓝色,一眼就能看出漏点在哪,修好再改回正式颜色。
5. 组合实战:用 graphics.h 写一个动态的小球轨迹演示
5.1 需求拆解:从静态图形到动态效果
函数一个个学会之后,得把它组合成一个能演示的东西。这里我选一个“沿轨道运动的小球”作为完整示例:表面上演示的是圆周运动,实际上它把坐标系统、图元、颜色、等待退出这几件事全部串了起来。需求拆成三块:第一,画一条轨道,让人看到运动是有基准的;第二,小球按角度逐步移动,每步刷新;第三,支持按键退出,而不是跑完就关。
角度从 0 到 360 度步进,每步通过三角函数计算出圆周上的像素坐标,画一个新位置的小球,延迟一小段时间,再想办法把旧位置擦掉,这样视觉上小球就在动。旧位置的擦除是这段代码里最容易出问题的地方,我会单独拿出来说清楚。
5.2 编码实现:核心循环与擦除细节
#include <graphics.h> #include <conio.h> #include <math.h> #ifndef M_PI #define M_PI 3.1415926535 #endif int main() { int gd = DETECT, gm; int cx = 320, cy = 240, r = 120; int angle, px, py; initgraph(&gd, &gm, ""); setcolor(WHITE); circle(cx, cy, r); /* 先画轨道 */ for (angle = 0; angle <= 360; angle += 5) { px = cx + (int)(r * cos(angle * M_PI / 180)); py = cy + (int)(r * sin(angle * M_PI / 180)); setcolor(RED); circle(px, py, 8); /* 新位置画球 */ delay(30); setcolor(BLACK); circle(px, py, 8); /* 用背景色擦旧球 */ } getch(); closegraph(); return 0; }这段代码踩了两个看不见的坑,我提前说明。一个是 math.h 在部分 Windows 编译器上不定义 M_PI,所以我在 include 之后补了一段条件编译兜底,这个细节在移植老代码时经常遇到;另一个是擦除法:用 BLACK 重画旧球,看起来像是旧球被背景色盖掉了。这里隐含一个假设——背景色是黑色。如果背景被 setbkcolor 改成了其他颜色,这种擦除法就会失败,旧球会残留下来形成一条拖影。
更稳妥的做法是用 cleardevice() 清屏后重绘整帧,但那样会闪屏。graphics.h 没有原生的双缓冲,实时动画只能靠局部重绘或者接受闪屏,这是这个老库在交互效果上的硬边界。我一般建议学生先把上面这段跑通,再改成整帧重绘,对比两种方案的闪烁程度,理解“重绘区域越小,闪烁越轻”这个基本规律。动画帧率上,delay(30) 大约能到 20 帧上下,低于 10 毫秒的 delay 在 Windows 定时器精度下并不精确,不用刻意追求高帧率。
5.3 编译验证:从编译到观察运行行为
把 main.c 保存后,用第 2.2 节的命令编译一次:
gcc main.c -o demo -I./include -L./lib -lbgi -lgdi32 -lcomdlg32 -luuid -loleaut32 -lole32运行之后观察三点:轨道是否完整;小球是否按逆时针方向移动;退出时是否能正常关闭图形窗口。如果小球运动方向和你的预期相反,那不是代码逻辑坏了,而是 y 轴向下的坐标系导致的视觉反转。想要顺时针,可以把角度步进从加法改成减法。如果想要验证 M_PI 的坑是否真实存在,把上面条件编译删掉,再用只定义标准库的方式编译,你的编译器大概率会给你一个名正言顺的报错。
6. 收在验证技巧上:用一条编译命令把翻车率压下来
graphics.h 项目的大多数翻车都发生在换环境这一步:头文件路径不对、链接库缺参数、字符编码出错。这三类问题有一个共同点——它们是环境问题,不是业务代码问题。如果每次换环境都靠人肉排查,会浪费大量时间。所以我强烈建议把编译、链接、运行三件事合成一条固定命令,而不是依赖 IDE 按钮。
gcc main.c -o main \ -I./include -L./lib \ -lbgi -lgdi32 -lcomdlg32 -luuid -loleaut32 -lole32 \ && ./main这条命令分成三段来看:-I 和 -L 指定了头文件与库文件的位置,是解决 “No such file” 的关键;五组 -l 参数补齐了链接依赖,是解决 “undefined reference” 的关键;最后的 && 保证只有编译和链接都成功时才真正运行程序,避免你盯着一份编译失败的残壳浪费时间。调试时可以把它拆开执行:先看有没有 “No such file”,再看有没有 “undefined reference”,两类错误互不混淆。
命令行不适合所有人,但至少可以做一个 build.sh 或 Makefile 放在项目根目录,新手复现时不会被 IDE 的选项界面劝退。我在一次外部协作里遇到过代码能跑但编译依赖全靠 IDE 记忆的项目,后来把编译命令固化成脚本,另一个开发者在完全不同的环境里也一条命令出了结果。从那以后,我每次拿到一个新的 graphics.h 项目,第一件事不是读源码,而是先跑一遍编译命令确认图形库链路是通的,再去看业务代码。这个习惯帮我省下了大量“以为是代码问题、实际是环境问题”的排查时间,希望帮到你。
本文还有配套的精品资源,点击获取