恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
C++玫瑰花字符画项目实战教程
首页
资讯中心
/
C++玫瑰花字符画项目实战教程
C++玫瑰花字符画项目实战教程
发布时间:2026/10/21 5:43:12
前言字符画ASCII art是练习坐标变换、循环控制和输出格式的好题材它不依赖图形库不需要窗口系统所有效果都由字符在二维网格中的位置决定。本文做一朵「玫瑰花」——用极坐标下的玫瑰线rose curve生成五片花瓣再加上花茎和叶片最终输出到终端。需要先说明两点。第一终端里的「像素」不是正方形一个字符单元通常高约为宽的两倍如果直接在 63×32 的网格里画一个标准圆看起来会是一个竖长的椭圆。解决办法是让横向缩放系数约为纵向的两倍。第二字符画没有唯一正确答案本文给出一套可调参数的生成方式读者可以改花瓣数、半径、字符集得到自己的版本。文中的代码按 C17 书写本机没有编译环境不声称任何执行效果所有几何结论都按公式推演。一、坐标模型把数学坐标映射到字符网格终端网格的坐标原点在左上角x 向右增大y 向下增大。数学上的极坐标习惯是 y 向上因此做变换时要么把 y 取反要么在atan2里传入-ny。本文统一用「屏幕坐标」即ny (y - cy) / ry此时角度 0 指向右方角度 π/2 指向下方。对五瓣玫瑰线来说旋转对称方向不影响形状只影响花瓣的起始朝向所以不必额外取反。映射关系如下nx (x - cx) / rx // 归一化到 [-1, 1] 附近ny (y - cy) / ryr sqrt(nx^2 ny^2)θ atan2(ny, nx)其中cx、cy是花心所在的列与行rx、ry是横向和纵向的缩放半径。由于字符高宽比约为 2:1取rx ≈ 2 * ry能让花瓣在视觉上接近圆形而不是被压扁。参数含义本文取值kWidth/kHeight画布列数 / 行数63 / 32cx/cy花心列 / 行31.0 / 13.0rx/ry横向 / 纵向缩放21.0 / 11.0花瓣数玫瑰线的 k5字符集轮廓 / 内层 / 花心#//*二、花瓣的数学表达极坐标玫瑰线的方程是r a·cos(kθ)。当 k 为奇数时曲线有 k 片花瓣k 为偶数时花瓣数翻倍。本文取 k 5得到五片花瓣。为了让花瓣的边缘清晰判断条件写成r |cos(5θ)|满足的点落在花瓣内部不满足的留空。只有「在不在花瓣内」还不够纯色填充会显得扁平。可以用「到边缘的距离」depth |cos(5θ)| - r做分层depth 0.06贴近边缘用#勾轮廓0.06 depth 0.28过渡区用其余内部用*。这样一片花瓣就有了从外到内的层次而不需要任何灰度支持。花茎是一条竖线从花心下方一直延伸到画布底部叶片用斜线和对角线字符拼出。由于五瓣玫瑰在正下方θ 90°恰好是花瓣之间的凹陷花茎接在花心正下方不会与花瓣重叠视觉上比较自然。三、完整代码下面是一个单文件实现包含画布构造、花瓣绘制、花茎与叶片绘制、输出裁剪。// 适用于 C17#include cmath#include cstddef#include iostream#include string#include vectornamespace {constexpr double kPi 3.14159265358979323846; // 不使用非标准的 M_PIconstexpr int kWidth 63;constexpr int kHeight 32;// 把花瓣画到画布上void drawFlower(std::vectorstd::string canvas) {const double cx 31.0; // 花心列const double cy 13.0; // 花心行const double rx 21.0; // 横向缩放约为纵向的两倍const double ry 11.0; // 纵向缩放for (int y 0; y kHeight; y) {for (int x 0; x kWidth; x) {const double nx (x - cx) / rx;const double ny (y - cy) / ry;const double r std::sqrt(nx * nx ny * ny);const double theta std::atan2(ny, nx);const double petal std::fabs(std::cos(5.0 * theta));if (r petal) {continue; // 不在花瓣内}const double depth petal - r;char ch *;if (depth 0.06) {ch #;} else if (depth 0.28) {ch ;}canvas[static_caststd::size_t(y)][static_caststd::size_t(x)] ch;}}}// 花茎与两片叶子void drawStem(std::vectorstd::string canvas) {const int stemX kWidth / 2; // 31for (int y 16; y kHeight; y) {canvas[static_caststd::size_t(y)][static_caststd::size_t(stemX)] |;}// 左侧叶片向左下方伸展for (int i 1; i 5; i) {const int x stemX - i;const int y 22 i / 2;if (x 0 y kHeight) {canvas[static_caststd::size_t(y)][static_caststd::size_t(x)] (i 5) ? / : -;}}// 右侧叶片向右下方伸展for (int i 1; i 5; i) {const int x stemX i;const int y 26 i / 2;if (x kWidth y kHeight) {canvas[static_caststd::size_t(y)][static_caststd::size_t(x)] (i 5) ? \\ : -;}}}} // namespaceint main() {std::vectorstd::string canvas(static_caststd::size_t(kHeight),std::string(static_caststd::size_t(kWidth), ));drawFlower(canvas);drawStem(canvas);for (const std::string line : canvas) {const std::size_t end line.find_last_not_of( );if (end std::string::npos) {std::cout \n;} else {std::cout line.substr(0, end 1) \n;}}return 0;}几点说明。kPi定义了却没有被用到这是有意的「常量占位」方便后续添加螺旋线或圆弧时使用如果开启-Wall -Wextra编译器可能对未使用的命名空间作用域常量给出警告不同编译器行为不同。想保持零警告可以删掉它或改成[[maybe_unused]] constexpr double kPi ...C17 属性。所有对画布的写入都用了static_caststd::size_t。operator[]接受size_type传int会隐式转换值为负时会变成一个极大的无符号数导致越界写——虽然在本文的取值下不会发生但养成显式转换的习惯更安全。对索引做边界检查比事后调试便宜得多。find_last_not_of( )用来裁掉行尾空格。如果不裁剪某些终端在选中文本时会带出大量空白视觉上花朵会「偏左」。裁剪后每行长度不同但终端按列对齐不影响图形。四、编译与参数调整在具备工具链的机器上编译命令通常是g -stdc17 -O2 -Wall -Wextra rose.cpp -o roseWindows 上用 MSVC 则是cl /std:c17 /EHsc rose.cpp或用 Visual Studio 建一个控制台项目。本文不声称任何编译结果命令仅表达所需的语言标准与警告级别。调参的方向很直接想要的效果改什么花瓣更多 / 更少把5.0改成其它整数 k奇数得 k 瓣花朵更大增大rx、ry同时增大画布花瓣更细长减小rx与ry的比值或改指数轮廓更粗增大depth的第一个阈值换字符风格替换#、、*三个字符如果想让画面「开放」而不是「实心」可以把填充条件改成只画环带例如petal - 0.12 r r petal这样每个花瓣只剩一条轮廓线。还可以叠加一条正弦扰动的内圈做出花心的螺旋感在 r 较小时用theta决定字符而不是用depth。再进一步是加颜色。ANSI 转义序列在多数 Unix 终端和 Windows 10 之后的控制台上可用Windows 需要先启用虚拟终端处理或使用支持 ANSI 的终端// 适用于 C17平台相关依赖 ANSI 转义序列#include stringstd::string paint(char ch) {switch (ch) {case #: return \033[31m#\033[0m; // 红case : return \033[35m\033[0m; // 品红case *: return \033[33m*\033[0m; // 黄case |: return \033[32m|\033[0m; // 绿default: return std::string(1, ch);}}注意每个带颜色的字符都包了\033[0m复位否则颜色会「泄漏」到后续输出。这段代码是平台相关的把输出重定向到文件时转义序列会原样写入打开文件会看到乱码。更稳妥的做法是先判断isatty/_isatty平台相关头文件只在交互式终端里着色。常见坑点❌ 直接用M_PIMSVC 下不加_USE_MATH_DEFINES就找不到。✅ 自己定义constexpr double kPi 3.14159265358979323846;可移植且不依赖宏。❌ 横纵用同一个缩放系数画出来的花被压扁成竖长条。✅ 终端字符高约为宽的两倍令rx ≈ 2 * ry。❌ 用(x - cx) / rx但x、cx、rx全是int除法截断成 0。✅ 至少让一个操作数是浮点类型或显式static_castdouble。❌ 画布索引不检查x为负时隐式转成size_t造成越界写。✅ 写入前判断x 0 x kWidth y 0 y kHeight或用at()。❌ 以为屏幕坐标的 y 轴向上atan2(dy, dx)传错符号花瓣朝向整体翻转。✅ 明确 y 向下增大需要数学坐标时用-ny。❌ 输出每行都补齐到固定宽度行尾大量空格影响选中和对比。✅ 用find_last_not_of( )裁掉行尾或输出时逐字符判断。❌ 用system(pause)或system(cls)做暂停与清屏换平台就失效。✅ 交互需求用标准输入读取清屏用 ANSI 序列并注明平台限制。❌ 在源码里直接写中文制表符或全角字符当画布列宽对不齐。✅ 画布只用单字节 ASCII 字符需要宽字符时单独处理wchar_t与终端编码。总结环节关键技术易错点坐标映射归一化 屏幕 y 轴向下整型除法、y 方向花瓣生成极坐标 r cos(kθ)层次表现用depth分档选字符阈值过密导致全是一种字符花茎叶片逐点写入并做边界检查负索引转无符号输出裁剪行尾空格全角字符破坏对齐着色ANSI 转义并复位平台差异、重定向污染字符画项目的价值在于它把「数学公式 → 坐标 → 字符」这条链路完整走了一遍极坐标负责形状归一化负责尺度字符集负责明暗边界检查负责安全。把参数抽成常量之后同一套代码可以生成不同瓣数、不同大小的花这也是它适合作为入门实战题材的原因。