NotOnlySuccess-C++零基础可视化

48 阅读10分钟

C++零基础可视化:从控制台到图形界面的编程之旅

C++可视化编程:打破文本界面的局限

传统的C++教学往往停留在控制台的黑白世界,让初学者难以感受到编程的直观魅力。可视化编程正是打破这一局限的关键技术。获课:yinheit.xyz/15305 《C++零基础可视化》课程的核心思想是:让编程结果"看得见、摸得着",通过图形界面、图像处理和动画效果,将抽象的算法和数据结构转化为直观的视觉呈现,极大提升学习兴趣和理解深度。

第一章:搭建可视化开发环境

1.1 工具选择与配置

对于C++可视化开发,推荐以下工具链组合:

cpp

复制下载

// CMakeLists.txt - 跨平台构建配置
cmake_minimum_required(VERSION 3.10)
project(CppVisualDemo)

set(CMAKE_CXX_STANDARD 17)

# 查找SFML库
find_package(SFML 2.5 COMPONENTS graphics window system REQUIRED)

# 添加可执行文件
add_executable(VisualDemo main.cpp)

# 链接SFML库
target_link_libraries(VisualDemo sfml-graphics sfml-window sfml-system)

1.2 第一个可视化窗口

cpp

复制下载

// first_window.cpp
#include <SFML/Graphics.hpp>

int main() {
    // 创建800x600像素的窗口
    sf::RenderWindow window(sf::VideoMode(800, 600), "C++可视化编程 - 第一个窗口");
    
    // 设置窗口帧率限制
    window.setFramerateLimit(60);
    
    // 主循环
    while (window.isOpen()) {
        // 处理事件
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed) {
                window.close();
            }
            
            // 键盘事件
            if (event.type == sf::Event::KeyPressed) {
                if (event.key.code == sf::Keyboard::Escape) {
                    window.close();
                }
            }
        }
        
        // 清空窗口(白色背景)
        window.clear(sf::Color::White);
        
        // 绘制内容
        
        // 显示窗口内容
        window.display();
    }
    
    return 0;
}

第二章:基础图形绘制与动画

2.1 绘制基本几何图形

cpp

复制下载

// basic_shapes.cpp
#include <SFML/Graphics.hpp>
#include <cmath>

class Visualizer {
private:
    sf::RenderWindow& window;
    
public:
    Visualizer(sf::RenderWindow& win) : window(win) {}
    
    void drawBasicShapes() {
        // 1. 绘制红色圆形
        sf::CircleShape circle(50);
        circle.setFillColor(sf::Color(255, 100, 100));  // RGB颜色
        circle.setPosition(100, 100);
        circle.setOutlineColor(sf::Color::Red);
        circle.setOutlineThickness(2);
        window.draw(circle);
        
        // 2. 绘制蓝色矩形
        sf::RectangleShape rectangle(sf::Vector2f(120, 80));
        rectangle.setFillColor(sf::Color(100, 100, 255));
        rectangle.setPosition(200, 200);
        rectangle.setRotation(45);  // 旋转45度
        window.draw(rectangle);
        
        // 3. 绘制绿色三角形
        sf::ConvexShape triangle;
        triangle.setPointCount(3);
        triangle.setPoint(0, sf::Vector2f(0, 0));
        triangle.setPoint(1, sf::Vector2f(100, 0));
        triangle.setPoint(2, sf::Vector2f(50, 86.6));  // 等边三角形
        triangle.setFillColor(sf::Color(100, 255, 100));
        triangle.setPosition(400, 300);
        window.draw(triangle);
        
        // 4. 绘制线条
        sf::Vertex line[] = {
            sf::Vertex(sf::Vector2f(500, 100), sf::Color::Black),
            sf::Vertex(sf::Vector2f(700, 200), sf::Color::Magenta)
        };
        window.draw(line, 2, sf::Lines);
    }
    
    void drawAnimatedCircle(float time) {
        // 创建脉动圆形
        float radius = 50 + 20 * std::sin(time * 3.0f);
        
        sf::CircleShape animatedCircle(radius);
        animatedCircle.setFillColor(sf::Color(255, 150, 50, 180));  // 带透明度
        animatedCircle.setPosition(600, 400);
        animatedCircle.setOrigin(radius, radius);  // 设置原点为中心
        
        window.draw(animatedCircle);
    }
};

2.2 简单动画实现

cpp

复制下载

// animation_demo.cpp
#include <SFML/Graphics.hpp>
#include <cmath>

class BouncingBall {
private:
    sf::CircleShape ball;
    sf::Vector2f position;
    sf::Vector2f velocity;
    float radius;
    
public:
    BouncingBall(float r, sf::Color color) : radius(r) {
        ball.setRadius(radius);
        ball.setFillColor(color);
        ball.setOrigin(radius, radius);  // 中心为原点
        
        position = sf::Vector2f(400, 300);
        velocity = sf::Vector2f(3.5f, 2.8f);
    }
    
    void update(sf::RenderWindow& window) {
        // 更新位置
        position += velocity;
        
        // 边界碰撞检测
        if (position.x - radius < 0 || position.x + radius > window.getSize().x) {
            velocity.x = -velocity.x;
            // 添加颜色变化效果
            ball.setFillColor(sf::Color(rand() % 256, rand() % 256, rand() % 256));
        }
        
        if (position.y - radius < 0 || position.y + radius > window.getSize().y) {
            velocity.y = -velocity.y;
            ball.setFillColor(sf::Color(rand() % 256, rand() % 256, rand() % 256));
        }
        
        // 设置球的位置
        ball.setPosition(position);
    }
    
    void draw(sf::RenderWindow& window) {
        window.draw(ball);
    }
};

int main() {
    sf::RenderWindow window(sf::VideoMode(800, 600), "弹跳球动画");
    sf::Clock clock;
    
    BouncingBall ball(30, sf::Color::Red);
    
    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }
        
        // 获取时间增量
        float deltaTime = clock.restart().asSeconds();
        
        // 更新逻辑
        ball.update(window);
        
        // 渲染
        window.clear(sf::Color::White);
        ball.draw(window);
        
        // 显示帧率
        float fps = 1.0f / deltaTime;
        // 实际应用中可以使用sf::Text显示帧率
        
        window.display();
    }
    
    return 0;
}

第三章:数据结构的可视化

3.1 数组和排序算法可视化

cpp

复制下载

// sorting_visualizer.cpp
#include <SFML/Graphics.hpp>
#include <vector>
#include <algorithm>
#include <random>
#include <thread>
#include <chrono>

class ArrayVisualizer {
private:
    std::vector<int> array;
    std::vector<sf::RectangleShape> bars;
    sf::RenderWindow& window;
    int comparisons;
    int swaps;
    
public:
    ArrayVisualizer(sf::RenderWindow& win, int size) 
        : window(win), comparisons(0), swaps(0) {
        
        // 生成随机数组
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<> dist(50, 550);
        
        for (int i = 0; i < size; i++) {
            array.push_back(dist(gen));
        }
        
        // 创建可视化柱状图
        createBars();
    }
    
    void createBars() {
        bars.clear();
        int barWidth = window.getSize().x / array.size();
        
        for (size_t i = 0; i < array.size(); i++) {
            sf::RectangleShape bar(sf::Vector2f(barWidth - 1, array[i]));
            bar.setPosition(i * barWidth, window.getSize().y - array[i]);
            bar.setFillColor(sf::Color(70, 130, 180));  // 钢蓝色
            bars.push_back(bar);
        }
    }
    
    void draw() {
        for (auto& bar : bars) {
            window.draw(bar);
        }
        
        // 显示统计信息
        // 实际应用中可以使用sf::Text显示比较和交换次数
    }
    
    void highlightBar(int index, sf::Color color) {
        if (index >= 0 && index < bars.size()) {
            bars[index].setFillColor(color);
        }
    }
    
    void resetColors() {
        for (auto& bar : bars) {
            bar.setFillColor(sf::Color(70, 130, 180));
        }
    }
    
    // 冒泡排序可视化
    void bubbleSortVisualize() {
        int n = array.size();
        comparisons = 0;
        swaps = 0;
        
        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - i - 1; j++) {
                comparisons++;
                
                // 高亮比较的两个元素
                highlightBar(j, sf::Color::Red);
                highlightBar(j + 1, sf::Color::Yellow);
                
                // 绘制当前状态
                window.clear(sf::Color::White);
                draw();
                window.display();
                
                std::this_thread::sleep_for(std::chrono::milliseconds(50));
                
                if (array[j] > array[j + 1]) {
                    std::swap(array[j], array[j + 1]);
                    swaps++;
                    
                    // 交换后更新可视化
                    createBars();
                    
                    // 绘制交换后的状态
                    window.clear(sf::Color::White);
                    draw();
                    window.display();
                    
                    std::this_thread::sleep_for(std::chrono::milliseconds(100));
                }
                
                // 重置颜色
                resetColors();
            }
        }
    }
    
    // 快速排序可视化
    void quickSortVisualize(int low, int high) {
        if (low < high) {
            int pi = partitionVisualize(low, high);
            quickSortVisualize(low, pi - 1);
            quickSortVisualize(pi + 1, high);
        }
    }
    
private:
    int partitionVisualize(int low, int high) {
        int pivot = array[high];
        int i = low - 1;
        
        highlightBar(high, sf::Color::Green);  // 高亮基准元素
        
        for (int j = low; j < high; j++) {
            comparisons++;
            highlightBar(j, sf::Color::Red);
            
            window.clear(sf::Color::White);
            draw();
            window.display();
            std::this_thread::sleep_for(std::chrono::milliseconds(30));
            
            if (array[j] < pivot) {
                i++;
                std::swap(array[i], array[j]);
                swaps++;
                
                createBars();
                window.clear(sf::Color::White);
                draw();
                window.display();
                std::this_thread::sleep_for(std::chrono::milliseconds(50));
            }
            
            resetColors();
        }
        
        std::swap(array[i + 1], array[high]);
        swaps++;
        createBars();
        
        return i + 1;
    }
};

3.2 链表可视化

cpp

复制下载

// linked_list_visualizer.cpp
#include <SFML/Graphics.hpp>
#include <vector>
#include <memory>

struct Node {
    int value;
    std::shared_ptr<Node> next;
    
    Node(int val) : value(val), next(nullptr) {}
};

class LinkedListVisualizer {
private:
    std::shared_ptr<Node> head;
    sf::RenderWindow& window;
    
public:
    LinkedListVisualizer(sf::RenderWindow& win) : window(win), head(nullptr) {}
    
    void insert(int value) {
        auto newNode = std::make_shared<Node>(value);
        
        if (!head) {
            head = newNode;
        } else {
            auto current = head;
            while (current->next) {
                current = current->next;
            }
            current->next = newNode;
        }
    }
    
    void draw() {
        auto current = head;
        int positionX = 100;
        int positionY = 300;
        
        while (current) {
            // 绘制节点
            drawNode(positionX, positionY, current->value);
            
            // 绘制箭头指向下一个节点
            if (current->next) {
                drawArrow(positionX + 60, positionY + 25, 
                         positionX + 140, positionY + 25);
            }
            
            positionX += 200;
            current = current->next;
        }
    }
    
private:
    void drawNode(int x, int y, int value) {
        // 绘制节点矩形
        sf::RectangleShape nodeRect(sf::Vector2f(120, 50));
        nodeRect.setPosition(x, y);
        nodeRect.setFillColor(sf::Color(200, 230, 255));
        nodeRect.setOutlineColor(sf::Color::Blue);
        nodeRect.setOutlineThickness(2);
        window.draw(nodeRect);
        
        // 绘制值
        // 实际应用中可以使用sf::Text显示数字
        sf::CircleShape valueCircle(20);
        valueCircle.setPosition(x + 40, y + 5);
        valueCircle.setFillColor(sf::Color::White);
        valueCircle.setOutlineColor(sf::Color::Black);
        valueCircle.setOutlineThickness(1);
        window.draw(valueCircle);
    }
    
    void drawArrow(int startX, int startY, int endX, int endY) {
        // 绘制线条
        sf::Vertex line[] = {
            sf::Vertex(sf::Vector2f(startX, startY), sf::Color::Black),
            sf::Vertex(sf::Vector2f(endX, endY), sf::Color::Black)
        };
        window.draw(line, 2, sf::Lines);
        
        // 绘制箭头头部(简化版)
        sf::CircleShape arrowHead(8, 3);  // 三角形
        arrowHead.setPosition(endX - 8, endY - 8);
        arrowHead.setRotation(90);
        arrowHead.setFillColor(sf::Color::Black);
        window.draw(arrowHead);
    }
};

第四章:交互式数学函数绘图

cpp

复制下载

// function_plotter.cpp
#include <SFML/Graphics.hpp>
#include <cmath>
#include <vector>
#include <functional>

class FunctionPlotter {
private:
    sf::RenderWindow& window;
    std::vector<sf::Vertex> points;
    float scaleX, scaleY;
    float offsetX, offsetY;
    
public:
    FunctionPlotter(sf::RenderWindow& win) 
        : window(win), scaleX(50.0f), scaleY(50.0f), 
          offsetX(400.0f), offsetY(300.0f) {}
    
    // 绘制坐标轴
    void drawAxes() {
        // X轴
        sf::Vertex xAxis[] = {
            sf::Vertex(sf::Vector2f(0, offsetY), sf::Color::Black),
            sf::Vertex(sf::Vector2f(window.getSize().x, offsetY), sf::Color::Black)
        };
        window.draw(xAxis, 2, sf::Lines);
        
        // Y轴
        sf::Vertex yAxis[] = {
            sf::Vertex(sf::Vector2f(offsetX, 0), sf::Color::Black),
            sf::Vertex(sf::Vector2f(offsetX, window.getSize().y), sf::Color::Black)
        };
        window.draw(yAxis, 2, sf::Lines);
        
        // 绘制刻度
        drawGrid();
    }
    
    // 绘制网格
    void drawGrid() {
        // 水平网格线
        for (float y = offsetY; y < window.getSize().y; y += scaleY) {
            sf::Vertex line[] = {
                sf::Vertex(sf::Vector2f(0, y), sf::Color(200, 200, 200)),
                sf::Vertex(sf::Vector2f(window.getSize().x, y), sf::Color(200, 200, 200))
            };
            window.draw(line, 2, sf::Lines);
        }
        
        for (float y = offsetY; y > 0; y -= scaleY) {
            sf::Vertex line[] = {
                sf::Vertex(sf::Vector2f(0, y), sf::Color(200, 200, 200)),
                sf::Vertex(sf::Vector2f(window.getSize().x, y), sf::Color(200, 200, 200))
            };
            window.draw(line, 2, sf::Lines);
        }
        
        // 垂直网格线
        for (float x = offsetX; x < window.getSize().x; x += scaleX) {
            sf::Vertex line[] = {
                sf::Vertex(sf::Vector2f(x, 0), sf::Color(200, 200, 200)),
                sf::Vertex(sf::Vector2f(x, window.getSize().y), sf::Color(200, 200, 200))
            };
            window.draw(line, 2, sf::Lines);
        }
        
        for (float x = offsetX; x > 0; x -= scaleX) {
            sf::Vertex line[] = {
                sf::Vertex(sf::Vector2f(x, 0), sf::Color(200, 200, 200)),
                sf::Vertex(sf::Vector2f(x, window.getSize().y), sf::Color(200, 200, 200))
            };
            window.draw(line, 2, sf::Lines);
        }
    }
    
    // 绘制函数图像
    void plotFunction(std::function<float(float)> func, sf::Color color) {
        points.clear();
        
        int width = window.getSize().x;
        
        for (int pixelX = 0; pixelX < width; pixelX++) {
            // 将像素坐标转换为数学坐标
            float mathX = (pixelX - offsetX) / scaleX;
            float mathY = func(mathX);
            
            // 将数学坐标转换回像素坐标
            float pixelY = offsetY - mathY * scaleY;
            
            // 确保Y坐标在窗口范围内
            if (pixelY >= 0 && pixelY < window.getSize().y) {
                points.push_back(sf::Vertex(
                    sf::Vector2f(pixelX, pixelY), color
                ));
            }
        }
        
        // 绘制函数曲线
        if (!points.empty()) {
            window.draw(&points[0], points.size(), sf::LineStrip);
        }
    }
    
    // 处理缩放和拖动
    void handleInput() {
        // 鼠标滚轮缩放
        // 鼠标拖动平移
        // 实际实现需要处理SFML事件
    }
};

int main() {
    sf::RenderWindow window(sf::VideoMode(800, 600), "函数绘图器");
    FunctionPlotter plotter(window);
    
    // 定义要绘制的函数
    auto sinFunc = [](float x) { return sin(x); };
    auto parabola = [](float x) { return x * x; };
    auto cubic = [](float x) { return x * x * x / 10.0f; };
    
    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }
        
        window.clear(sf::Color::White);
        
        // 绘制坐标轴
        plotter.drawAxes();
        
        // 绘制多个函数
        plotter.plotFunction(sinFunc, sf::Color::Red);
        plotter.plotFunction(parabola, sf::Color::Blue);
        plotter.plotFunction(cubic, sf::Color::Green);
        
        window.display();
    }
    
    return 0;
}

第五章:游戏开发与项目实战

5.1 简单游戏示例:打砖块

cpp

复制下载

// brick_breaker.cpp
#include <SFML/Graphics.hpp>
#include <vector>

class Brick {
public:
    sf::RectangleShape shape;
    bool destroyed;
    
    Brick(float x, float y, sf::Color color) : destroyed(false) {
        shape.setPosition(x, y);
        shape.setSize(sf::Vector2f(80, 30));
        shape.setFillColor(color);
        shape.setOutlineColor(sf::Color::Black);
        shape.setOutlineThickness(1);
    }
    
    void draw(sf::RenderWindow& window) {
        if (!destroyed) {
            window.draw(shape);
        }
    }
    
    bool contains(sf::Vector2f point) {
        return shape.getGlobalBounds().contains(point);
    }
};

int main() {
    sf::RenderWindow window(sf::VideoMode(800, 600), "打砖块游戏");
    window.setFramerateLimit(60);
    
    // 创建挡板
    sf::RectangleShape paddle(sf::Vector2f(120, 20));
    paddle.setFillColor(sf::Color::Blue);
    paddle.setPosition(350, 550);
    
    // 创建球
    sf::CircleShape ball(15);
    ball.setFillColor(sf::Color::Red);
    ball.setPosition(400, 500);
    sf::Vector2f ballVelocity(4, -4);
    
    // 创建砖块
    std::vector<Brick> bricks;
    for (int i = 0; i < 10; i++) {
        for (int j = 0; j < 5; j++) {
            sf::Color color = sf::Color((i * 25) % 255, (j * 50) % 255, 150);
            bricks.push_back(Brick(i * 80 + 10, j * 35 + 50, color));
        }
    }
    
    while (window.isOpen()) {
        sf::Event event;
        while (window.pollEvent(event)) {
            if (event.type == sf::Event::Closed)
                window.close();
        }
        
        // 控制挡板
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
            if (paddle.getPosition().x > 0)
                paddle.move(-8, 0);
        }
        if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) {
            if (paddle.getPosition().x < 680)
                paddle.move(8, 0);
        }
        
        // 更新球的位置
        ball.move(ballVelocity);
        
        // 球与边界碰撞
        if (ball.getPosition().x < 0 || ball.getPosition().x > 770)
            ballVelocity.x = -ballVelocity.x;
        if (ball.getPosition().y < 0)
            ballVelocity.y = -ballVelocity.y;
        
        // 球与挡板碰撞
        if (ball.getGlobalBounds().intersects(paddle.getGlobalBounds())) {
            ballVelocity.y = -abs(ballVelocity.y);  // 确保向上反弹
        }
        
        // 球与砖块碰撞
        for (auto& brick : bricks) {
            if (!brick.destroyed && 
                ball.getGlobalBounds().intersects(brick.shape.getGlobalBounds())) {
                brick.destroyed = true;
                ballVelocity.y = -ballVelocity.y;
                break;
            }
        }
        
        // 游戏结束条件
        if (ball.getPosition().y > 600) {
            // 游戏结束逻辑
            ball.setPosition(400, 500);
        }
        
        // 渲染
        window.clear(sf::Color::White);
        
        // 绘制所有元素
        window.draw(paddle);
        window.draw(ball);
        for (auto& brick : bricks) {
            brick.draw(window);
        }
        
        window.display();
    }
    
    return 0;
}

学习路径与进阶方向

学习路线图:

  1. 第1-2周:SFML基础、图形绘制、简单动画
  2. 第3-4周:数据结构可视化、算法动画
  3. 第5-6周:交互设计、用户界面开发
  4. 第7-8周:项目实战、游戏开发

推荐资源:

  1. SFML官方文档www.sfml-dev.org/
  2. ImGui-SFML:即时模式GUI库
  3. TGUI:高级GUI库
  4. Box2D:物理引擎集成

进阶方向:

  1. OpenGL集成:3D图形编程
  2. 计算机视觉:图像处理与识别
  3. 数据可视化:复杂数据图表
  4. VR/AR开发:虚拟现实应用

结语:可视化编程的价值

C++可视化不仅仅是让程序"好看",更重要的是:

  1. 提升学习兴趣:直观的反馈让编程更有成就感
  2. 加深理解:将抽象概念转化为具体图像
  3. 培养工程思维:从界面设计到性能优化
  4. 扩展应用领域:游戏开发、科学计算、数据可视化

《C++零基础可视化》课程的核心目标是:让每个C++学习者都能看到自己代码的"生命力",通过图形化界面与程序进行深度对话。从今天开始,让你的C++代码不仅能够计算,更能"绘声绘色"地展示它的价值!