详细介绍C++中通过OLE操作excel时,一般会出现哪些异常,这些异常的原因是什么,如何来解决这些异常

文章目录

C++ OLE操作Excel异常全解析:从原理到实战解决方案

引言:Excel OLE自动化异常的特点与挑战

通过OLE自动化操作Excel是C++程序中常见的需求,但这个过程充满了各种潜在的异常。与普通的C++异常不同,Excel OLE异常涉及进程间通信、COM组件模型、Excel对象模型等多个层次,具有复杂性高、错误信息不直观、调试困难等特点。本文将系统分析这些异常的类型、原因,并提供详细的解决方案。

第一章:Excel OLE异常分类体系

1.1 异常层次结构

复制代码
Excel OLE异常体系
├── 初始化与连接异常
│   ├── COM库初始化失败
│   ├── Excel实例创建失败
│   └── 版本兼容性异常
├── 文件操作异常
│   ├── 文件不存在或路径错误
│   ├── 文件格式不支持
│   ├── 文件被占用或权限不足
│   └── 文件损坏异常
├── 对象模型操作异常
│   ├── 对象引用无效
│   ├── 属性访问异常
│   ├── 方法调用异常
│   └── 集合操作异常
├── 数据操作异常
│   ├── 数据类型不匹配
│   ├── 范围越界异常
│   ├── 公式计算异常
│   └── 格式设置异常
├── 运行时环境异常
│   ├── 内存不足异常
│   ├── Excel进程无响应
│   ├── 自动化服务器异常
│   └── 超时异常
└── 权限与安全异常
    ├── 宏安全设置阻止
    ├── 受信任文档限制
    ├── UAC权限不足
    └── 防病毒软件干扰

第二章:初始化与连接异常详解

2.1 COM库初始化失败

异常现象:

  • CoInitialize/CoInitializeEx返回失败HRESULT
  • _com_error异常,错误码通常为RPC_E_CHANGED_MODE

根本原因:

  • COM库未初始化或重复初始化
  • 线程模型不匹配(单线程 vs 多线程)
  • COM版本冲突

解决方案:

cpp 复制代码
#include <comdef.h>
#include <windows.h>

class COMInitializer {
private:
    bool initialized_;
    HRESULT hr_;
    
public:
    COMInitializer(DWORD dwCoInit = COINIT_APARTMENTTHREADED) 
        : initialized_(false), hr_(S_OK) {
        
        hr_ = CoInitializeEx(NULL, dwCoInit);
        if (SUCCEEDED(hr_)) {
            initialized_ = true;
        } else if (hr_ == RPC_E_CHANGED_MODE) {
            // 已经以不同模式初始化,记录但继续
            initialized_ = true;
        }
    }
    
    ~COMInitializer() {
        if (initialized_ && hr_ != RPC_E_CHANGED_MODE) {
            CoUninitialize();
        }
    }
    
    bool is_initialized() const { return initialized_; }
    HRESULT get_hr() const { return hr_; }
};

// 使用示例
bool initialize_excel_environment() {
    COMInitializer com_init(COINIT_MULTITHREADED);
    
    if (!com_init.is_initialized()) {
        if (com_init.get_hr() == RPC_E_CHANGED_MODE) {
            // 尝试适应现有模式
            return try_alternative_initialization();
        }
        return false;
    }
    
    return true;
}

2.2 Excel实例创建异常

异常现象:

  • CreateInstance返回REGDB_E_CLASSNOTREG
  • _com_error异常,错误码为0x80040154

根本原因:

  • Excel未安装或安装损坏
  • 版本特定的ProgID错误
  • 注册表信息损坏

解决方案:

cpp 复制代码
#include <vector>
#include <comdef.h>

Excel::_ApplicationPtr create_excel_instance() {
    std::vector<const char*> excel_versions = {
        "Excel.Application",      // 最新版本
        "Excel.Application.16",   // Office 2016+
        "Excel.Application.15",   // Office 2013
        "Excel.Application.14",   // Office 2010
        "Excel.Application.12"    // Office 2007
    };
    
    for (const auto* prog_id : excel_versions) {
        try {
            Excel::_ApplicationPtr excel;
            HRESULT hr = excel.CreateInstance(prog_id);
            
            if (SUCCEEDED(hr)) {
                std::cout << "成功创建Excel实例: " << prog_id << std::endl;
                return excel;
            }
        } catch (const _com_error& e) {
            std::cerr << "ProgID " << prog_id << " 失败: " 
                      << static_cast<const char*>(e.Description()) << std::endl;
            continue;
        }
    }
    
    // 尝试通过CLSID创建
    try {
        Excel::_ApplicationPtr excel;
        CLSID clsid;
        HRESULT hr = CLSIDFromProgID(L"Excel.Application", &clsid);
        
        if (SUCCEEDED(hr)) {
            hr = excel.CreateInstance(clsid);
            if (SUCCEEDED(hr)) return excel;
        }
    } catch (...) {
        // 忽略异常,继续后续处理
    }
    
    throw std::runtime_error("无法创建Excel实例,请检查Excel安装");
}

第三章:文件操作异常深度解析

3.1 文件路径与访问异常

常见错误码与场景:

  • 0x800A03EC: 文件不存在或路径无效
  • 0x800A0C6C: 文件被其他进程占用
  • 0x800A0CB0: 文件格式不支持或损坏
  • 0x80070005: 权限不足

综合解决方案:

cpp 复制代码
class ExcelFileManager {
private:
    Excel::_ApplicationPtr excel_;
    
    bool validate_file_path(const std::string& filepath) {
        DWORD attributes = GetFileAttributesA(filepath.c_str());
        if (attributes == INVALID_FILE_ATTRIBUTES) {
            return false; // 文件不存在
        }
        
        if (attributes & FILE_ATTRIBUTE_DIRECTORY) {
            return false; // 路径是目录
        }
        
        // 检查文件是否可读
        HANDLE hFile = CreateFileA(filepath.c_str(), GENERIC_READ, 
                                  FILE_SHARE_READ, NULL, OPEN_EXISTING, 
                                  FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFile == INVALID_HANDLE_VALUE) {
            return false;
        }
        CloseHandle(hFile);
        
        return true;
    }
    
public:
    Excel::_WorkbookPtr open_workbook(const std::string& filepath, 
                                     bool read_only = false) {
        if (!validate_file_path(filepath)) {
            throw std::runtime_error("文件路径无效或不可访问: " + filepath);
        }
        
        try {
            Excel::WorkbooksPtr workbooks = excel_->Workbooks;
            Excel::_WorkbookPtr workbook;
            
            if (read_only) {
                workbook = workbooks->Open(_bstr_t(filepath.c_str()),
                                         vtMissing, true,  // ReadOnly
                                         vtMissing, vtMissing, vtMissing,
                                         vtMissing, vtMissing, vtMissing,
                                         vtMissing, vtMissing, vtMissing,
                                         vtMissing, vtMissing);
            } else {
                workbook = workbooks->Open(_bstr_t(filepath.c_str()));
            }
            
            return workbook;
            
        } catch (const _com_error& e) {
            switch (e.Error()) {
                case 0x800A03EC: // 文件不存在
                    throw std::runtime_error("文件不存在: " + filepath);
                    
                case 0x800A0C6C: // 文件被占用
                    throw std::runtime_error("文件被其他进程占用: " + filepath);
                    
                case 0x800A0CB0: // 格式不支持
                    throw std::runtime_error("文件格式不支持: " + filepath);
                    
                default:
                    throw; // 重新抛出未知异常
            }
        }
    }
};

第四章:对象模型操作异常

4.1 对象引用无效异常

典型场景:

  • 访问已释放的COM对象
  • Worksheet/Workbook关闭后仍尝试访问
  • 集合索引越界

智能指针管理方案:

cpp 复制代码
template<typename T>
class ComSmartPtr {
private:
    T* ptr_;
    
public:
    ComSmartPtr() : ptr_(nullptr) {}
    
    ComSmartPtr(T* p) : ptr_(p) {
        if (ptr_) ptr_->AddRef();
    }
    
    ~ComSmartPtr() { release(); }
    
    // 禁止拷贝
    ComSmartPtr(const ComSmartPtr&) = delete;
    ComSmartPtr& operator=(const ComSmartPtr&) = delete;
    
    // 允许移动
    ComSmartPtr(ComSmartPtr&& other) noexcept : ptr_(other.ptr_) {
        other.ptr_ = nullptr;
    }
    
    ComSmartPtr& operator=(ComSmartPtr&& other) noexcept {
        if (this != &other) {
            release();
            ptr_ = other.ptr_;
            other.ptr_ = nullptr;
        }
        return *this;
    }
    
    T** operator&() { return &ptr_; }
    T* operator->() const { return ptr_; }
    operator bool() const { return ptr_ != nullptr; }
    
    void release() {
        if (ptr_) {
            ptr_->Release();
            ptr_ = nullptr;
        }
    }
};

class SafeExcelRange {
private:
    ComSmartPtr<Excel::Range> range_;
    bool valid_;
    
public:
    SafeExcelRange(Excel::Range* range) : range_(range), valid_(range != nullptr) {}
    
    _variant_t get_value() {
        if (!valid_) {
            throw std::runtime_error("Range对象无效");
        }
        
        try {
            return range_->Value;
        } catch (const _com_error& e) {
            if (e.Error() == 0x800A03EC) { // 引用无效
                valid_ = false;
                throw std::runtime_error("Range引用已失效");
            }
            throw;
        }
    }
    
    bool is_valid() const { return valid_; }
};

4.2 集合操作异常处理

代码示例:

cpp 复制代码
class ExcelCollectionHelper {
public:
    template<typename CollectionT, typename ItemT>
    static ItemT get_safe_item(CollectionT* collection, long index) {
        if (!collection) {
            throw std::invalid_argument("集合对象为空");
        }
        
        try {
            long count = collection->Count;
            if (index < 1 || index > count) {
                throw std::out_of_range("集合索引越界: " + std::to_string(index));
            }
            
            return collection->Item[index];
            
        } catch (const _com_error& e) {
            if (e.Error() == 0x800A03EC) { // Item方法失败
                throw std::runtime_error("无法访问集合项,可能已被删除");
            }
            throw;
        }
    }
    
    template<typename CollectionT, typename ItemT>
    static ItemT find_item_by_name(CollectionT* collection, const std::string& name) {
        try {
            return collection->Item[_variant_t(name.c_str())];
        } catch (const _com_error& e) {
            if (e.Error() == 0x8002000B) { // DISP_E_BADINDEX
                throw std::runtime_error("未找到指定名称的项: " + name);
            }
            throw;
        }
    }
};

// 使用示例
void access_worksheet_safely(Excel::_WorkbookPtr workbook) {
    try {
        Excel::SheetsPtr sheets = workbook->Worksheets;
        Excel::_WorksheetPtr sheet = ExcelCollectionHelper::get_safe_item<
            Excel::Sheets, Excel::_WorksheetPtr>(sheets, 1);
        
        // 安全访问单元格
        Excel::RangePtr range = sheet->Range["A1"];
        _variant_t value = range->Value;
        
    } catch (const std::exception& e) {
        std::cerr << "工作表访问错误: " << e.what() << std::endl;
    }
}

第五章:数据操作异常处理

5.1 数据类型转换异常

常见问题:

  • 数值与文本类型不匹配
  • 日期格式解析错误
  • 数组维度不匹配

安全数据类型转换:

cpp 复制代码
class ExcelDataConverter {
public:
    static std::string variant_to_string(const _variant_t& var) {
        try {
            if (var.vt == VT_BSTR) {
                return static_cast<const char*>(_bstr_t(var.bstrVal));
            } else if (var.vt == VT_R8) {
                return std::to_string(var.dblVal);
            } else if (var.vt == VT_I4) {
                return std::to_string(var.lVal);
            } else if (var.vt == VT_DATE) {
                return format_date(var.date);
            } else if (var.vt == VT_EMPTY) {
                return "";
            } else {
                throw std::runtime_error("不支持的数据类型转换");
            }
        } catch (const _com_error& e) {
            throw std::runtime_error("VARIANT转换失败: " + 
                std::string(static_cast<const char*>(e.Description())));
        }
    }
    
    static _variant_t string_to_variant(const std::string& str) {
        try {
            return _variant_t(str.c_str());
        } catch (const _com_error& e) {
            throw std::runtime_error("字符串转换失败: " + 
                std::string(static_cast<const char*>(e.Description())));
        }
    }
    
    static double variant_to_double(const _variant_t& var) {
        try {
            if (var.vt == VT_R8) {
                return var.dblVal;
            } else if (var.vt == VT_BSTR) {
                return std::stod(static_cast<const char*>(_bstr_t(var.bstrVal)));
            } else if (var.vt == VT_I4) {
                return static_cast<double>(var.lVal);
            } else {
                throw std::runtime_error("无法转换为数值类型");
            }
        } catch (const std::exception& e) {
            throw std::runtime_error("数值转换失败: " + std::string(e.what()));
        }
    }
};

5.2 公式计算与数组操作异常

cpp 复制代码
class ExcelFormulaProcessor {
private:
    Excel::_ApplicationPtr excel_;
    
public:
    _variant_t calculate_formula_safely(Excel::RangePtr range, 
                                        const std::string& formula) {
        if (!range) {
            throw std::invalid_argument("Range对象无效");
        }
        
        try {
            // 设置公式
            range->Formula = _variant_t(formula.c_str());
            
            // 等待计算完成
            excel_->Calculate();
            
            // 获取计算结果
            _variant_t result = range->Value;
            
            // 检查计算错误
            if (is_error_value(result)) {
                handle_formula_error(result, formula);
            }
            
            return result;
            
        } catch (const _com_error& e) {
            if (e.Error() == 0x800A03EC) { // 公式语法错误
                throw std::runtime_error("公式语法错误: " + formula);
            }
            throw;
        }
    }
    
private:
    bool is_error_value(const _variant_t& value) {
        if (value.vt == VT_BSTR) {
            std::string str = static_cast<const char*>(_bstr_t(value.bstrVal));
            return str.find("#") == 0; // Excel错误值以#开头
        }
        return false;
    }
    
    void handle_formula_error(const _variant_t& error_value, 
                             const std::string& formula) {
        std::string error_str = static_cast<const char*>(_bstr_t(error_value.bstrVal));
        
        if (error_str == "#VALUE!") {
            throw std::runtime_error("公式值错误: " + formula);
        } else if (error_str == "#REF!") {
            throw std::runtime_error("公式引用无效: " + formula);
        } else if (error_str == "#DIV/0!") {
            throw std::runtime_error("除零错误: " + formula);
        }
        // 其他错误处理...
    }
};

第六章:运行时环境异常处理

6.1 Excel进程无响应检测与恢复

cpp 复制代码
class ExcelProcessMonitor {
private:
    DWORD excel_pid_;
    HANDLE excel_handle_;
    
public:
    ExcelProcessMonitor(Excel::_ApplicationPtr excel) {
        // 获取Excel进程ID
        excel->get_Hwnd(&excel_pid_); // 注意:这里获取的是窗口句柄,需要转换
        
        // 通过窗口句柄获取进程ID
        DWORD pid;
        GetWindowThreadProcessId((HWND)excel_pid_, &pid);
        excel_pid_ = pid;
        
        excel_handle_ = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, excel_pid_);
    }
    
    ~ExcelProcessMonitor() {
        if (excel_handle_) {
            CloseHandle(excel_handle_);
        }
    }
    
    bool is_excel_responsive() {
        if (!excel_handle_) return false;
        
        DWORD exit_code;
        if (GetExitCodeProcess(excel_handle_, &exit_code)) {
            return exit_code == STILL_ACTIVE;
        }
        
        return false;
    }
    
    bool wait_for_excel_response(int timeout_ms = 5000) {
        auto start_time = std::chrono::steady_clock::now();
        
        while (std::chrono::duration_cast<std::chrono::milliseconds>(
               std::chrono::steady_clock::now() - start_time).count() < timeout_ms) {
            
            if (is_excel_responsive()) {
                return true;
            }
            
            Sleep(100); // 等待100ms再次检查
        }
        
        return false;
    }
};

class ResilientExcelOperation {
private:
    Excel::_ApplicationPtr excel_;
    ExcelProcessMonitor monitor_;
    int max_retries_;
    
public:
    ResilientExcelOperation(Excel::_ApplicationPtr excel, int max_retries = 3)
        : excel_(excel), monitor_(excel), max_retries_(max_retries) {}
    
    template<typename Func>
    auto execute_with_retry(Func&& operation) {
        int retry_count = 0;
        
        while (retry_count < max_retries_) {
            try {
                if (!monitor_.is_excel_responsive()) {
                    throw std::runtime_error("Excel进程无响应");
                }
                
                return operation();
                
            } catch (const _com_error& e) {
                if (e.Error() == 0x80010108 || // RPC_E_DISCONNECTED
                    e.Error() == 0x80010001) {  // RPC_E_CALL_REJECTED
                    
                    retry_count++;
                    if (retry_count >= max_retries_) {
                        throw std::runtime_error("Excel操作重试次数超限");
                    }
                    
                    // 等待后重试
                    if (!monitor_.wait_for_excel_response()) {
                        throw std::runtime_error("Excel进程恢复超时");
                    }
                    
                    continue;
                }
                throw; // 非连接异常,直接抛出
            }
        }
        
        throw std::runtime_error("超出最大重试次数");
    }
};

第七章:综合异常处理框架

7.1 完整的异常处理策略

cpp 复制代码
class ExcelOperationException : public std::exception {
private:
    std::string message_;
    HRESULT hresult_;
    std::string context_;
    std::chrono::system_clock::time_point timestamp_;
    
public:
    ExcelOperationException(const std::string& msg, HRESULT hr = S_OK, 
                          const std::string& ctx = "")
        : message_(msg), hresult_(hr), context_(ctx), 
          timestamp_(std::chrono::system_clock::now()) {}
    
    const char* what() const noexcept override {
        return message_.c_str();
    }
    
    HRESULT get_hresult() const { return hresult_; }
    std::string get_context() const { return context_; }
    auto get_timestamp() const { return timestamp_; }
};

class ExcelOperationGuard {
private:
    Excel::_ApplicationPtr excel_;
    std::string operation_name_;
    
public:
    ExcelOperationGuard(Excel::_ApplicationPtr excel, const std::string& op_name)
        : excel_(excel), operation_name_(op_name) {
        
        // 记录操作开始
        log_operation_start();
    }
    
    ~ExcelOperationGuard() {
        // 记录操作结束
        log_operation_end();
    }
    
    template<typename Func>
    auto execute(Func&& operation) {
        try {
            ResilientExcelOperation resilient_op(excel_);
            return resilient_op.execute_with_retry( {
                return operation();
            });
            
        } catch (const _com_error& e) {
            throw ExcelOperationException(
                "COM异常: " + std::string(static_cast<const char*>(e.Description())),
                e.Error(), operation_name_);
                
        } catch (const std::exception& e) {
            throw ExcelOperationException(
                "标准异常: " + std::string(e.what()), S_OK, operation_name_);
                
        } catch (...) {
            throw ExcelOperationException("未知异常", S_OK, operation_name_);
        }
    }
    
private:
    void log_operation_start() {
        std::cout << "开始Excel操作: " << operation_name_ << std::endl;
    }
    
    void log_operation_end() {
        std::cout << "结束Excel操作: " << operation_name_ << std::endl;
    }
};

// 使用示例
void safe_excel_data_processing() {
    COMInitializer com_init;
    if (!com_init.is_initialized()) {
        throw std::runtime_error("COM初始化失败");
    }
    
    try {
        auto excel = create_excel_instance();
        ExcelOperationGuard guard(excel, "数据处理");
        
        auto result = guard.execute( {
            // 执行Excel操作
            auto workbook = open_excel_workbook(excel, "data.xlsx");
            return process_workbook_data(workbook);
        });
        
        std::cout << "操作成功完成" << std::endl;
        
    } catch (const ExcelOperationException& e) {
        std::cerr << "Excel操作失败: " << e.what() 
                  << " [HRESULT: 0x" << std::hex << e.get_hresult() << "]" 
                  << " 上下文: " << e.get_context() << std::endl;
        
        // 根据异常类型采取恢复措施
        handle_excel_operation_failure(e);
    }
}

第八章:异常场景对照与解决方案速查表

8.1 综合异常处理参考表

异常类别 典型错误码 症状表现 根本原因 解决方案
初始化异常 0x80040154 创建实例失败 Excel未安装/注册表错误 多版本ProgID尝试、CLSID创建
RPC_E_CHANGED_MODE CoInitialize失败 线程模型冲突 统一线程模型、适应现有模式
文件操作异常 0x800A03EC 文件不存在 路径错误/权限不足 路径验证、权限检查
0x800A0C6C 文件被占用 其他进程占用 只读模式、文件重试机制
0x800A0CB0 格式不支持 文件损坏/版本不兼容 格式检测、备份恢复
对象模型异常 0x800A03EC 引用无效 对象已释放/索引越界 智能指针、引用验证
0x8002000B 集合项不存在 名称错误/索引超限 范围检查、异常处理
数据操作异常 0x800A03EC 公式错误 语法错误/引用无效 公式验证、逐步计算
类型不匹配 转换失败 数据类型冲突 安全转换、类型检查
运行时异常 0x80010108 RPC断开 Excel无响应/崩溃 进程监控、超时重试
0x80010001 调用被拒绝 资源竞争/忙状态 异步操作、资源调度
权限安全异常 0x80070005 访问被拒 UAC限制/安全设置 权限提升、信任设置

8.2 最佳实践总结

  1. 防御性编程:始终假设Excel操作可能失败,添加充分的错误检查
  2. 资源管理:使用RAII模式管理COM对象,避免资源泄漏
  3. 异常分层:区分可恢复异常和不可恢复异常,采取不同策略
  4. 重试机制:对于临时性故障(如Excel忙),实现智能重试
  5. 详细日志:记录完整的异常上下文,便于问题诊断
  6. 优雅降级:在主功能失败时提供替代方案或友好提示

通过本文的详细分析和代码示例,开发者可以建立起完整的Excel OLE异常处理体系,显著提高程序的健壮性和用户体验。在实际项目中,建议根据具体需求选择合适的异常处理策略,并不断完善异常恢复机制。

上一篇:详细介绍C++中捕获异常类型的方式有哪些,分别用于哪些情形,哪些异常捕获可用于通过OLE操作excel异常


不积跬步,无以至千里。


代码铸就星河,探索永无止境

在这片由逻辑与算法编织的星辰大海中,每一次报错都是宇宙抛来的谜题,每一次调试都是与未知的深度对话。不要因短暂的"运行失败"而止步,因为真正的光芒,往往诞生于反复试错的暗夜。

请铭记

  • 你写下的每一行代码,都在为思维锻造韧性;
  • 你破解的每一个Bug,都在为认知推开新的门扉;
  • 你坚持的每一分钟,都在为未来的飞跃积蓄势能。

技术的疆域没有终点,只有不断刷新的起点。无论是递归般的层层挑战,还是如异步并发的复杂困局,你终将以耐心为栈、以好奇心为指针,遍历所有可能。

向前吧,开发者

让代码成为你攀登的绳索,让逻辑化作照亮迷雾的灯塔。当你在终端看到"Success"的瞬间,便是宇宙对你坚定信念的回响------
此刻的成就,永远只是下一个奇迹的序章! 🚀


(将技术挑战比作宇宙探索,用代码、算法等意象强化身份认同,传递"持续突破"的信念,结尾以动态符号激发行动力。)

cpp 复制代码
//c++ hello world示例
#include <iostream>  // 引入输入输出流库

int main() {
    std::cout << "Hello World!" << std::endl;  // 输出字符串并换行
    return 0;  // 程序正常退出
}

print("Hello World!")  # 调用内置函数输出字符串

package main  // 声明主包
py 复制代码
#python hello world示例
import "fmt"  // 导入格式化I/O库
go 复制代码
//go hello world示例
func main() {
    fmt.Println("Hello World!")  // 输出并换行
}
C# 复制代码
//c# hello world示例
using System;  // 引入System命名空间

class Program {
    static void Main() {
        Console.WriteLine("Hello World!");  // 输出并换行
        Console.ReadKey();  // 等待按键(防止控制台闪退)
    }
}
相关推荐
Yupureki4 小时前
从零开始的C++学习生活 8:list的入门使用
c语言·c++·学习·visual studio
jc06204 小时前
4.4-中间件之gRPC
c++·中间件·rpc
Wu_hello_mi4 小时前
Excel使用教程笔记
笔记·excel
恶猫4 小时前
Polaris Officev9.9.12全功能解锁版
pdf·word·excel·ppt·office·办公·打工
十五年专注C++开发4 小时前
C++类型转换通用接口设计实现
开发语言·c++·跨平台·类设计
胡萝卜3.04 小时前
掌握string类:从基础到实战
c++·学习·string·string的使用
江公望4 小时前
通过QQmlExtensionPlugin进行Qt QML插件开发
c++·qt·qml
Syntech_Wuz5 小时前
从 C 到 C++:容器适配器 std::stack 与 std::queue 详解
数据结构·c++·容器··队列
杂货铺的小掌柜5 小时前
apache poi excel 字体数量限制
java·excel·poi