在 VS2022 中创建 Qt C++ 项目并配置 OpenSceneGraph 3.6.5,进行三维模型开发

在 VS2022 中创建 Qt C++ 项目并配置 OpenSceneGraph 3.6.5

以下是详细的步骤指南,帮助您在 Visual Studio 2022 中创建 Qt C++ 项目并配置 OpenSceneGraph 3.6.5 开发环境:

1. 环境准备

1.1 安装必要软件

  1. Visual Studio 2022

    • 安装时选择:
      • 使用 C++ 的桌面开发
      • Windows 10/11 SDK
      • C++ CMake 工具
      • MSVC v143 - VS 2022 C++ x64/x86 生成工具
  2. Qt 6.5.0+ (MSVC 2022 64-bit)

    • https://www.qt.io/download 下载在线安装器
    • 选择组件:
      • Qt 6.5.0 → MSVC 2022 64-bit
      • Qt Creator (可选)
      • Qt Visual Studio Tools
  3. OpenSceneGraph 3.6.5 VC2022 64-bit

    • 下载预编译包:OpenSceneGraph-3.6.5-VC2022-64-2025-04
    • 解压到:C:\OSG\D:\Development\OSG\
  4. osgQOpenGL 库

    • 需要从源码编译或下载预编译版本

2. 配置系统环境变量

2.1 添加环境变量

在系统环境变量中添加:

复制代码
Path 添加:
C:\Qt\6.5.0\msvc2022_64\bin
C:\OSG\bin
C:\OSG\bin\osgPlugins-3.6.5

新建变量:
OSG_FILE_PATH = C:\OSG\data
QT_DIR = C:\Qt\6.5.0\msvc2022_64
OSG_DIR = C:\OSG

2.2 验证安装

打开命令提示符,运行:

cmd 复制代码
osgversion
qmake --version

3. 创建 Qt C++ 项目

3.1 方法一:使用 Qt Creator 创建

  1. 打开 Qt Creator
  2. 新建项目 → Qt Widgets Application
  3. 选择工具链:Desktop Qt 6.5.0 MSVC2022 64-bit
  4. 项目名称:OSGViewer
  5. 创建完成后,在 .pro 文件中添加:
pro 复制代码
# OSGViewer.pro
QT       += core gui opengl

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++17

# 包含目录
INCLUDEPATH += $$(OSG_DIR)/include
INCLUDEPATH += $$(OSG_DIR)/include/osg
INCLUDEPATH += $$(OSG_DIR)/include/osgDB
INCLUDEPATH += $$(OSG_DIR)/include/osgGA
INCLUDEPATH += $$(OSG_DIR)/include/osgViewer

# 库目录
LIBS += -L$$(OSG_DIR)/lib

# 链接库
LIBS += -losgd
LIBS += -losgDBd
LIBS += -losgGAd
LIBS += -losgViewerd
LIBS += -losgUtild
LIBS += -losgTextd
LIBS += -lOpenThreadsd

# 如果是 Release 版本,去掉 'd' 后缀
CONFIG(debug, debug|release) {
    # 调试版本
} else {
    LIBS += -losg
    LIBS += -losgDB
    LIBS += -losgGA
    LIBS += -losgViewer
    LIBS += -losgUtil
    LIBS += -losgText
    LIBS += -lOpenThreads
}

# 输出目录
DESTDIR = $$OUT_PWD/bin
OBJECTS_DIR = $$OUT_PWD/obj
MOC_DIR = $$OUT_PWD/moc
RCC_DIR = $$OUT_PWD/rcc
UI_DIR = $$OUT_PWD/ui

3.2 方法二:使用 VS2022 创建

  1. 打开 VS2022
  2. 创建新项目 → Qt Widgets Application
  3. 配置项目:
    • 项目名称:OSGViewer
    • 位置:选择项目文件夹
    • 解决方案名称:OSGViewer
    • Qt 版本:选择 Qt 6.5.0 MSVC2022 64-bit

4. 配置 VS2022 项目属性

4.1 打开项目属性

右键项目 → 属性

4.2 配置包含目录

VC++ 目录 → 包含目录:

复制代码
$(OSG_DIR)\include
$(OSG_DIR)\include\osg
$(OSG_DIR)\include\osgDB
$(OSG_DIR)\include\osgGA
$(OSG_DIR)\include\osgViewer
$(OSG_DIR)\include\osgUtil
$(OSG_DIR)\include\osgText
$(QT_DIR)\include

4.3 配置库目录

VC++ 目录 → 库目录:

复制代码
$(OSG_DIR)\lib
$(QT_DIR)\lib

4.4 配置链接器

链接器 → 输入 → 附加依赖项:

Debug 配置:

复制代码
osgd.lib
osgDBd.lib
osgGAd.lib
osgViewerd.lib
osgUtild.lib
osgTextd.lib
OpenThreadsd.lib
Qt6Cored.lib
Qt6Guid.lib
Qt6Widgetsd.lib
Qt6OpenGLd.lib

Release 配置:

复制代码
osg.lib
osgDB.lib
osgGA.lib
osgViewer.lib
osgUtil.lib
osgText.lib
OpenThreads.lib
Qt6Core.lib
Qt6Gui.lib
Qt6Widgets.lib
Qt6OpenGL.lib

4.5 配置预处理器定义

C/C++ → 预处理器 → 预处理器定义:

复制代码
_CRT_SECURE_NO_WARNINGS
_SCL_SECURE_NO_WARNINGS
WIN32
_WINDOWS
UNICODE
_UNICODE
QT_CORE_LIB
QT_GUI_LIB
QT_WIDGETS_LIB
QT_OPENGL_LIB

4.6 配置调试环境

调试 → 环境:

复制代码
PATH=$(OSG_DIR)\bin;$(QT_DIR)\bin;%PATH%
OSG_FILE_PATH=$(OSG_DIR)\data

5. 创建 OSG 窗口类

5.1 OSGWidget.h

cpp 复制代码
#pragma once

#include <QOpenGLWidget>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include <osgGA/TrackballManipulator>
#include <osgDB/ReadFile>
#include <osg/Group>

class OSGWidget : public QOpenGLWidget
{
    Q_OBJECT

public:
    explicit OSGWidget(QWidget* parent = nullptr);
    ~OSGWidget();

    bool loadModel(const QString& filePath);

protected:
    virtual void initializeGL() override;
    virtual void resizeGL(int w, int h) override;
    virtual void paintGL() override;

    virtual void mousePressEvent(QMouseEvent* event) override;
    virtual void mouseReleaseEvent(QMouseEvent* event) override;
    virtual void mouseMoveEvent(QMouseEvent* event) override;
    virtual void wheelEvent(QWheelEvent* event) override;

private:
    osg::ref_ptr<osgViewer::Viewer> m_viewer;
    osg::ref_ptr<osg::Group> m_root;
    osg::ref_ptr<osg::Node> m_model;
    osg::ref_ptr<osgGA::TrackballManipulator> m_manipulator;
    QPoint m_lastMousePos;
};

5.2 OSGWidget.cpp

cpp 复制代码
#include "OSGWidget.h"
#include <QMouseEvent>
#include <QWheelEvent>
#include <QTimer>
#include <osg/MatrixTransform>
#include <osg/Geode>
#include <osg/ShapeDrawable>
#include <osg/StateSet>
#include <osg/PolygonMode>

OSGWidget::OSGWidget(QWidget* parent)
    : QOpenGLWidget(parent)
    , m_viewer(new osgViewer::Viewer)
    , m_root(new osg::Group)
    , m_manipulator(new osgGA::TrackballManipulator)
{
    setFocusPolicy(Qt::StrongFocus);
    
    // 设置场景数据
    m_viewer->setSceneData(m_root);
    
    // 设置相机操作器
    m_viewer->setCameraManipulator(m_manipulator);
    
    // 添加状态事件处理器
    m_viewer->addEventHandler(new osgViewer::StatsHandler);
    
    // 设置线程模型
    m_viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded);
    
    // 创建默认场景(一个立方体)
    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    osg::ref_ptr<osg::Box> box = new osg::Box(osg::Vec3(0, 0, 0), 1.0f);
    osg::ref_ptr<osg::ShapeDrawable> shape = new osg::ShapeDrawable(box);
    shape->setColor(osg::Vec4(0.8f, 0.2f, 0.2f, 1.0f));
    geode->addDrawable(shape);
    
    // 设置线框模式
    osg::ref_ptr<osg::StateSet> stateset = geode->getOrCreateStateSet();
    osg::ref_ptr<osg::PolygonMode> pm = new osg::PolygonMode(
        osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE);
    stateset->setAttributeAndModes(pm, osg::StateAttribute::ON);
    
    m_root->addChild(geode);
    
    // 设置定时器刷新
    QTimer* timer = new QTimer(this);
    connect(timer, &QTimer::timeout, this,  { update(); });
    timer->start(16); // 约60FPS
}

OSGWidget::~OSGWidget()
{
    m_viewer->setDone(true);
}

void OSGWidget::initializeGL()
{
    // 初始化OpenGL上下文
    m_viewer->realize();
    
    // 设置背景色
    m_viewer->getCamera()->setClearColor(osg::Vec4(0.1f, 0.1f, 0.1f, 1.0f));
    
    // 设置投影矩阵
    m_viewer->getCamera()->setProjectionMatrixAsPerspective(
        30.0f, static_cast<double>(width()) / height(), 1.0f, 10000.0f);
}

void OSGWidget::resizeGL(int w, int h)
{
    // 更新视口
    m_viewer->getCamera()->setViewport(0, 0, w, h);
    
    // 更新投影矩阵
    m_viewer->getCamera()->setProjectionMatrixAsPerspective(
        30.0f, static_cast<double>(w) / h, 1.0f, 10000.0f);
}

void OSGWidget::paintGL()
{
    // 渲染场景
    m_viewer->frame();
}

bool OSGWidget::loadModel(const QString& filePath)
{
    // 移除旧模型
    if (m_model.valid())
    {
        m_root->removeChild(m_model);
    }
    
    // 加载新模型
    m_model = osgDB::readNodeFile(filePath.toStdString());
    if (!m_model.valid())
    {
        return false;
    }
    
    m_root->addChild(m_model);
    
    // 自动调整视图
    osg::BoundingSphere bs = m_model->getBound();
    if (bs.valid())
    {
        m_manipulator->setHomePosition(
            bs.center() + osg::Vec3d(0.0, -bs.radius() * 3.0, bs.radius() * 0.5),
            bs.center(),
            osg::Vec3d(0.0, 0.0, 1.0),
            false);
        m_manipulator->home(0.0);
    }
    
    return true;
}

void OSGWidget::mousePressEvent(QMouseEvent* event)
{
    m_lastMousePos = event->pos();
    
    // 处理鼠标事件
    if (event->button() == Qt::LeftButton)
    {
        // 旋转
    }
    else if (event->button() == Qt::MiddleButton)
    {
        // 平移
    }
    else if (event->button() == Qt::RightButton)
    {
        // 缩放
    }
    
    QOpenGLWidget::mousePressEvent(event);
}

void OSGWidget::mouseReleaseEvent(QMouseEvent* event)
{
    QOpenGLWidget::mouseReleaseEvent(event);
}

void OSGWidget::mouseMoveEvent(QMouseEvent* event)
{
    int dx = event->x() - m_lastMousePos.x();
    int dy = event->y() - m_lastMousePos.y();
    
    if (event->buttons() & Qt::LeftButton)
    {
        // 旋转
        m_manipulator->rotate(dx * 0.01, dy * 0.01);
    }
    else if (event->buttons() & Qt::MiddleButton)
    {
        // 平移
        m_manipulator->pan(dx * 0.01, dy * 0.01);
    }
    else if (event->buttons() & Qt::RightButton)
    {
        // 缩放
        m_manipulator->zoom(dy * 0.01);
    }
    
    m_lastMousePos = event->pos();
    update();
    
    QOpenGLWidget::mouseMoveEvent(event);
}

void OSGWidget::wheelEvent(QWheelEvent* event)
{
    // 滚轮缩放
    QPoint numDegrees = event->angleDelta() / 8;
    if (!numDegrees.isNull())
    {
        m_manipulator->zoom(numDegrees.y() * 0.1);
        update();
    }
    
    QOpenGLWidget::wheelEvent(event);
}

6. 创建主窗口

6.1 MainWindow.h

cpp 复制代码
#pragma once

#include <QMainWindow>
#include <QFileDialog>
#include <QMessageBox>
#include "OSGWidget.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget* parent = nullptr);
    ~MainWindow();

private slots:
    void on_actionOpen_triggered();
    void on_actionExit_triggered();
    void on_actionWireframe_triggered(bool checked);
    void on_actionSolid_triggered();

private:
    Ui::MainWindow* ui;
    OSGWidget* m_osgWidget;
};

6.2 MainWindow.cpp

cpp 复制代码
#include "MainWindow.h"
#include "ui_MainWindow.h"

MainWindow::MainWindow(QWidget* parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    
    // 创建OSG窗口
    m_osgWidget = new OSGWidget(this);
    setCentralWidget(m_osgWidget);
    
    // 连接菜单信号
    connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::on_actionOpen_triggered);
    connect(ui->actionExit, &QAction::triggered, this, &MainWindow::on_actionExit_triggered);
    connect(ui->actionWireframe, &QAction::toggled, this, &MainWindow::on_actionWireframe_triggered);
    connect(ui->actionSolid, &QAction::triggered, this, &MainWindow::on_actionSolid_triggered);
    
    // 设置窗口标题
    setWindowTitle("OSG Qt Viewer - 未加载模型");
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::on_actionOpen_triggered()
{
    QString fileName = QFileDialog::getOpenFileName(
        this,
        "打开IVE模型文件",
        QDir::currentPath(),
        "IVE文件 (*.ive);;OSG文件 (*.osg *.osgt *.osgb);;所有文件 (*.*)"
    );
    
    if (fileName.isEmpty())
    {
        return;
    }
    
    if (m_osgWidget->loadModel(fileName))
    {
        setWindowTitle(QString("OSG Qt Viewer - %1").arg(QFileInfo(fileName).fileName()));
        statusBar()->showMessage(QString("成功加载: %1").arg(fileName), 3000);
    }
    else
    {
        QMessageBox::warning(this, "错误", 
            QString("无法加载模型文件:\n%1\n请检查文件格式和路径。").arg(fileName));
    }
}

void MainWindow::on_actionExit_triggered()
{
    close();
}

void MainWindow::on_actionWireframe_triggered(bool checked)
{
    // 线框模式切换
    // 需要在OSGWidget中添加相应功能
}

void MainWindow::on_actionSolid_triggered()
{
    // 实体模式
    // 需要在OSGWidget中添加相应功能
}

6.3 MainWindow.ui (Qt Designer 设计)

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>OSG Qt Viewer</string>
  </property>
  <widget class="QWidget" name="centralwidget"/>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>22</height>
    </rect>
   </property>
   <widget class="QMenu" name="menuFile">
    <property name="title">
     <string>文件(&F)</string>
    </property>
    <addaction name="actionOpen"/>
    <addaction name="separator"/>
    <addaction name="actionExit"/>
   </widget>
   <widget class="QMenu" name="menuView">
    <property name="title">
     <string>视图(&V)</string>
    </property>
    <addaction name="actionWireframe"/>
    <addaction name="actionSolid"/>
   </widget>
   <addaction name="menuFile"/>
   <addaction name="menuView"/>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
  <action name="actionOpen">
   <property name="text">
    <string>打开模型(&O)</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+O</string>
   </property>
  </action>
  <action name="actionExit">
   <property name="text">
    <string>退出(&X)</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+Q</string>
   </property>
  </action>
  <action name="actionWireframe">
   <property name="checkable">
    <bool>true</bool>
   </property>
   <property name="text">
    <string>线框模式(&W)</string>
   </property>
   <property name="shortcut">
    <string>F2</string>
   </property>
  </action>
  <action name="actionSolid">
   <property name="text">
    <string>实体模式(&S)</string>
   </property>
   <property name="shortcut">
    <string>F3</string>
   </property>
  </action>
 </widget>
 <resources/>
 <connections/>
</ui>

7. 主程序入口

7.1 main.cpp

cpp 复制代码
#include "MainWindow.h"
#include <QApplication>
#include <QSurfaceFormat>

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    
    // 设置OpenGL格式
    QSurfaceFormat format;
    format.setRenderableType(QSurfaceFormat::OpenGL);
    format.setProfile(QSurfaceFormat::CoreProfile);
    format.setVersion(3, 3);
    format.setDepthBufferSize(24);
    format.setStencilBufferSize(8);
    format.setSamples(4); // 4x MSAA
    QSurfaceFormat::setDefaultFormat(format);
    
    // 设置应用程序信息
    app.setApplicationName("OSG Qt Viewer");
    app.setOrganizationName("YourCompany");
    app.setApplicationVersion("1.0.0");
    
    MainWindow window;
    window.show();
    
    return app.exec();
}

8. 编译和运行

8.1 编译步骤

  1. 生成解决方案

    • 在 VS2022 中:生成 → 生成解决方案 (Ctrl+Shift+B)
  2. 复制 DLL 文件

    创建 copy_dlls.bat 脚本:

    batch 复制代码
    @echo off
    set BUILD_DIR=.\x64\Debug
    set QT_DIR=C:\Qt\6.5.0\msvc2022_64\bin
    set OSG_DIR=C:\OSG\bin
    
    echo 复制Qt DLL文件...
    copy "%QT_DIR%\Qt6Cored.dll" "%BUILD_DIR%"
    copy "%QT_DIR%\Qt6Guid.dll" "%BUILD_DIR%"
    copy "%QT_DIR%\Qt6Widgetsd.dll" "%BUILD_DIR%"
    copy "%QT_DIR%\Qt6OpenGLd.dll" "%BUILD_DIR%"
    
    echo 复制OSG DLL文件...
    copy "%OSG_DIR%\osgd.dll" "%BUILD_DIR%"
    copy "%OSG_DIR%\osgDBd.dll" "%BUILD_DIR%"
    copy "%OSG_DIR%\osgGAd.dll" "%BUILD_DIR%"
    copy "%OSG_DIR%\osgViewerd.dll" "%BUILD_DIR%"
    copy "%OSG_DIR%\osgUtild.dll" "%BUILD_DIR%"
    copy "%OSG_DIR%\osgTextd.dll" "%BUILD_DIR%"
    copy "%OSG_DIR%\OpenThreadsd.dll" "%BUILD_DIR%"
    
    echo 复制插件...
    xcopy "%OSG_DIR%\osgPlugins-3.6.5" "%BUILD_DIR%\osgPlugins-3.6.5" /E /I /Y
    
    echo 完成!
    pause
  3. 运行程序

    • 按 F5 调试运行
    • 或直接运行 x64\Debug\OSGViewer.exe

8.2 常见问题解决

问题1:找不到 Qt 库

解决方案:

batch 复制代码
# 在系统环境变量中添加
Path += C:\Qt\6.5.0\msvc2022_64\bin
问题2:OSG 插件加载失败

解决方案:

cpp 复制代码
// 在 main.cpp 中添加
#include <osgDB/Registry>
int main(int argc, char* argv[])
{
    // 设置插件路径
    osgDB::Registry::instance()->setLibraryFilePathList(
        "C:/OSG/bin/osgPlugins-3.6.5");
    // ...
}
问题3:OpenGL 版本不兼容

解决方案:

cpp 复制代码
// 在 OSGWidget.cpp 的 initializeGL 中添加
osg::DisplaySettings::instance()->setGLContextVersion("3.3");
osg::DisplaySettings::instance()->setGLContextProfileMask(
    osg::DisplaySettings::CORE_PROFILE);

9. 使用 osgQOpenGL 库的替代方案

如果您想使用 osgQOpenGL 库,可以按以下步骤:

9.1 编译 osgQOpenGL

  1. 下载源码:

    bash 复制代码
    git clone https://github.com/openscenegraph/osgQt.git
  2. 使用 CMake 配置:

    • 设置 OpenSceneGraph_DIR: C:/OSG/lib/cmake/OpenSceneGraph
    • 设置 Qt6_DIR: C:/Qt/6.5.0/msvc2022_64/lib/cmake/Qt6
    • 生成 VS2022 解决方案
  3. 编译并安装

9.2 修改项目配置

在项目属性中添加:

  • 包含目录: C:/OSG/include/osgQOpenGL
  • 库目录: C:/OSG/lib
  • 附加依赖项: osgQOpenGLd.lib

9.3 修改 OSGWidget

cpp 复制代码
// 使用 osgQOpenGLWidget
#include <osgQOpenGL/osgQOpenGLWidget>

class OSGWidget : public osgQOpenGLWidget
{
    // ...
};

10. 项目结构

复制代码
OSGViewer/
├── OSGViewer.sln
├── OSGViewer/
│   ├── OSGViewer.vcxproj
│   ├── main.cpp
│   ├── MainWindow.h
│   ├── MainWindow.cpp
│   ├── MainWindow.ui
│   ├── OSGWidget.h
│   └── OSGWidget.cpp
├── x64/
│   └── Debug/
│       ├── OSGViewer.exe
│       ├── *.dll
│       └── osgPlugins-3.6.5/
└── resources/
    └── models/
        └── example.ive

11. 测试模型

您可以从以下位置获取测试模型:

  1. OpenSceneGraph 示例数据:C:\OSG\data
  2. 在线资源:

12. 高级功能扩展

12.1 添加模型列表

cpp 复制代码
// 在 MainWindow 中添加
QListWidget* m_modelList;
void addModelToList(const QString& filePath);

12.2 添加灯光控制

cpp 复制代码
// 在 OSGWidget 中添加
osg::ref_ptr<osg::LightSource> m_lightSource;
void setLightEnabled(bool enabled);
void setLightPosition(const osg::Vec3& position);

12.3 添加截图功能

cpp 复制代码
void OSGWidget::saveScreenshot(const QString& filePath)
{
    osg::ref_ptr<osg::Image> image = new osg::Image;
    m_viewer->getCamera()->attach(
        osg::Camera::COLOR_BUFFER, image.get());
    m_viewer->frame();
    osgDB::writeImageFile(*image, filePath.toStdString());
}

这个配置指南应该能帮助您在 VS2022 中成功创建和配置 Qt C++ 项目,并使用 OpenSceneGraph 3.6.5 进行三维开发。

相关推荐
tankeven2 小时前
HJ133 隐匿社交网络
c++·算法
xcLeigh2 小时前
SQL 注入防不住?金仓内核级防火墙,白名单防护零误报
数据库·数据安全·sql注入·kingbasees·金仓数据库·数据补丁
fareast_mzh2 小时前
Mistral AI本地部署 C++无需Nvidiad独立显卡也能运行(CPU推理)
开发语言·c++·人工智能
轩情吖2 小时前
MySQL之复合查询
android·数据库·mysql·多表·符合查询·自连接·合并查询
Predestination王瀞潞2 小时前
2.3 依赖管理Maven工具->dependency详解:JUnit 3.8.1 vs 4.12
数据库·junit
FirstFrost --sy2 小时前
MySQL表的增删查改
数据库·mysql
m0_716667072 小时前
C++中的访问者模式高级应用
开发语言·c++·算法
小江的记录本2 小时前
【会话:Cookie与Session】Cookie与Session的区别(附对比表)
java·数据库·后端·sql·http·https·安全架构
Oueii2 小时前
C++中的访问者模式变体
开发语言·c++·算法