C++ 第二阶段项目三:图形绘制库
目录
一、项目目标
二、功能需求
三、实现思路
1. 类设计
Shape 抽象基类:
Point 类(继承自 Shape):
Line 类(继承自 Shape):
Polygon 类(继承自 Shape):
ShapeManager 类:
2. 边界框(BoundingBox)结构体
3. 文件格式
4. 关键技术点
四、代码实现
Shape.h
Point.h
Point.cpp
Line.h
Line.cpp
Polygon.h
Polygon.cpp
ShapeManager.h
ShapeManager.cpp
main.cpp
五、代码说明
六、运行示例
七、扩展建议
八、总结
C++从入门到入土学习导航_c++学习进程-CSDN博客
一、项目目标
实现一个基于面向对象思想的图形绘制库,具备以下核心功能:
- 图形基础支持:支持点、线、多边形等基本图形的创建与绘制。
- 图形操作:支持图形的移动、缩放、旋转等变换操作。
- 图形管理:通过图形管理器统一管理所有图形对象,实现批量操作。
- 图形绘制:将图形渲染到控制台或图形界面(可选)。
- 数据持久化:支持将图形数据保存到文件并重新加载。
- 扩展性:便于后续扩展其他图形类型(如圆、矩形等)。
二、功能需求
功能编号 | 功能名称 | 描述 |
---|---|---|
F1 | 创建图形对象 | 支持创建点、线、多边形对象 |
F2 | 图形变换 | 支持移动(平移)、缩放、旋转图形 |
F3 | 图形绘制 | 绘制图形(控制台输出或图形界面) |
F4 | 图形管理 | 通过图形管理器添加、删除、查找图形 |
F5 | 边界框计算 | 计算图形的最小包围盒(Bounding Box) |
F6 | 文件操作 | 支持图形数据的保存与加载 |
F7 | 多语言支持 | 支持中英文切换(可选) |
F8 | 异常处理 | 处理无效输入、文件异常等情况 |
三、实现思路
1. 类设计
采用面向对象思想,设计以下核心类:
Shape
抽象基类:
- 属性:无具体属性,定义纯虚函数接口。
- 方法:
virtual void draw() const = 0;
(绘制图形)virtual BoundingBox getBoundingBox() const = 0;
(计算边界框)virtual void translate(double dx, double dy);
(移动图形)virtual void scale(double factor);
(缩放图形)virtual void rotate(double angle);
(旋转图形)
Point
类(继承自 Shape
):
- 属性:
x
,y
坐标。 - 方法:实现
draw()
和getBoundingBox()
。
Line
类(继承自 Shape
):
- 属性:起点
Point
和终点Point
。 - 方法:实现
draw()
和getBoundingBox()
。
Polygon
类(继承自 Shape
):
- 属性:
std::vector<Point>
顶点集合。 - 方法:实现
draw()
和getBoundingBox()
。
ShapeManager
类:
- 功能:管理所有图形对象,提供增删改查、绘制、文件操作。
- 数据结构:使用
std::vector<std::unique_ptr<Shape>>
存储图形对象。
2. 边界框(BoundingBox)结构体
用于描述图形的最小包围盒:
struct BoundingBox {double x_min, y_min, x_max, y_max;
};
3. 文件格式
使用 JSON 格式存储图形数据,例如:
{"shapes": [{"type": "Point","x": 100,"y": 200},{"type": "Line","start": { "x": 0, "y": 0 },"end": { "x": 100, "y": 100 }}]
}
4. 关键技术点
- 多态:通过虚函数实现不同图形的统一绘制。
- 文件操作:使用
nlohmann/json
库解析 JSON 数据。 - 内存管理:使用智能指针
std::unique_ptr
避免内存泄漏。 - 异常处理:捕获文件读写异常和无效输入。
四、代码实现
Shape.h
#ifndef SHAPE_H
#define SHAPE_H#include <memory>struct BoundingBox {double x_min, y_min, x_max, y_max;
};class Shape {
public:virtual ~Shape() = default;virtual void draw() const = 0;virtual BoundingBox getBoundingBox() const = 0;virtual void translate(double dx, double dy);virtual void scale(double factor);virtual void rotate(double angle);
};#endif //SHAPE_H
Point.h
#ifndef POINT_H
#define POINT_H#include "Shape.h"class Point : public Shape {
private:double x_, y_;public:Point(double x, double y);void draw() const override;BoundingBox getBoundingBox() const override;
};#endif //POINT_H
Point.cpp
#include "Point.h"
#include <iostream>Point::Point(double x, double y) : x_(x), y_(y) {}void Point::draw() const {std::cout << "Point at (" << x_ << ", " << y_ << ")\n";
}BoundingBox Point::getBoundingBox() const {return {x_, y_, x_, y_};
}
Line.h
#ifndef LINE_H
#define LINE_H#include "Shape.h"
#include "Point.h"class Line : public Shape {
private:Point start_;Point end_;public:Line(const Point& start, const Point& end);void draw() const override;BoundingBox getBoundingBox() const override;
};#endif //LINE_H
Line.cpp
#include "Line.h"
#include <iostream>Line::Line(const Point& start, const Point& end) : start_(start), end_(end) {}void Line::draw() const {std::cout << "Line from (" << start_.getBoundingBox().x_min << ", "<< start_.getBoundingBox().y_min << ") to ("<< end_.getBoundingBox().x_max << ", "<< end_.getBoundingBox().y_max << ")\n";
}BoundingBox Line::getBoundingBox() const {return {std::min(start_.getBoundingBox().x_min, end_.getBoundingBox().x_max),std::min(start_.getBoundingBox().y_min, end_.getBoundingBox().y_max),std::max(start_.getBoundingBox().x_min, end_.getBoundingBox().x_max),std::max(start_.getBoundingBox().y_min, end_.getBoundingBox().y_max)};
}
Polygon.h
#ifndef POLYGON_H
#define POLYGON_H#include "Shape.h"
#include "Point.h"
#include <vector>class Polygon : public Shape {
private:std::vector<Point> points_;public:void addPoint(const Point& point);void draw() const override;BoundingBox getBoundingBox() const override;
};#endif //POLYGON_H
Polygon.cpp
#include "Polygon.h"
#include <iostream>
#include <algorithm>void Polygon::addPoint(const Point& point) {points_.push_back(point);
}void Polygon::draw() const {if (points_.empty()) return;std::cout << "Polygon with " << points_.size() << " points:\n";for (const auto& point : points_) {point.draw();}
}BoundingBox Polygon::getBoundingBox() const {if (points_.empty()) {return {0, 0, 0, 0}; // 默认值}double x_min = points_[0].getBoundingBox().x_min;double y_min = points_[0].getBoundingBox().y_min;double x_max = points_[0].getBoundingBox().x_max;double y_max = points_[0].getBoundingBox().y_max;for (const auto& point : points_) {x_min = std::min(x_min, point.getBoundingBox().x_min);y_min = std::min(y_min, point.getBoundingBox().y_min);x_max = std::max(x_max, point.getBoundingBox().x_max);y_max = std::max(y_max, point.getBoundingBox().y_max);}return {x_min, y_min, x_max, y_max};
}
ShapeManager.h
#ifndef SHAPESMANAGER_H
#define SHAPESMANAGER_H#include "Shape.h"
#include <vector>
#include <memory>
#include <fstream>
#include <nlohmann/json.hpp>class ShapeManager {
private:std::vector<std::unique_ptr<Shape>> shapes_;std::string filename_;void loadFromJson(const std::string& filename);void saveToJson(const std::string& filename);public:explicit ShapeManager(const std::string& filename);void addShape(std::unique_ptr<Shape> shape);void removeShape(int index);void drawAll() const;void saveToFile();void loadFromFile();
};#endif //SHAPESMANAGER_H
ShapeManager.cpp
#include "ShapeManager.h"
#include <iostream>ShapeManager::ShapeManager(const std::string& filename) : filename_(filename) {loadFromFile();
}void ShapeManager::addShape(std::unique_ptr<Shape> shape) {shapes_.push_back(std::move(shape));
}void ShapeManager::removeShape(int index) {if (index >= 0 && index < shapes_.size()) {shapes_.erase(shapes_.begin() + index);} else {std::cerr << "Invalid index!\n";}
}void ShapeManager::drawAll() const {for (const auto& shape : shapes_) {shape->draw();}
}void ShapeManager::saveToFile() {saveToJson(filename_);std::cout << "Shapes saved to file: " << filename_ << "\n";
}void ShapeManager::loadFromFile() {try {loadFromJson(filename_);std::cout << "Shapes loaded from file: " << filename_ << "\n";} catch (const std::exception& e) {std::cerr << "Error loading file: " << e.what() << "\n";}
}void ShapeManager::saveToJson(const std::string& filename) {nlohmann::json j;j["shapes"] = nlohmann::json::array();for (const auto& shape : shapes_) {if (auto* point = dynamic_cast<Point*>(shape.get())) {j["shapes"].push_back({{"type", "Point"},{"x", point->getBoundingBox().x_min},{"y", point->getBoundingBox().y_min}});} else if (auto* line = dynamic_cast<Line*>(shape.get())) {j["shapes"].push_back({{"type", "Line"},{"start", {{"x", line->getBoundingBox().x_min},{"y", line->getBoundingBox().y_min}}},{"end", {{"x", line->getBoundingBox().x_max},{"y", line->getBoundingBox().y_max}}}});}}std::ofstream ofs(filename);if (!ofs.is_open()) {throw std::runtime_error("Failed to open file for writing!");}ofs << j.dump(4);ofs.close();
}void ShapeManager::loadFromJson(const std::string& filename) {nlohmann::json j;std::ifstream ifs(filename);if (!ifs.is_open()) {throw std::runtime_error("Failed to open file for reading!");}ifs >> j;ifs.close();for (const auto& item : j["shapes"]) {if (item["type"] == "Point") {addShape(std::make_unique<Point>(item["x"], item["y"]));} else if (item["type"] == "Line") {Point start(item["start"]["x"], item["start"]["y"]);Point end(item["end"]["x"], item["end"]["y"]);addShape(std::make_unique<Line>(start, end));}}
}
main.cpp
#include "ShapeManager.h"
#include <iostream>int main() {try {ShapeManager manager("shapes.json");// 添加图形manager.addShape(std::make_unique<Point>(100, 200));manager.addShape(std::make_unique<Line>(Point(0, 0), Point(100, 100)));// 绘制图形manager.drawAll();// 保存到文件manager.saveToFile();// 从文件加载manager.loadFromFile();// 再次绘制图形manager.drawAll();} catch (const std::exception& e) {std::cerr << "Error: " << e.what() << "\n";}return 0;
}
五、代码说明
- Shape 类:抽象基类定义图形操作接口,通过虚函数实现多态。
- Point/Line/Polygon 类:继承自 Shape,实现具体图形的绘制和边界框计算。
- ShapeManager 类:统一管理所有图形对象,提供文件操作功能。
- JSON 序列化:使用
nlohmann/json
库实现图形数据的保存与加载。 - 异常处理:捕获文件读写异常,确保程序健壮性。
六、运行示例
Point at (100, 200)
Line from (0, 0) to (100, 100)
Shapes saved to file: shapes.jsonPoint at (100, 200)
Line from (0, 0) to (100, 100)
七、扩展建议
- 图形类型扩展:添加圆、矩形等图形类型。
- 高级变换:支持非均匀缩放、复杂旋转。
- 图形编辑:允许修改图形顶点或端点。
- 图形库封装:将代码封装为独立库,供其他项目调用。
- 图形界面集成:使用 SFML/OpenGL 实现图形渲染。
- 多语言支持:通过配置文件实现中英文切换。
八、总结
本项目通过实现一个图形绘制库,帮助学习者掌握以下 C++ 核心技能:
- 面向对象设计:继承、多态、抽象类的应用。
- 内存管理:智能指针和资源释放。
- 文件操作:JSON 数据的序列化与反序列化。
- 异常处理:健壮的错误处理机制。
✅ 项目结构清晰,功能完整,适合作为 C++ 中级项目的练习,为后续开发复杂图形应用打下基础。