c语言控制台窗口图形界面编程

99ANYc3cd6
预计阅读时长 26 分钟
位置: 首页 C语言 正文

这通常被称为“控制台图形学”或“文本模式图形”,它不依赖任何图形库(如 OpenGL, SDL),而是纯粹通过在控制台(命令提示符 CMD、终端)中输出字符(主要是 ASCII 字符)来构建图形界面。

这种方法虽然简单,但非常适合学习图形学的基本概念,如坐标系统、双缓冲、用户输入处理和简单的动画。


核心原理

控制台图形编程的核心原理有以下几点:

  1. 字符作为像素:控制台中的每一个字符位置都可以看作是一个“像素”,通过改变这个位置上的字符和它的颜色,就可以“绘制”出图形。
  2. 二维坐标系:我们可以建立一个二维坐标系,左上角是原点 (0, 0),x 轴向右延伸,y 轴向下延伸,屏幕的宽度就是 x 的最大值,高度就是 y 的最大值。
  3. 光标控制:这是最关键的技术,我们需要能够精确地控制光标在屏幕的任意位置,以便在指定的地方“画”上字符。
  4. 颜色控制:我们可以改变字符和其背景的颜色,让图形更丰富。
  5. 双缓冲:为了解决闪烁问题,我们通常采用“双缓冲”技术,即在内存中构建好一整帧图像,然后一次性将其“刷新”到屏幕上,而不是在屏幕上逐个字符地修改。

核心技术实现 (Windows平台)

在 Windows 平台下,我们可以使用 Windows API 函数来控制控制台。

1 获取控制台屏幕缓冲区句柄

所有操作都需要一个句柄,就像我们操作文件需要文件句柄一样。

#include <windows.h>
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
if (hConsole == INVALID_HANDLE_VALUE) {
    // 处理错误
}

2 光标控制

设置光标位置

COORD 结构体用于表示一个字符在控制台屏幕缓冲区中的坐标 (x, y)。

COORD pos;
pos.X = 10;
pos.Y = 5;
SetConsoleCursorPosition(hConsole, pos);

隐藏光标:为了让界面看起来更干净,我们通常会隐藏光标。

CONSOLE_CURSOR_INFO cursorInfo;
cursorInfo.dwSize = 1; // 设置光标大小
cursorInfo.bVisible = FALSE; // 设置光标不可见
SetConsoleCursorInfo(hConsole, &cursorInfo);

3 颜色控制

通过 SetConsoleTextAttribute 函数来设置后续输出的字符颜色。

// 函数原型: SetConsoleTextAttribute(HANDLE hConsoleOutput, WORD wAttributes);
// WORD 是一个16位无符号整数,低8位是前景色,高8位是背景色。
// 设置颜色为:红色前景,黑色背景
SetConsoleTextAttribute(hConsole, FOREGROUND_RED | BACKGROUND_BLACK);
// 设置颜色为:绿色前景,蓝色背景
SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN | BACKGROUND_BLUE);
// 常用颜色组合
#define COLOR_RED     (FOREGROUND_RED | FOREGROUND_INTENSITY)
#define COLOR_GREEN   (FOREGROUND_GREEN | FOREGROUND_INTENSITY)
#define COLOR_BLUE    (FOREGROUND_BLUE | FOREGROUND_INTENSITY)
#define COLOR_YELLOW  (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY)
#define COLOR_WHITE   (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)

完整示例:绘制一个动态的矩形

下面是一个完整的示例,它会在控制台中绘制一个移动的、彩色的矩形,并演示了双缓冲技术。

#include <stdio.h>
#include <windows.h>
#include <stdbool.h>
// 定义屏幕尺寸
#define SCREEN_WIDTH 80
#define SCREEN_HEIGHT 25
// 定义颜色
#define COLOR_RED     (FOREGROUND_RED | FOREGROUND_INTENSITY)
#define COLOR_GREEN   (FOREGROUND_GREEN | FOREGROUND_INTENSITY)
#define COLOR_BLUE    (FOREGROUND_BLUE | FOREGROUND_INTENSITY)
#define COLOR_YELLOW  (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY)
#define COLOR_WHITE   (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)
#define COLOR_DEFAULT (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE)
// 双缓冲:一个在内存中的屏幕
char screenBuffer[SCREEN_HEIGHT][SCREEN_WIDTH + 1]; // +1 for null terminator
// 清空屏幕缓冲区
void clearBuffer() {
    for (int y = 0; y < SCREEN_HEIGHT; y++) {
        for (int x = 0; x < SCREEN_WIDTH; x++) {
            screenBuffer[y][x] = ' ';
        }
        screenBuffer[y][SCREEN_WIDTH] = '\0'; // 字符串结束符
    }
}
// 将屏幕缓冲区输出到控制台
void renderBuffer() {
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    COORD coord = { 0, 0 };
    DWORD dwBytesWritten;
    // 将整个缓冲区写入控制台
    WriteConsoleOutputCharacterA(
        hConsole,
        (LPCSTR)screenBuffer,
        SCREEN_WIDTH * SCREEN_HEIGHT,
        coord,
        &dwBytesWritten
    );
}
// 在屏幕缓冲区中绘制一个字符
void drawChar(int x, int y, char c, WORD color) {
    if (x >= 0 && x < SCREEN_WIDTH && y >= 0 && y < SCREEN_HEIGHT) {
        screenBuffer[y][x] = c;
        // 设置颜色
        HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
        SetConsoleTextAttribute(hConsole, color);
    }
}
// 绘制一个实心矩形
void drawFilledRect(int x, int y, int width, int height, char c, WORD color) {
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            drawChar(x + j, y + i, c, color);
        }
    }
}
int main() {
    // 隐藏光标
    HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_CURSOR_INFO cursorInfo;
    cursorInfo.dwSize = 1;
    cursorInfo.bVisible = FALSE;
    SetConsoleCursorInfo(hConsole, &cursorInfo);
    // 矩形属性
    int rect_x = 0;
    int rect_y = 0;
    int rect_width = 10;
    int rect_height = 5;
    int rect_dx = 1; // x方向速度
    int rect_dy = 1; // y方向速度
    // 主循环
    while (true) {
        // 1. 清空缓冲区
        clearBuffer();
        // 2. 在内存中绘制场景
        drawFilledRect(rect_x, rect_y, rect_width, rect_height, '@', COLOR_RED);
        drawFilledRect(rect_x + 15, rect_y + 10, rect_width, rect_height, '#', COLOR_GREEN);
        // 3. 更新对象位置
        rect_x += rect_dx;
        rect_y += rect_dy;
        // 4. 边界检测和反弹
        if (rect_x <= 0 || rect_x + rect_width >= SCREEN_WIDTH) {
            rect_dx = -rect_dx;
        }
        if (rect_y <= 0 || rect_y + rect_height >= SCREEN_HEIGHT) {
            rect_dy = -rect_dy;
        }
        // 5. 将缓冲区一次性刷新到屏幕
        renderBuffer();
        // 6. 控制帧率
        Sleep(50); // 暂停50毫秒
    }
    return 0;
}

如何编译和运行

  1. 保存代码:将上述代码保存为 console_graphics.c
  2. 编译:如果你使用的是 MinGW (GCC),可以在命令行中编译:
    gcc console_graphics.c -o console_graphics.exe
  3. 运行:在命令行中运行生成的 .exe 文件。
    console_graphics.exe

    你会看到一个红色的 符号在控制台窗口中移动并反弹。


跨平台考虑 (Linux/macOS)

上面的代码使用了 Windows API,因此只能在 Windows 上编译运行,如果你想在 Linux 或 macOS 上实现类似的功能,需要使用不同的系统调用。

  • Linux/macOS:主要使用 ncurses 库,这是一个专门用于在终端上创建文本用户界面的库,功能非常强大,支持窗口、颜色、鼠标事件、键盘输入等。

使用 ncurses 的简单示例 (Linux/macOS):

#include <ncurses.h>
int main() {
    // 初始化 ncurses
    initscr();          // 开始 curses 模式
    cbreak();           // 禁行缓冲
    noecho();           // 不显示输入的字符
    keypad(stdscr, TRUE); // 启用功能键(如方向键)
    curs_set(0);        // 隐藏光标
    start_color();      // 启用颜色
    // 定义颜色对
    init_pair(1, COLOR_RED, COLOR_BLACK);
    init_pair(2, COLOR_GREEN, COLOR_BLACK);
    int y = 0, x = 0;
    int max_y, max_x;
    getmaxyx(stdscr, max_y, max_x); // 获取屏幕尺寸
    while (true) {
        // 清屏
        clear();
        // 绘制
        attron(COLOR_PAIR(1));
        mvprintw(y, x, "O");
        attroff(COLOR_PAIR(1));
        attron(COLOR_PAIR(2));
        mvprintw(max_y / 2, max_x / 2, "X");
        attroff(COLOR_PAIR(2));
        // 刷新屏幕
        refresh();
        // 处理输入
        int ch = getch();
        switch (ch) {
            case KEY_UP:    if (y > 0) y--; break;
            case KEY_DOWN:  if (y < max_y - 1) y++; break;
            case KEY_LEFT:  if (x > 0) x--; break;
            case KEY_RIGHT: if (x < max_x - 1) x++; break;
            case 'q':       goto end_loop; // 按 q 退出
        }
    }
end_loop:
    // 结束 ncurses
    endwin();
    return 0;
}

编译 ncurses 程序 (Linux):

gcc my_ncurses_app.c -o my_ncurses_app -lncurses

总结与进阶

C语言控制台图形编程是一种非常有趣且能巩固基础概念的方法,它通过控制字符、光标和颜色来模拟图形界面,核心在于Windows APIncurses库的使用,以及双缓冲技术的应用以避免闪烁。

进阶方向

  1. 用户输入处理:除了 kbhit(),可以更深入地处理键盘输入(方向键、功能键)和鼠标输入。
  2. 更复杂的图形:可以尝试绘制线条、圆形、精灵(使用字符画)等。
  3. 游戏开发:利用这些技术,可以开发简单的文字冒险游戏、贪吃蛇、俄罗斯方块等。
  4. UI控件:尝试模拟按钮、文本框、菜单等基本的UI元素。
  5. 状态机:对于游戏,使用状态机来管理不同的游戏场景(如菜单、游戏中、游戏结束)。

希望这个详细的指南能帮助你入门 C 语言的控制台图形界面编程!

-- 展开阅读全文 --
头像
C51实战集锦,从入门到精通的实战案例有哪些?
« 上一篇 02-04
dede数据库读取配置文件在何处?
下一篇 » 02-04

相关文章

取消
微信二维码
支付宝二维码

目录[+]