简介:使用C语言(含C++写法)实现的控制台表白小项目,面向编程初学者、嵌入式/系统开发入门者,以及想用代码制造浪漫的同学;它以极简源码演示了如何用循环、延时、屏幕输出来生成动态文字和闪烁效果,适用于课堂实验、课程设计或趣味编程练习。整个资源压缩后仅4KB,包含1个cpp源文件,没有多余素材,直接查看代码就能上手,便于学习。目前已有2333人学习或下载,属于轻量热门的小型示例。代码集中展示了字符动画的常见做法:通过循环次数控制延时帧率,逐字打印表白内容,借助Windows API实现颜色与闪烁效果,同时保持程序结构清晰、无毒无副作用;运行后即可看到动态展示,修改文本或循环参数便能定制为自己的表白页面,对理解C/C++的程序流程、时间控制和控制台交互很有帮助。1. 表白代码为什么讲究“无毒”,C/C++写的到底靠不靠谱在技术圈搜“表白代码”,你能看到两种极端:一种是三行 printf 的敷衍 demo,另一种则“毒性”拉满——弹窗刷屏、假关机、死循环逼你输入 yes,甚至悄悄改注册表。这哪是表白,分明是当众把人架在火上烤。真正值得投入时间写的表白代码,应当干净、纯粹、零高危调用,编译成一个单文件 exe,双击就能跑,不依赖任何运行时和联网环境。C 语言做控制台动画,C++ 做图形窗口,正好覆盖从“刚学完指针的萌新”到“想把心意做成正经程序”的开发者。下面直接给出两套能跑通的方案,把原理、代码、编译命令和最容易翻车的几个坑一次讲透。2. C语言表白程序:在控制台做一颗会跳的心2.1 心跳动画的原理:帧、清屏与延时,控制台怎么做“会动”控制台没有真正的动画,它只能展示静态字符。所谓动画,就是“清掉上一帧、画上下一帧”的快速切换。清屏用system("cls"),画帧用printf,控制节奏用Sleep(),这三样组合起来就是控制台动画的全部。很多新手一上来就追求高帧率,结果画面狂闪,观感还不如一张静态图。控制台动画的正确思路是“用帧间隔表达节奏”,而不是“跑得够快”。心跳的真实节奏是“咚—哒”,第一下重、第二下轻,中间有微弱停顿。用代码模拟时,第一下画完心形后等 400 到 500 毫秒,第二下等 150 到 200 毫秒,一组心跳就完成了。这个间隔来自你对自己心跳的感知,写代码的时候可以一边按脉搏一边调参数,调出来的节奏才像真的心跳,而不是两个等长的闪烁。心形本身不建议用一串写死的字符拼图,字符一旦换终端宽度就容易错位。更稳的做法是用数学公式实时扫描:心形公式(x² + y² - 1)³ - x²y³ ≤ 0,只要某个坐标代入后成立,就输出一个星号,否则输出空格。控制台终端在屏幕上逐行扫描这个公式,天然就是一颗可以调整大小、不会散架的心。2.2 可直接抄的C语言版本:完整代码与参数解释/* * love.c * C语言控制台版表白程序 * 编译:Windows 下用任意 C 编译器(VS / MinGW / Dev-C++)直接编译 */ #include <stdio.h> #include <windows.h> /* 用数学公式画出爱心,打印在控制台上 */ void draw_heart() { for (double y = 1.1; y > -1.1; y -= 0.1) { /* 逐行扫描 */ for (double x = -1.2; x < 1.2; x += 0.05) { /* 逐列扫描 */ double a = x * x + y * y - 1; double v = a * a * a - x * x * y * y * y; putchar(v <= 0 ? '*' : ' '); /* 在公式内部就打星号 */ } putchar('\n'); } } /* 逐字输出文字,模拟打字机效果 */ void type_text(const char *s) { while (*s) { putchar(*s); fflush(stdout); /* 立即刷出,否则会积在缓冲区 */ Sleep(60); /* 每个字符间距 60ms */ s++; } } int main() { /* 第一段:三组心跳,每次两组,模拟“咚哒” */ for (int i = 0; i < 3; i++) { system("cls"); draw_heart(); printf("\n I LOVE YOU\n"); Sleep(450); /* 第一下,间隔稍长 */ system("cls"); draw_heart(); printf("\n I LOVE YOU\n"); Sleep(180); /* 第二下,间隔短,形成心跳节奏 */ } /* 第二段:清屏后逐字打出想说的话 */ system("cls"); type_text("写这段程序的时候,我一直在改你名字的拼音。\n"); type_text("后来发现,不用改,你的名字在每一行注释里。\n\n"); type_text("所以……愿不愿意,让我在你的 main 函数里\n"); type_text("永远占一个位置?\n\n"); printf("\n —— 等你回复的某开发者\n"); getchar(); /* 防止窗口一闪而过 */ return 0; }这段代码分两段讲。第一段是心跳:draw_heart()用公式实时计算整颗心,清屏后立刻重画,配合两个Sleep()的间隔产生跳动感。第二段是打字机效果:type_text()每次只输出一个字符并停顿 60 毫秒,配合fflush(stdout)强制刷新,让文字一句一句“打”出来,比一次性打印更有临场感。参数调节集中在三处。第一处是draw_heart()里的y -= 0.1和x += 0.05,它们决定心形的密度和宽高比:终端字符本身是“高约等于两倍宽”的,所以纵向步长要比横向大一些,心形才不会被压扁。第二处是心跳节奏,Sleep(450)和Sleep(180)分别对应“咚”和“哒”,想更沉稳就都加 80,想更活泼就都减 50。第三处是type_text()里的Sleep(60),这是打字速度,中文阅读舒适区间在 50 到 100 毫秒,太快像机器,太慢让人着急。2.3 把文字换成你自己的话:编码、节奏、配色三个参数第一步是解决中文乱码。printf("我喜欢你")显示成乱码,通常是源文件编码与终端代码页不一致导致的:源文件是 UTF-8,终端却是 GBK,两边就翻译错位。最简单的方法是在main()第一行加一句SetConsoleOutputCP(65001);,并且把源文件保存成 UTF-8 编码。如果你的编译器默认存成 GBK,就把参数改成SetConsoleOutputCP(936);,语句放在main()开头,中文字符就能正常显示了。第二步是替换表白文案。直接改main()里type_text("...")的内容就行,建议控制在三到四句,语气越像日常说话越好。真正加分的是藏彩蛋:把对方的名字、你们第一次见面的日期,写进代码的注释里。对方如果打开源码看到这些细节,会比看到动画更触动。第三步是给爱心上色。默认星号是灰色,可以在draw_heart()调用之前加一句SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_INTENSITY);,之后输出的星号就变成亮红色。FOREGROUND_RED是 4,FOREGROUND_INTENSITY是 8,合起来是 12;同理绿色是 2,蓝色是 1,想调成其他颜色就组合这几个值。3. C++表白程序:用Windows API弹出一个爱心窗口3.1 为什么要挑Windows自带的GDI,而不是第三方图形库C++ 做图形界面,很多人第一反应是引入第三方图形库,常见的有 EasyX、SDL、SFML。这些库确实好用,但也带来一个问题:对方电脑上未必装了运行时,或者收到一个压缩包里有 exe、DLL、字体文件好多个,满怀期待解开一看反而犯嘀咕。表白代码最重要的是让对方“双击就能看”,任何额外依赖都会增加怀疑成本。Windows 系统自带的 GDI(图形设备接口)不需要装任何库,编译器也原生支持。它藏在系统里,编译出来的就是一个单文件 exe,不需要联网,不需要安装,双击直接弹窗口。它的边界也很清楚:只适用于 Windows 平台。如果你的目标对象用 macOS 或 Linux,那需要换方案;但就“用 C++ 做表白程序”这个场景来说,绝大多数目标对象用的都是 Windows,所以选 GDI 是最稳妥的路径。3.2 画一颗走心的爱心:窗口版完整代码/* * love.cpp * C++ 图形窗口版表白程序 * 依赖:仅 Windows 系统自带 GDI,无第三方库 */ #include <windows.h> /* 窗口过程:负责绘制爱心和文字 */ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); /* 1) 创建红色画刷并选入设备上下文 */ HBRUSH brush = CreateSolidBrush(RGB(220, 20, 60)); HBRUSH oldBrush = (HBRUSH)SelectObject(hdc, brush); /* 2) 两个椭圆拼出爱心的两个“肩” */ Ellipse(hdc, 70, 70, 220, 190); /* 左半圆 */ Ellipse(hdc, 180, 70, 330, 190); /* 右半圆 */ /* 3) 一个三角形兜底,形成心尖 */ POINT pts[] = { {85, 165}, {315, 165}, {200, 350} }; Polygon(hdc, pts, 3); SelectObject(hdc, oldBrush); DeleteObject(brush); /* 4) 在窗口下方写表白文案:白字、透明背景 */ SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, RGB(255, 255, 255)); RECT rc = { 0, 390, 400, 460 }; DrawTextW(hdc, L"你愿意做我的女朋友吗?", -1, &rc, DT_CENTER | DT_VCENTER); EndPaint(hwnd, &ps); return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { /* 注册窗口类 */ WNDCLASSW wc = {}; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.hCursor = LoadCursorW(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = L"HeartWindow"; RegisterClassW(&wc); /* 创建固定大小的窗口,禁止拉伸以防止爱心变形 */ HWND hwnd = CreateWindowW( L"HeartWindow", L"一封来自代码的信", WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, 400, 500, NULL, NULL, hInstance, NULL); /* 播放心跳音效:低音“咚” + 高音“哒”,循环 4 组 */ for (int i = 0; i < 4; i++) { Beep(120, 180); Beep(160, 90); } ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); /* 消息循环:保证窗口不退出 */ MSG msg; while (GetMessageW(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessageW(&msg); } return 0; }这段代码的核心在WM_PAINT里。先创建一个红色画刷,选入设备上下文,然后用两个椭圆拼出爱心上方的两个圆弧,再用一个三角形兜出心尖。三角形上边的 y 坐标是 165,而两个椭圆的最低点到了 190,三角形上边会切掉椭圆底部一小段弧线,拼出心形顶部那个标志性的内凹,不会出现明显拼缝。WinMain负责注册窗口类、创建窗口、循环播放心跳音效,然后进入标准消息循环。参数调节也集中在三处。心形的宽度由两个椭圆水平方向控制:左椭圆Ellipse(hdc, 70, 70, 220, 190)的圆心在(150, 130),右椭圆圆心在(255, 130),两个圆心水平距离 105。想加宽就把右椭圆左边界调小,比如从 180 改成 160;想收窄就往大调。心尖的长度由三角形底部顶点{200, 350}控制,改成 380 心会更尖,改成 320 会更圆润。颜色RGB(220, 20, 60)是带蓝调的绯红,比纯红耐看,也可以换成对方喜欢的颜色,比如樱粉色RGB(255, 182, 193)。Beep那段是心跳音效,Beep(120, 180)是低音“咚”,Beep(160, 90)是高音“哒”,循环 4 组。需要注意Beep在系统没有音频设备或音频服务未启动时会直接返回失败,但不会崩溃,最多就是安静地弹窗,不影响程序运行。3.3 编译成单文件exe:VS与MinGW的参数对照用 Visual Studio 的话,操作路径是:新建一个“空项目”,添加love.cpp,然后右键项目进入“属性”,把“链接器 → 系统 → 子系统”从“控制台”改成“Windows”。这个改动很关键,不改的话运行时会先弹一个黑色控制台窗口,惊喜感直接减半。设置完成后按生成,得到一个 exe。如果你习惯命令行,用 MinGW 的g++编译是另一种常见做法:g++ love.cpp -o love.exe -mwindows -lgdi32 -luser32这条命令里-mwindows告诉链接器这是一个 GUI 程序,运行时不要创建控制台窗口;-lgdi32链接图形设备接口库,Ellipse、Polygon、BeginPaint这些绘制函数都依赖它;-luser32链接用户接口库,CreateWindowW、Beep、MessageBox在这里面。三个参数缺一不可,少一个都会在链接阶段报一堆无法解析的外部符号。发布给对方的版本只用 Release,不用 Debug。Debug 版体积大,而且有些编译器版本会把调试运行库一起依赖进去,对方机器上没有就会直接报“缺少 DLL”。编译完成后可以检查一下:整个目录里只有一个 exe,这才是“无毒”交付的标准形态。4. 表白程序避坑指南:5个最容易翻车的地方4.1 双击exe后黑窗口一闪而过现象:双击运行,屏幕刚闪一下,窗口就消失了,对方还没看清内容,程序已经退出。原因:main()函数执行到最后的return 0时进程立即结束,控制台窗口跟着关闭。很多人自己在 IDE 里按 Ctrl+F5 运行没问题,因为 IDE 会自动加一个“按任意键继续”的暂停;但直接双击 exe 就不会有这个暂停了。解决:在main()结尾、return 0之前加一行getchar();,程序会一直停到对方按下回车。如果前面已经用了type_text()逐字输出,就放在所有文字打印完之后。这一步是所有控制台版表白程序都必须做的,不加等于白写。4.2 中文字符全变乱码现象:printf("我喜欢你")在控制台里输出成一堆乱码,像“鎴戝枩娆綘”这样。原因:源文件的编码方式与终端当前代码页不一致。比如源文件保存成了 UTF-8,而终端代码页还是默认的 GBK(936),中文被拆成字节后按另一种表去翻译,就变成乱码。解决:在main()开头调用SetConsoleOutputCP(65001);,同时把源文件保存为 UTF-8 编码。如果你的编译器默认保存成 GBK,那就不改编码,只加一句SetConsoleOutputCP(936);。更稳妥的做法是程序里自动判断,但最简单实用的就是这两选一。需要注意旧版 Windows 7 的终端对 UTF-8 原生支持不佳,遇到老系统就用 GBK。4.3 Sleep和system像“玄学”一样不生效现象:编译器直接报错Sleep was not declared in this scope,或者在 Linux 下运行system("cls")显示“sh: cls: command not found”。原因:Sleep和system都不是 C 标准库函数。Sleep是 Windows API,必须包含<windows.h>才认;cls是 cmd 的命令,Linux 终端用的是clear或 ANSI 转义序列。很多教程只说 Windows 的写法,你在其他环境照着敲自然翻车。解决:用条件编译把平台差异包起来,这样同一份代码在 Windows、Linux、macOS 都能编译运行。#ifdef _WIN32 #include <windows.h> #define CLEAR() system("cls") #define SLEEP(ms) Sleep(ms) #else #include <unistd.h> #define CLEAR() printf("\033[2J\033[H") #define SLEEP(ms) usleep((ms) * 1000) #endif这段代码的核心在预处理宏:Windows 平台走system("cls")和Sleep,其他平台走 ANSI 转义序列和usleep。注意usleep的单位是微秒,所以后面乘以 1000,SLEEP(450)就等价于usleep(450000),这样调用方不需要感知平台差异。4.4 爱心图案变形、缺角、糊成一团现象:画出来的心要么像被压扁的苹果,要么尖角缺了一块,要么左右不对称,跟想象中完全不一样。原因:控制台字符不是正方形,每个字符单元宽约是高的 0.5 倍,直接按数学坐标逐行扫描,纵向单位长度对应的屏幕距离比横向短一半,心就被压扁了。缺角则多半是循环范围没覆盖到心尖,公式的尖点在y = -1附近,如果循环下界只到-0.9,尖角就永远画不出来。解决:按症状调整参数,方法如下表。症状调整位置心形被压扁y步长调大,如0.1改为0.15心形被拉高y步长调小,如0.1改为0.08尖角缺失循环下界从-1.1改成-1.3左右超出屏幕x边界从±1.2收到±1.0另外,控制台字体对观感影响也很大。默认的“新宋体”字符宽高比在 2 左右,点阵字体比例不一样。如果图案怎么调都不对,可以试试把控制台字体改成“点阵字体”,或者把窗口手动拉宽到 120 字符以上,再回来看步长。4.5 杀毒软件把你的exe干掉了现象:编译好的 exe 发过去,对方一解压就被隔离,或者自己还没双击,杀毒软件已经弹窗报警。原因:很多网上的“表白程序”确实整活,代码里调用了system("shutdown -s -t 0")这种关机命令,或者reg、format之类的高危操作,被安全软件收进了特征库。还有一类程序启动后主动隐藏窗口、修改系统设置,这些灰色行为也会触发启发式查杀。解决:第一,代码里只保留printf、Sleep、Beep、MessageBox这类常规 API,不碰注册表、不调关机、不模拟按键。第二,把源码一起发过去,让对方找信任的人看一眼,或者把源码贴到公开代码托管平台上生成一个链接,先看代码再决定是否运行。如果对方仍旧谨慎,那就当面运行一次。“无毒”的本质是每行代码都见得光,不需要为了效果去碰高危 API。5. 进阶:藏起控制台、发布检查,把惊喜安全送到对方手上5.1 一条API把控制台藏起来C 语言版如果想摆脱黑色终端窗口,可以在main()开头加一句:ShowWindow(GetConsoleWindow(), SW_HIDE);这一句会让控制台窗口在启动后立刻隐藏。但它有个副作用:system("cls")清屏之类的操作也会失去意义,动态心跳动画就看不到了。所以这个技巧只适合“一次性把终版画面画完”的场景,或者你已经改用第 3 章的 Windows API 版本——它一开始就是图形窗口,根本没有控制台,才是“藏起来”的正解。5.2 发布前检查清单检查项怎么做为什么Release 编译VS 配 Release + x86Debug 版跑得慢且可能依赖调试运行库不带 DLL运行库选/MT静态链接避免对方机器缺运行库直接报错裸机试运行复制 exe 到一台没装开发环境的电脑上跑一次验证对方拿到手也能双击打开杀毒检测开着实时防护运行一遍提前暴露误报,不要等对方那边拦截静音容错在系统静音状态下运行确认没声音时程序不会卡住或崩掉发布时把 exe 和源码一起打包成一个 zip 发过去,文件越少越好。不要做自解压 exe,那种格式本身就容易被安全软件拦。zip 里放一个说明文件,写上“双击打开,不需要安装任何东西”,就足够清楚了。5.3 把自己当成对方,再过一遍流程发布前把自己切换成“完全不懂程序的人”,从双击开始走一遍:屏幕弹出来先看到什么?字够不够大?窗口有没有超出屏幕?有没有可能误触关闭?这步用虚拟机看最准。我一般会先发给信得过的某同学,让他双击,我在旁边看他的第一时间反应。他如果笑了,代码就合格了。收到代码的人不一定懂 C 语言,也不一定在乎这颗爱心是用公式算的还是用多边形拼的,但一定能看懂“你花了时间”这个事实。希望帮到你。本文还有配套的精品资源,点击获取
简介:使用C语言(含C++写法)实现的控制台表白小项目,面向编程初学者、嵌入式/系统开发入门者,以及想用代码制造浪漫的同学;它以极简源码演示了如何用循环、延时、屏幕输出来生成动态文字和闪烁效果,适用于课堂实验、课程设计或趣味编程练习。整个资源压缩后仅4KB,包含1个cpp源文件,没有多余素材,直接查看代码就能上手,便于学习。目前已有2333人学习或下载,属于轻量热门的小型示例。代码集中展示了字符动画的常见做法:通过循环次数控制延时帧率,逐字打印表白内容,借助Windows API实现颜色与闪烁效果,同时保持程序结构清晰、无毒无副作用;运行后即可看到动态展示,修改文本或循环参数便能定制为自己的表白页面,对理解C/C++的程序流程、时间控制和控制台交互很有帮助。1. 表白代码为什么讲究“无毒”,C/C++写的到底靠不靠谱在技术圈搜“表白代码”,你能看到两种极端:一种是三行 printf 的敷衍 demo,另一种则“毒性”拉满——弹窗刷屏、假关机、死循环逼你输入 yes,甚至悄悄改注册表。这哪是表白,分明是当众把人架在火上烤。真正值得投入时间写的表白代码,应当干净、纯粹、零高危调用,编译成一个单文件 exe,双击就能跑,不依赖任何运行时和联网环境。C 语言做控制台动画,C++ 做图形窗口,正好覆盖从“刚学完指针的萌新”到“想把心意做成正经程序”的开发者。下面直接给出两套能跑通的方案,把原理、代码、编译命令和最容易翻车的几个坑一次讲透。2. C语言表白程序:在控制台做一颗会跳的心2.1 心跳动画的原理:帧、清屏与延时,控制台怎么做“会动”控制台没有真正的动画,它只能展示静态字符。所谓动画,就是“清掉上一帧、画上下一帧”的快速切换。清屏用system("cls"),画帧用printf,控制节奏用Sleep(),这三样组合起来就是控制台动画的全部。很多新手一上来就追求高帧率,结果画面狂闪,观感还不如一张静态图。控制台动画的正确思路是“用帧间隔表达节奏”,而不是“跑得够快”。心跳的真实节奏是“咚—哒”,第一下重、第二下轻,中间有微弱停顿。用代码模拟时,第一下画完心形后等 400 到 500 毫秒,第二下等 150 到 200 毫秒,一组心跳就完成了。这个间隔来自你对自己心跳的感知,写代码的时候可以一边按脉搏一边调参数,调出来的节奏才像真的心跳,而不是两个等长的闪烁。心形本身不建议用一串写死的字符拼图,字符一旦换终端宽度就容易错位。更稳的做法是用数学公式实时扫描:心形公式(x² + y² - 1)³ - x²y³ ≤ 0,只要某个坐标代入后成立,就输出一个星号,否则输出空格。控制台终端在屏幕上逐行扫描这个公式,天然就是一颗可以调整大小、不会散架的心。2.2 可直接抄的C语言版本:完整代码与参数解释/* * love.c * C语言控制台版表白程序 * 编译:Windows 下用任意 C 编译器(VS / MinGW / Dev-C++)直接编译 */ #include <stdio.h> #include <windows.h> /* 用数学公式画出爱心,打印在控制台上 */ void draw_heart() { for (double y = 1.1; y > -1.1; y -= 0.1) { /* 逐行扫描 */ for (double x = -1.2; x < 1.2; x += 0.05) { /* 逐列扫描 */ double a = x * x + y * y - 1; double v = a * a * a - x * x * y * y * y; putchar(v <= 0 ? '*' : ' '); /* 在公式内部就打星号 */ } putchar('\n'); } } /* 逐字输出文字,模拟打字机效果 */ void type_text(const char *s) { while (*s) { putchar(*s); fflush(stdout); /* 立即刷出,否则会积在缓冲区 */ Sleep(60); /* 每个字符间距 60ms */ s++; } } int main() { /* 第一段:三组心跳,每次两组,模拟“咚哒” */ for (int i = 0; i < 3; i++) { system("cls"); draw_heart(); printf("\n I LOVE YOU\n"); Sleep(450); /* 第一下,间隔稍长 */ system("cls"); draw_heart(); printf("\n I LOVE YOU\n"); Sleep(180); /* 第二下,间隔短,形成心跳节奏 */ } /* 第二段:清屏后逐字打出想说的话 */ system("cls"); type_text("写这段程序的时候,我一直在改你名字的拼音。\n"); type_text("后来发现,不用改,你的名字在每一行注释里。\n\n"); type_text("所以……愿不愿意,让我在你的 main 函数里\n"); type_text("永远占一个位置?\n\n"); printf("\n —— 等你回复的某开发者\n"); getchar(); /* 防止窗口一闪而过 */ return 0; }这段代码分两段讲。第一段是心跳:draw_heart()用公式实时计算整颗心,清屏后立刻重画,配合两个Sleep()的间隔产生跳动感。第二段是打字机效果:type_text()每次只输出一个字符并停顿 60 毫秒,配合fflush(stdout)强制刷新,让文字一句一句“打”出来,比一次性打印更有临场感。参数调节集中在三处。第一处是draw_heart()里的y -= 0.1和x += 0.05,它们决定心形的密度和宽高比:终端字符本身是“高约等于两倍宽”的,所以纵向步长要比横向大一些,心形才不会被压扁。第二处是心跳节奏,Sleep(450)和Sleep(180)分别对应“咚”和“哒”,想更沉稳就都加 80,想更活泼就都减 50。第三处是type_text()里的Sleep(60),这是打字速度,中文阅读舒适区间在 50 到 100 毫秒,太快像机器,太慢让人着急。2.3 把文字换成你自己的话:编码、节奏、配色三个参数第一步是解决中文乱码。printf("我喜欢你")显示成乱码,通常是源文件编码与终端代码页不一致导致的:源文件是 UTF-8,终端却是 GBK,两边就翻译错位。最简单的方法是在main()第一行加一句SetConsoleOutputCP(65001);,并且把源文件保存成 UTF-8 编码。如果你的编译器默认存成 GBK,就把参数改成SetConsoleOutputCP(936);,语句放在main()开头,中文字符就能正常显示了。第二步是替换表白文案。直接改main()里type_text("...")的内容就行,建议控制在三到四句,语气越像日常说话越好。真正加分的是藏彩蛋:把对方的名字、你们第一次见面的日期,写进代码的注释里。对方如果打开源码看到这些细节,会比看到动画更触动。第三步是给爱心上色。默认星号是灰色,可以在draw_heart()调用之前加一句SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_INTENSITY);,之后输出的星号就变成亮红色。FOREGROUND_RED是 4,FOREGROUND_INTENSITY是 8,合起来是 12;同理绿色是 2,蓝色是 1,想调成其他颜色就组合这几个值。3. C++表白程序:用Windows API弹出一个爱心窗口3.1 为什么要挑Windows自带的GDI,而不是第三方图形库C++ 做图形界面,很多人第一反应是引入第三方图形库,常见的有 EasyX、SDL、SFML。这些库确实好用,但也带来一个问题:对方电脑上未必装了运行时,或者收到一个压缩包里有 exe、DLL、字体文件好多个,满怀期待解开一看反而犯嘀咕。表白代码最重要的是让对方“双击就能看”,任何额外依赖都会增加怀疑成本。Windows 系统自带的 GDI(图形设备接口)不需要装任何库,编译器也原生支持。它藏在系统里,编译出来的就是一个单文件 exe,不需要联网,不需要安装,双击直接弹窗口。它的边界也很清楚:只适用于 Windows 平台。如果你的目标对象用 macOS 或 Linux,那需要换方案;但就“用 C++ 做表白程序”这个场景来说,绝大多数目标对象用的都是 Windows,所以选 GDI 是最稳妥的路径。3.2 画一颗走心的爱心:窗口版完整代码/* * love.cpp * C++ 图形窗口版表白程序 * 依赖:仅 Windows 系统自带 GDI,无第三方库 */ #include <windows.h> /* 窗口过程:负责绘制爱心和文字 */ LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_PAINT: { PAINTSTRUCT ps; HDC hdc = BeginPaint(hwnd, &ps); /* 1) 创建红色画刷并选入设备上下文 */ HBRUSH brush = CreateSolidBrush(RGB(220, 20, 60)); HBRUSH oldBrush = (HBRUSH)SelectObject(hdc, brush); /* 2) 两个椭圆拼出爱心的两个“肩” */ Ellipse(hdc, 70, 70, 220, 190); /* 左半圆 */ Ellipse(hdc, 180, 70, 330, 190); /* 右半圆 */ /* 3) 一个三角形兜底,形成心尖 */ POINT pts[] = { {85, 165}, {315, 165}, {200, 350} }; Polygon(hdc, pts, 3); SelectObject(hdc, oldBrush); DeleteObject(brush); /* 4) 在窗口下方写表白文案:白字、透明背景 */ SetBkMode(hdc, TRANSPARENT); SetTextColor(hdc, RGB(255, 255, 255)); RECT rc = { 0, 390, 400, 460 }; DrawTextW(hdc, L"你愿意做我的女朋友吗?", -1, &rc, DT_CENTER | DT_VCENTER); EndPaint(hwnd, &ps); return 0; } case WM_DESTROY: PostQuitMessage(0); return 0; } return DefWindowProcW(hwnd, msg, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { /* 注册窗口类 */ WNDCLASSW wc = {}; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.hCursor = LoadCursorW(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszClassName = L"HeartWindow"; RegisterClassW(&wc); /* 创建固定大小的窗口,禁止拉伸以防止爱心变形 */ HWND hwnd = CreateWindowW( L"HeartWindow", L"一封来自代码的信", WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, 400, 500, NULL, NULL, hInstance, NULL); /* 播放心跳音效:低音“咚” + 高音“哒”,循环 4 组 */ for (int i = 0; i < 4; i++) { Beep(120, 180); Beep(160, 90); } ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); /* 消息循环:保证窗口不退出 */ MSG msg; while (GetMessageW(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessageW(&msg); } return 0; }这段代码的核心在WM_PAINT里。先创建一个红色画刷,选入设备上下文,然后用两个椭圆拼出爱心上方的两个圆弧,再用一个三角形兜出心尖。三角形上边的 y 坐标是 165,而两个椭圆的最低点到了 190,三角形上边会切掉椭圆底部一小段弧线,拼出心形顶部那个标志性的内凹,不会出现明显拼缝。WinMain负责注册窗口类、创建窗口、循环播放心跳音效,然后进入标准消息循环。参数调节也集中在三处。心形的宽度由两个椭圆水平方向控制:左椭圆Ellipse(hdc, 70, 70, 220, 190)的圆心在(150, 130),右椭圆圆心在(255, 130),两个圆心水平距离 105。想加宽就把右椭圆左边界调小,比如从 180 改成 160;想收窄就往大调。心尖的长度由三角形底部顶点{200, 350}控制,改成 380 心会更尖,改成 320 会更圆润。颜色RGB(220, 20, 60)是带蓝调的绯红,比纯红耐看,也可以换成对方喜欢的颜色,比如樱粉色RGB(255, 182, 193)。Beep那段是心跳音效,Beep(120, 180)是低音“咚”,Beep(160, 90)是高音“哒”,循环 4 组。需要注意Beep在系统没有音频设备或音频服务未启动时会直接返回失败,但不会崩溃,最多就是安静地弹窗,不影响程序运行。3.3 编译成单文件exe:VS与MinGW的参数对照用 Visual Studio 的话,操作路径是:新建一个“空项目”,添加love.cpp,然后右键项目进入“属性”,把“链接器 → 系统 → 子系统”从“控制台”改成“Windows”。这个改动很关键,不改的话运行时会先弹一个黑色控制台窗口,惊喜感直接减半。设置完成后按生成,得到一个 exe。如果你习惯命令行,用 MinGW 的g++编译是另一种常见做法:g++ love.cpp -o love.exe -mwindows -lgdi32 -luser32这条命令里-mwindows告诉链接器这是一个 GUI 程序,运行时不要创建控制台窗口;-lgdi32链接图形设备接口库,Ellipse、Polygon、BeginPaint这些绘制函数都依赖它;-luser32链接用户接口库,CreateWindowW、Beep、MessageBox在这里面。三个参数缺一不可,少一个都会在链接阶段报一堆无法解析的外部符号。发布给对方的版本只用 Release,不用 Debug。Debug 版体积大,而且有些编译器版本会把调试运行库一起依赖进去,对方机器上没有就会直接报“缺少 DLL”。编译完成后可以检查一下:整个目录里只有一个 exe,这才是“无毒”交付的标准形态。4. 表白程序避坑指南:5个最容易翻车的地方4.1 双击exe后黑窗口一闪而过现象:双击运行,屏幕刚闪一下,窗口就消失了,对方还没看清内容,程序已经退出。原因:main()函数执行到最后的return 0时进程立即结束,控制台窗口跟着关闭。很多人自己在 IDE 里按 Ctrl+F5 运行没问题,因为 IDE 会自动加一个“按任意键继续”的暂停;但直接双击 exe 就不会有这个暂停了。解决:在main()结尾、return 0之前加一行getchar();,程序会一直停到对方按下回车。如果前面已经用了type_text()逐字输出,就放在所有文字打印完之后。这一步是所有控制台版表白程序都必须做的,不加等于白写。4.2 中文字符全变乱码现象:printf("我喜欢你")在控制台里输出成一堆乱码,像“鎴戝枩娆綘”这样。原因:源文件的编码方式与终端当前代码页不一致。比如源文件保存成了 UTF-8,而终端代码页还是默认的 GBK(936),中文被拆成字节后按另一种表去翻译,就变成乱码。解决:在main()开头调用SetConsoleOutputCP(65001);,同时把源文件保存为 UTF-8 编码。如果你的编译器默认保存成 GBK,那就不改编码,只加一句SetConsoleOutputCP(936);。更稳妥的做法是程序里自动判断,但最简单实用的就是这两选一。需要注意旧版 Windows 7 的终端对 UTF-8 原生支持不佳,遇到老系统就用 GBK。4.3 Sleep和system像“玄学”一样不生效现象:编译器直接报错Sleep was not declared in this scope,或者在 Linux 下运行system("cls")显示“sh: cls: command not found”。原因:Sleep和system都不是 C 标准库函数。Sleep是 Windows API,必须包含<windows.h>才认;cls是 cmd 的命令,Linux 终端用的是clear或 ANSI 转义序列。很多教程只说 Windows 的写法,你在其他环境照着敲自然翻车。解决:用条件编译把平台差异包起来,这样同一份代码在 Windows、Linux、macOS 都能编译运行。#ifdef _WIN32 #include <windows.h> #define CLEAR() system("cls") #define SLEEP(ms) Sleep(ms) #else #include <unistd.h> #define CLEAR() printf("\033[2J\033[H") #define SLEEP(ms) usleep((ms) * 1000) #endif这段代码的核心在预处理宏:Windows 平台走system("cls")和Sleep,其他平台走 ANSI 转义序列和usleep。注意usleep的单位是微秒,所以后面乘以 1000,SLEEP(450)就等价于usleep(450000),这样调用方不需要感知平台差异。4.4 爱心图案变形、缺角、糊成一团现象:画出来的心要么像被压扁的苹果,要么尖角缺了一块,要么左右不对称,跟想象中完全不一样。原因:控制台字符不是正方形,每个字符单元宽约是高的 0.5 倍,直接按数学坐标逐行扫描,纵向单位长度对应的屏幕距离比横向短一半,心就被压扁了。缺角则多半是循环范围没覆盖到心尖,公式的尖点在y = -1附近,如果循环下界只到-0.9,尖角就永远画不出来。解决:按症状调整参数,方法如下表。症状调整位置心形被压扁y步长调大,如0.1改为0.15心形被拉高y步长调小,如0.1改为0.08尖角缺失循环下界从-1.1改成-1.3左右超出屏幕x边界从±1.2收到±1.0另外,控制台字体对观感影响也很大。默认的“新宋体”字符宽高比在 2 左右,点阵字体比例不一样。如果图案怎么调都不对,可以试试把控制台字体改成“点阵字体”,或者把窗口手动拉宽到 120 字符以上,再回来看步长。4.5 杀毒软件把你的exe干掉了现象:编译好的 exe 发过去,对方一解压就被隔离,或者自己还没双击,杀毒软件已经弹窗报警。原因:很多网上的“表白程序”确实整活,代码里调用了system("shutdown -s -t 0")这种关机命令,或者reg、format之类的高危操作,被安全软件收进了特征库。还有一类程序启动后主动隐藏窗口、修改系统设置,这些灰色行为也会触发启发式查杀。解决:第一,代码里只保留printf、Sleep、Beep、MessageBox这类常规 API,不碰注册表、不调关机、不模拟按键。第二,把源码一起发过去,让对方找信任的人看一眼,或者把源码贴到公开代码托管平台上生成一个链接,先看代码再决定是否运行。如果对方仍旧谨慎,那就当面运行一次。“无毒”的本质是每行代码都见得光,不需要为了效果去碰高危 API。5. 进阶:藏起控制台、发布检查,把惊喜安全送到对方手上5.1 一条API把控制台藏起来C 语言版如果想摆脱黑色终端窗口,可以在main()开头加一句:ShowWindow(GetConsoleWindow(), SW_HIDE);这一句会让控制台窗口在启动后立刻隐藏。但它有个副作用:system("cls")清屏之类的操作也会失去意义,动态心跳动画就看不到了。所以这个技巧只适合“一次性把终版画面画完”的场景,或者你已经改用第 3 章的 Windows API 版本——它一开始就是图形窗口,根本没有控制台,才是“藏起来”的正解。5.2 发布前检查清单检查项怎么做为什么Release 编译VS 配 Release + x86Debug 版跑得慢且可能依赖调试运行库不带 DLL运行库选/MT静态链接避免对方机器缺运行库直接报错裸机试运行复制 exe 到一台没装开发环境的电脑上跑一次验证对方拿到手也能双击打开杀毒检测开着实时防护运行一遍提前暴露误报,不要等对方那边拦截静音容错在系统静音状态下运行确认没声音时程序不会卡住或崩掉发布时把 exe 和源码一起打包成一个 zip 发过去,文件越少越好。不要做自解压 exe,那种格式本身就容易被安全软件拦。zip 里放一个说明文件,写上“双击打开,不需要安装任何东西”,就足够清楚了。5.3 把自己当成对方,再过一遍流程发布前把自己切换成“完全不懂程序的人”,从双击开始走一遍:屏幕弹出来先看到什么?字够不够大?窗口有没有超出屏幕?有没有可能误触关闭?这步用虚拟机看最准。我一般会先发给信得过的某同学,让他双击,我在旁边看他的第一时间反应。他如果笑了,代码就合格了。收到代码的人不一定懂 C 语言,也不一定在乎这颗爱心是用公式算的还是用多边形拼的,但一定能看懂“你花了时间”这个事实。希望帮到你。本文还有配套的精品资源,点击获取