如何解决error C2664: “atof”: 不能将参数 1 从“std::string”转换为“const char *”

本文介绍C++中如何使用c_str()函数将string对象转换为C风格字符串,并展示了具体的实现方法与注意事项。

c_str

  c_str函数的返回值是const char*的,不能直接赋值给char*,所以就需要我们进行相应的操作转化,下面就是这一转化过程。
  c++语言提供了两种字符串实现,其中较原始的一种只是字符串的 c语言实现。与C语言的其他部分一样,它在c++的所有实现中可用,我们将这种实现提供的字符串对象,归为c-串,每个c-串char*类型的。
  标准头文件<cstring>包含操作c-串的函数库。这些 库函数表达了我们希望使用的几乎每种字符串操作。 当调用 库函数,客户程序提供的是 string类型参数,而库函数内部实现用的是c-串,因此需要将string对象,转化为char*对象,而c_str()提供了这样一种方法,它返回const char*类型(可读不可改)的指向字符 数组指针。 例:
  #include <iostream>
  #include <string>
  using namespace std;
  int main()
  {
  string add_to = "hello!";
  const string add_on = "baby";
  const char *cfirst = add_to.c_str();
  const char *csecond = add_on.c_str();
  char *copy = new char[strlen(cfirst) + strlen(csecond) + 1];
  strcpy(copy, cfirst);
  strcat(copy, csecond);
  add_to = copy;
  cout << "copy: " << copy << endl;
  delete [] copy;
  cout << "add_to: " << add_to << endl;
  //我强烈建议,以及我的老师也强烈建议我
  //一定要用return 0; 来告诉系统:程序正常结束.
  //return -1;是用来告诉系统:程序异常结束
  //亲们,做开发要认真~共勉~
  //一定要用 int main()
  return 0;
  }
  //以上程序过编译
  //VC 6.0, win7, - -!兼容性差
  
result

  result

——————
  简单的例子:
  函数声明:const char *c_str();
  c_str()函数返回一个指向正规C字符串的 指针, 内容与本string串相同.
  这是为了与 c语言兼容,在c语言中没有 string类型,故必须通过string类对象的成员函数c_str()把string 对象转换成c中的字符串样式。
  注意:一定要使用strcpy()函数 等来操作方法c_str()返回的 指针
  比如:最好不要这样:
  char* c;
  string s="1234";
  c = s.c_str();
  //c最后指向的内容是垃圾,因为s对象被析构,其内容被处理(纠正:s对象的析构是在对 指针c完成赋值操作之后进行的,故此处并没有错误)
  在vc++2010中提示的错误原因:
  
vc++2010中提示的错误原因

  vc++2010中提示的错误原因

应该这样用:
  char c[20];
  string s="1234";
  strcpy(c,s.c_str());
  这样才不会出错,c_str()返回的是一个临时 指针,不能对其进行操作
  ———再举个例子———
  c_str() 以const char* 类型返回 string 内含的字符串
  如果一个函数要求char*参数,可以使用c_str()方法:
  string s = "Hello World!";
  printf("%s", s.c_str()); //输出 "Hello World!"
  ——————
  c_str在打开文件时的用处:
  当需要打开一个由用户自己输入文件名的文件时,可以这样写:ifstream in(st.c_str());。其中st是 string类型,存放的即为用户输入的文件名。
void MaintenanceNMCPMModule::TempLinearParaInput::execute() //add by cmh { ControlObject *owner = getOwner(); if (owner != NULL) { try { (static_cast<MaintenanceNMCPMModule*>(owner))->do_TempLinearParaInput(Params["InnerA"].Str, Params["InnerB"].Str); } catch(const AbortException &ex) { SysLogger::getInstance()->logMsg(LOGBRANCH::APP, LEVEL::ERROR, getSelfName() + ": TempLinearParaInput service was aborted for:" + string(ex.what())); throw; } catch(const exception &ex) { SysLogger::getInstance()->logMsg(LOGBRANCH::APP, LEVEL::ERROR, getSelfName() + ": TempLinearParaInput service has error for:" + string(ex.what())); throw; } } } //add by cmh void MaintenanceNMCPMModule::do_TempLinearParaInput(const std::string& InnerA, const std::string& InnerB) { if (!isStatusOK("perform TempLinearParaInput service")) { return; } std::istringstream issA(InnerA); std::vector<double> values_InnerA; std::string token; std::istringstream issB(InnerB); std::vector<double> values_InnerB; while (std::getline(issA, token, ',')) { values_InnerA.push_back(std::stod(token)); } while (std::getline(issB, token, ',')) { values_InnerB.push_back(std::stod(token)); } for (int i = 0; i < 10; ++i) { std::string InnerAPath = "/SETUP/ESCTemp/cfgInner_a" + std::to_string(i + 1); std::string InnerBPath = "/SETUP/ESCTemp/cfgInner_b" + std::to_string(i + 1); dynamic_cast<ReadWriteDouble*>(ObjectManager::getInstance()->getObject(InnerAPath))->setValue(values_InnerA[i]); dynamic_cast<ReadWriteDouble*>(ObjectManager::getInstance()->getObject(InnerBPath))->setValue(values_InnerB[i]); } } to_string std::stod 我无法用这个 我的gcc版本太低 如何替换这个方案
08-28
// main.cpp - 结构化数据对比工具(Qt4 兼容版 + 线程池支持) // 本程序用于比较两组结构化文本数据(如 CSV 格式)中的对象差异 // 支持 Person 和 Product 两种类型,并使用 QThreadPool 避免界面阻塞 #include <QApplication> // QApplication:管理整个应用程序的核心,包括事件循环、窗口系统等 #include <QWidget> // QWidget:所有 GUI 控件的基类,主窗口继承自它 #include <QVBoxLayout> // QVBoxLayout:垂直布局容器,自动将子控件从上到下排列 #include <QHBoxLayout> // QHBoxLayout:水平布局容器,自动将子控件从左到右排列 #include <QLabel> // QLabel:显示不可编辑的文字标签,例如说明文字 #include <QTextEdit> // QTextEdit:多行文本输入/输出框,可用于显示或输入大段文本 #include <QPushButton> // QPushButton:可点击的按钮控件,用户通过点击触发操作 #include <QComboBox> // QComboBox:下拉选择框,允许用户在多个选项中选择一个 #include <QSplitter> // QSplitter:分割窗口区域,用户可以用鼠标拖动调整左右/上下大小 #include <QMessageBox> // QMessageBox:弹出标准对话框(信息提示、警告、错误等) #include <QFileDialog> // QFileDialog:打开或保存文件的标准图形界面对话框 #include <QFile> // QFile:对本地文件进行读写操作的基础类 #include <QTextStream> // QTextStream:以流的方式读写文本内容,支持设置编码格式 #include <QDateTime> // QDateTime:获取当前时间并格式化为字符串(如 yyyy-MM-dd HH:mm:ss) #include <QClipboard> // QClipboard:访问系统剪贴板,实现复制粘贴功能 #include <QThread> // QThread:提供线程相关功能,用于调试输出当前线程 ID #include <QDebug> // qDebug():打印调试信息到控制台,比 std::cout 更安全跨平台 // 必须包含 QTextCodec 来处理中文字符编码(尤其在 Qt4 中) #include <QTextCodec> // 多线程相关头文件 #include <QThreadPool> // QThreadPool:全局线程池,自动管理和复用工作线程 #include <QRunnable> // QRunnable:表示一个可在子线程中运行的任务接口 #include <QFutureWatcher> // (未直接使用)但常配合 QtConcurrent 使用,监听异步任务状态 #include <QObject> // QObject:信号与槽机制的基础类,也是 invokeMethod 所需 // C++ 标准库头文件 #include <memory> // std::shared_ptr:智能指针,自动管理动态分配的对象生命周期 #include <map> // std::map:基于红黑树的键值对容器,支持按 key 快速查找 #include <vector> // std::vector:动态数组,用于存储一组对象指针 #include <string> // std::stringC++ 标准字符串类型,用于保存字段内容 #include <sstream> // std::stringstream:字符串流,用于将一行 CSV 拆分为多个字段 #include <cstdlib> // std::atoi, std::atof:C 风格函数,将字符串转为整数或浮点数 #include <cmath> // std::fabs:浮点数绝对值函数,用于判断两个 double 是否“近似相等” // ==================== 抽象数据对象接口 ==================== // 所有具体的数据结构(如 Person、Product)都必须继承此类 class DataObject { public: // 定义别名 Ptr,简化 shared_ptr<DataObject> 的书写 typedef std::shared_ptr<DataObject> Ptr; // 虚析构函数:确保通过基类指针删除派生类对象时能正确调用其析构函数 virtual ~DataObject() {} // 获取该对象的唯一标识键(例如 ID 或 SKU),用于匹配 A 和 B 中的同一条记录 virtual std::string getKey() const = 0; // 将对象转换为易读的字符串表示形式,用于输出显示 virtual std::string toString() const = 0; // 比较当前对象与另一个对象之间的差异,返回修改字段描述 virtual std::string diff(const DataObject &other) const = 0; // 克隆方法:返回当前对象的一个深拷贝副本(实现多态复制) virtual Ptr clone() const = 0; }; // ==================== 示例结构体 1: Person ==================== // 表示一个人的信息:ID、姓名、年龄 struct Person : DataObject { int id; // 唯一编号 std::string name; // 姓名 int age; // 年龄 // 构造函数:从一行 CSV 文本创建 Person 对象 explicit Person(const std::string &line) : id(0), age(0) { std::stringstream ss(line); // 创建字符串流来逐个提取字段 std::string field; // 临时变量保存当前字段内容 if (std::getline(ss, field, ',')) id = atoi(field.c_str()); // 第一个字段是 id,使用 atoi 转成整数 if (std::getline(ss, field, ',')) name = field; // 第二个字段是 name,直接赋值 if (std::getline(ss, field, ',')) age = atoi(field.c_str()); // 第三个字段是 age,转成整数 } // 默认构造函数:初始化为空对象 Person() : id(0), age(0) {} // 实现 getKey() 方法:返回唯一的键 "Person:ID" std::string getKey() const { return "Person:" + std::to_string(id); } // 实现 toString() 方法:生成可读的对象描述 std::string toString() const { return "Person{id=" + std::to_string(id) + ", name='" + name + "'" + ", age=" + std::to_string(age) + "}"; } // 实现 diff() 方法:比较当前对象与另一个 Person 是否有不同字段 std::string diff(const DataObject &other) const { const Person& p = static_cast<const Person&>(other); // 安全类型转换(已知类型) std::string changes; // 存储变化信息 if (name != p.name) { changes += " name: '" + name + "' -> '" + p.name + "';"; } if (age != p.age) { changes += " age: " + std::to_string(age) + " -> " + std::to_string(p.age) + ";"; } // 如果没有任何变化,返回“(无变化)”提示 return changes.empty() ? "(无变化)" : changes; } // 实现 clone() 方法:返回一个新的 Person 副本(深拷贝) Ptr clone() const { return std::make_shared<Person>(*this); // 使用智能指针包装新对象 } }; // ==================== 示例结构体 2: Product ==================== // 表示商品信息:SKU 编号、标题、价格 struct Product : DataObject { std::string sku; // 商品唯一编号 std::string title; // 商品名称 double price; // 商品价格(浮点数) // 构造函数:从一行 CSV 文本创建 Product 对象 explicit Product(const std::string &line) : price(0.0) { std::stringstream ss(line); std::string field; if (std::getline(ss, field, ',')) sku = field; // 第一列:SKU if (std::getline(ss, field, ',')) title = field; // 第二列:title if (std::getline(ss, field, ',')) price = atof(field.c_str()); // 第三列:price,使用 atof 转换 } // 默认构造函数 Product() : price(0.0) {} // 返回唯一键:“Product:SKU” std::string getKey() const { return "Product:" + sku; } // 易读格式输出(价格保留两位小数) std::string toString() const { char buf[100]; snprintf(buf, sizeof(buf), "%.2f", price); // 格式化价格 return "Product{sku='" + sku + "', title='" + title + "', price=" + std::string(buf) + "}"; } // 比较两个 Product 是否有字段不同 std::string diff(const DataObject &other) const { const Product& p = static_cast<const Product&>(other); std::string changes; if (title != p.title) { changes += " title: '" + title + "' -> '" + p.title + "';"; } if (fabs(price - p.price) > 1e-5) { // 浮点数不能直接 == 比较,需用误差判断 char oldPrice[20], newPrice[20]; snprintf(oldPrice, sizeof(oldPrice), "%.2f", price); snprintf(newPrice, sizeof(newPrice), "%.2f", p.price); changes += " price: " + std::string(oldPrice) + " -> " + std::string(newPrice) + ";"; } return changes.empty() ? "(无变化)" : changes; } // 返回克隆对象(深拷贝) Ptr clone() const { return std::make_shared<Product>(*this); } }; // ==================== 数据解析器工厂 ==================== // 静态类:根据用户选择的类型将文本解析为对象列表 class DataParser { public: // 定义别名:一组数据对象的列表 typedef std::vector<DataObject::Ptr> ObjectList; // 静态方法:将输入文本按指定类型解析成对象列表 static ObjectList parse(const QString &text, const QString &type) { ObjectList result; // 存放成功解析的对象 QStringList lines = text.split('\n', QString::SkipEmptyParts); // 按换行拆分,跳过空行 // 遍历每一行文本 for (int i = 0; i < lines.size(); ++i) { const QString &line = lines[i]; // 当前行 try { if (type == "Person") { // 解析为 Person 类型并加入结果集 result.push_back(std::make_shared<Person>(line.toStdString())); } else if (type == "Product") { // 解析为 Product 类型 result.push_back(std::make_shared<Product>(line.toStdString())); } else { // 不支持的类型,打印调试日志 qWarning("未知类型: %s", type.toLocal8Bit().data()); } } catch (...) { // 捕获任何异常(比如数字转换失败) // 注意:这里不能弹 QMessageBox(非主线程禁止操作 GUI) // 我们改为记录错误并在主线程提示 qDebug() << "[Worker] 解析失败行:" << line; } } return result; // 返回解析完成的对象列表 } }; // ==================== 异步任务类:继承 QRunnable ==================== // 表示一个异步加载并解析文件的任务 class LoadDataTask : public QRunnable { public: // 枚举标识是哪个输入源(A 左侧 / B 右侧) enum Source { SourceA, SourceB }; // 构造函数:传入文件路径、数据类型、来源(A 或 B)、接收结果的对象 LoadDataTask(const QString &filePath, const QString &dataType, Source src, QObject *receiver) : m_filePath(filePath), m_dataType(dataType), m_source(src), m_receiver(receiver) {} // run() 是 QRunnable 的纯虚函数,会被线程池自动调用,在子线程中执行 void run() override { // 输出调试信息:正在处理哪个文件、运行在线程几 qDebug() << "正在子线程中处理文件:" << m_filePath << " | 线程ID:" << QThread::currentThreadId(); // 1. 读取文件内容 QFile file(m_filePath); // 创建文件对象 QString content; // 用于存储读取的全部文本 bool success = false; // 记录是否成功 QString error; // 存储错误信息(如果失败) // 尝试以只读文本模式打开文件 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); // 创建文本流 stream.setCodec(QTextCodec::codecForName("UTF-8")); // 设置编码为 UTF-8(支持中文) content = stream.readAll(); // 一次性读取所有内容 file.close(); // 关闭文件 success = true; // 标记成功 } else { error = "无法打开文件"; // 设置错误消息 } // 2. 如果读取成功,则进行解析(也在子线程) std::vector<DataObject::Ptr> parsedData; if (success) { parsedData = DataParser::parse(content, m_dataType); // 调用解析器解析文本 } // 3. 准备结果对象,封装所有需要传回主线程的数据 AsyncTaskResult result; result.success = success; // 是否成功 result.content = content; // 文件原始内容(用于显示在 QTextEdit) result.parsedData = parsedData; // 解析后的对象列表(用于后续对比) result.source = m_source; // 来源是 A 还是 B result.errorMsg = error; // 错误信息(如果有) result.filePath = m_filePath; // 文件路径(用于错误提示) // 4. 使用元对象系统跨线程发送信号通知主线程 // Qt::QueuedConnection 确保槽函数在目标线程(GUI 主线程)排队执行 QMetaObject::invokeMethod(m_receiver, "onTaskFinished", Qt::QueuedConnection, Q_ARG(AsyncTaskResult, result)); } private: QString m_filePath; // 要加载的文件路径 QString m_dataType; // 当前选择的数据类型("Person" 或 "Product") Source m_source; // 来源标识(A 或 B) QObject *m_receiver; // 接收结果的对象(即主窗口 this) // 内部结构体:封装任务执行结果,便于跨线程传递 struct AsyncTaskResult { bool success; // 成功标志 QString content; // 文件内容(QStringstd::vector<DataObject::Ptr> parsedData; // 解析后的对象列表 Source source; // 来源 A/B QString errorMsg; // 错误信息 QString filePath; // 文件路径 }; // 声明该结构体支持 Qt 元对象系统序列化(让 invokeMethod 能传递它) Q_DECLARE_METATYPE(AsyncTaskResult) }; // 手动注册类型到 Qt 元对象系统(必须放在类外) Q_DECLARE_METATYPE(LoadDataTask::AsyncTaskResult) // ==================== 主窗口类声明 ==================== // 继承自 QWidget,作为主界面窗口 class StructuredDataComparator : public QWidget { Q_OBJECT // 必须添加:启用 Qt 的元对象系统(信号槽、moc 工具需要) public: // 构造函数:parent 表示父窗口,默认为空 explicit StructuredDataComparator(QWidget *parent = 0); private slots: // 槽函数:响应按钮点击事件 void onCompare(); // 开始对比 A 和 B 的数据 void onClear(); // 清空所有输入输出内容 void onCopyResult(); // 复制对比结果到系统剪贴板 void onLoadFileA(); // 加载文件 A 到左侧输入框 void onLoadFileB(); // 加载文件 B 到右侧输入框 void onTaskFinished(const LoadDataTask::AsyncTaskResult &result); // 接收子线程完成的结果 private: // 私有方法:初始化用户界面布局和控件 void setupUI(); // 成员变量:GUI 控件指针 QTextEdit *inputA; // 左侧输入框(旧数据) QTextEdit *inputB; // 右侧输入框(新数据) QTextEdit *outputArea; // 下方输出区域(显示对比结果) QPushButton *compareBtn; // “开始对比”按钮 QPushButton *clearBtn; // “清空”按钮 QPushButton *copyBtn; // “复制结果”按钮 QComboBox *dataTypeCombo; // 下拉框选择数据类型(Person / Product) // 缓存最后一次加载成功的解析数据(用于对比,避免重复解析) std::vector<DataObject::Ptr> m_lastParsedA; std::vector<DataObject::Ptr> m_lastParsedB; }; // ========== 主窗口构造函数实现 ========== StructuredDataComparator::StructuredDataComparator(QWidget *parent) : QWidget(parent) // 调用父类 QWidget 的构造函数 { // 注册自定义类型,使它可以跨线程传递(由 QMetaObject::invokeMethod 使用) qRegisterMetaType<LoadDataTask::AsyncTaskResult>("LoadDataTask::AsyncTaskResult"); setupUI(); // 初始化界面布局和控件 } // ========== setupUI 函数实现:构建整个用户界面 ========== void StructuredDataComparator::setupUI() { setWindowTitle("🧾 结构化数据对比工具(多线程安全)"); // 设置窗口标题 resize(900, 700); // 设置初始窗口大小(宽900px,高700px) QVBoxLayout *mainLayout = new QVBoxLayout(this); // 创建主垂直布局,作为顶层容器 // --- 第一部分:顶部控件 —— 选择数据类型 --- QHBoxLayout *topLayout = new QHBoxLayout; // 水平布局放置标签和下拉框 topLayout->addWidget(new QLabel("数据类型:")); // 添加说明文字 dataTypeCombo = new QComboBox; // 创建下拉选择框 dataTypeCombo->addItem("Person"); // 添加选项:Person dataTypeCombo->addItem("Product"); // 添加选项:Product topLayout->addWidget(dataTypeCombo); // 将下拉框加入布局 topLayout->addStretch(); // 右侧留白,使控件靠左排列 mainLayout->addLayout(topLayout); // 将顶部布局加入主布局 // --- 第二部分:左右输入区(使用分割器)--- QSplitter *splitter = new QSplitter(Qt::Horizontal); // 创建水平分割器,左右可拖动 // 左侧容器:旧数据输入 QWidget *containerA = new QWidget; // 包裹左侧控件的容器 QVBoxLayout *layoutA = new QVBoxLayout; // 垂直布局 layoutA->addWidget(new QLabel("来源 A(旧数据)")); // 添加标签说明 inputA = new QTextEdit; // 创建多行文本框 inputA->setPlaceholderText("输入格式:\nid,name,age\n1,Alice,30"); // 占位提示文本 layoutA->addWidget(inputA); // 将文本框加入布局 containerA->setLayout(layoutA); // 容器应用此布局 // 右侧容器:新数据输入 QWidget *containerB = new QWidget; QVBoxLayout *layoutB = new QVBoxLayout; layoutB->addWidget(new QLabel("来源 B(新数据)")); inputB = new QTextEdit; inputB->setPlaceholderText("输入格式:\nid,name,age\n2,Bob,25"); layoutB->addWidget(inputB); containerB->setLayout(layoutB); // 将左右两个容器加入分割器 splitter->addWidget(containerA); splitter->addWidget(containerB); mainLayout->addWidget(splitter); // 将分割器加入主布局 // --- 第三部分:按钮行 --- QHBoxLayout *btnLayout = new QHBoxLayout; // 水平布局放置按钮 QPushButton *loadBtnA = new QPushButton("📁 加载 A"); // 加载文件 A 的按钮 QPushButton *loadBtnB = new QPushButton("📁 加载 B"); // 加载文件 B 的按钮 compareBtn = new QPushButton("🔍 开始对比"); // 开始对比按钮 clearBtn = new QPushButton("🗑️ 清空"); // 清空按钮 copyBtn = new QPushButton("📋 复制结果"); // 复制结果按钮 // 按顺序添加按钮到布局 btnLayout->addWidget(loadBtnA); btnLayout->addWidget(loadBtnB); btnLayout->addSpacing(40); // 插入空白间距,美观分隔 btnLayout->addWidget(compareBtn); btnLayout->addWidget(clearBtn); btnLayout->addWidget(copyBtn); mainLayout->addLayout(btnLayout); // 将按钮行加入主布局 // --- 第四部分:输出结果显示区 --- outputArea = new QTextEdit; // 创建输出文本框 outputArea->setReadOnly(true); // 设置只读,防止用户误改 outputArea->setPlaceholderText("对比结果将显示在这里..."); // 提示语 mainLayout->addWidget(outputArea); // 加入主布局 // === 最后:连接信号与槽(使用 Qt4 宏语法)=== connect(compareBtn, SIGNAL(clicked()), this, SLOT(onCompare())); // 点击“开始对比”执行 onCompare connect(clearBtn, SIGNAL(clicked()), this, SLOT(onClear())); // 点击“清空”执行 onClear connect(copyBtn, SIGNAL(clicked()), this, SLOT(onCopyResult())); // 点击“复制”执行 onCopyResult connect(loadBtnA, SIGNAL(clicked()), this, SLOT(onLoadFileA())); // 点击“加载 A”执行 onLoadFileA connect(loadBtnB, SIGNAL(clicked()), this, SLOT(onLoadFileB())); // 点击“加载 B”执行 onLoadFileB } // ========== 槽函数:执行数据对比逻辑 ========== void StructuredDataComparator::onCompare() { QString type = dataTypeCombo->currentText(); // 获取当前选择的数据类型 QString textA = inputA->toPlainText(); // 获取左侧输入的所有文本 QString textB = inputB->toPlainText(); // 获取右侧输入的所有文本 // 检查是否至少有一个输入为空 if (textA.trimmed().isEmpty() || textB.trimmed().isEmpty()) { outputArea->setText("⚠️ 请确保两个输入框都有数据!"); // 提示用户 return; // 提前退出 } // 使用已缓存的 parsed 数据(由线程任务填充) std::vector<DataObject::Ptr> listA = m_lastParsedA; std::vector<DataObject::Ptr> listB = m_lastParsedB; // 如果没有缓存且文本不为空,则临时同步解析一次(适用于手动粘贴的小数据) if (listA.empty() && !textA.isEmpty()) { listA = DataParser::parse(textA, type); } if (listB.empty() && !textB.isEmpty()) { listB = DataParser::parse(textB, type); } // 创建 map:key -> object,便于 O(log n) 查找 std::map<std::string, DataObject::Ptr> mapA, mapB; for (size_t i = 0; i < listA.size(); ++i) mapA[listA[i]->getKey()] = listA[i]; for (size_t i = 0; i < listB.size(); ++i) mapB[listB[i]->getKey()] = listB[i]; // 定义三个向量分别存储新增、删除、修改的对象描述 std::vector<std::string> added; std::vector<std::string> removed; std::vector<std::string> modified; // 在 A 中但不在 B 中 → 被删除 for (std::map<std::string, DataObject::Ptr>::iterator it = mapA.begin(); it != mapA.end(); ++it) { if (mapB.find(it->first) == mapB.end()) { removed.push_back(it->second->toString()); } } // 在 B 中但不在 A 中 → 新增 for (std::map<std::string, DataObject::Ptr>::iterator it = mapB.begin(); it != mapB.end(); ++it) { if (mapA.find(it->first) == mapA.end()) { added.push_back(it->second->toString()); } } // 同时存在于 A 和 B → 检查是否修改 for (std::map<std::string, DataObject::Ptr>::iterator it = mapB.begin(); it != mapB.end(); ++it) { std::string key = it->first; std::map<std::string, DataObject::Ptr>::iterator oldIt = mapA.find(key); if (oldIt != mapA.end()) { std::string diffStr = it->second->diff(*oldIt->second); if (diffStr != "(无变化)") { modified.push_back("🔹 " + key + "\n 修改: " + diffStr); } } } // 生成最终报告文本 QString result; result += "🧾 结构化数据对比报告\n"; result += "==============================\n"; result += QString("📅 时间: %1\n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")); result += QString("📊 类型: %1\n\n").arg(type); result += QString("🆕 新增 (%1):\n").arg(added.size()); for (size_t i = 0; i < added.size(); ++i) { result += " • " + QString::fromStdString(added[i]) + "\n"; } result += "\n"; result += QString("🗑️ 删除 (%1):\n").arg(removed.size()); for (size_t i = 0; i < removed.size(); ++i) { result += " • " + QString::fromStdString(removed[i]) + "\n"; } result += "\n"; result += QString("✏️ 修改 (%1):\n").arg(modified.size()); for (size_t i = 0; i < modified.size(); ++i) { result += QString::fromStdString(modified[i]) + "\n"; } // 显示在输出框 outputArea->setText(result); } // ========== 清空所有内容 ========== void StructuredDataComparator::onClear() { inputA->clear(); // 清空左侧输入框 inputB->clear(); // 清空右侧输入框 outputArea->clear(); // 清空输出框 m_lastParsedA.clear(); // 清除缓存的解析结果 m_lastParsedB.clear(); } // ========== 复制结果到剪贴板 ========== void StructuredDataComparator::onCopyResult() { // 获取 QApplication 的全局剪贴板对象,并设置当前输出文本 QApplication::clipboard()->setText(outputArea->toPlainText()); // 弹出成功提示对话框 QMessageBox::information(this, "已复制", "结果已复制到剪贴板!"); } // ========== 加载文件 A(启动线程任务)========== void StructuredDataComparator::onLoadFileA() { // 弹出文件选择对话框,过滤 .csv 和 .txt 文件 QString filePath = QFileDialog::getOpenFileName(this, "加载 A 文件", "", "CSV/TXT (*.csv *.txt)"); if (!filePath.isEmpty()) { // 用户未取消选择 // 创建任务:传入文件路径、当前类型、来源 A、接收者(this) LoadDataTask *task = new LoadDataTask(filePath, dataTypeCombo->currentText(), LoadDataTask::SourceA, this); task->setAutoDelete(true); // 任务完成后由线程池自动 delete,避免内存泄漏 // 提交到全局线程池执行(run() 将在后台线程被调用) QThreadPool::globalInstance()->start(task); // 显示加载中提示(用户体验优化) inputA->setPlainText("⏳ 正在加载并解析文件..."); } } // ========== 加载文件 B ========== void StructuredDataComparator::onLoadFileB() { QString filePath = QFileDialog::getOpenFileName(this, "加载 B 文件", "", "CSV/TXT (*.csv *.txt)"); if (!filePath.isEmpty()) { // 创建任务:来源为 B LoadDataTask *task = new LoadDataTask(filePath, dataTypeCombo->currentText(), LoadDataTask::SourceB, this); task->setAutoDelete(true); QThreadPool::globalInstance()->start(task); // 显示加载中提示 inputB->setPlainText("⏳ 正在加载并解析文件..."); } } // ========== 接收子线程完成的结果(主线程执行)========== void StructuredDataComparator::onTaskFinished(const LoadDataTask::AsyncTaskResult &result) { // 此函数在主线程执行,可以安全地更新 UI qDebug() << "[MainThread] 收到任务完成通知,来源:" << (result.source == LoadDataTask::SourceA ? "A" : "B") << " | 线程ID:" << QThread::currentThreadId(); if (result.success) { // 更新对应的输入框文本 if (result.source == LoadDataTask::SourceA) { inputA->setPlainText(result.content); // 显示文件内容 m_lastParsedA = result.parsedData; // 缓存解析结果供对比使用 } else { inputB->setPlainText(result.content); m_lastParsedB = result.parsedData; } // 提示用户加载成功 QMessageBox::information(this, "完成", "文件加载并解析成功!"); } else { // 加载失败则显示错误信息 QString msg = "加载失败:" + result.errorMsg + "\n文件:" + result.filePath; if (result.source == LoadDataTask::SourceA) { inputA->setPlainText(msg); } else { inputB->setPlainText(msg); } // 弹出错误对话框 QMessageBox::critical(this, "错误", msg); } } // ========== MOC 编译支持(必须)========== // Qt 的元对象编译器(moc)需要处理含有 Q_OBJECT 的类 // 此行告诉 moc 处理这个文件中的信号槽 #include "main.moc" // ========== 程序入口点 ========== int main(int argc, char *argv[]) { QApplication app(argc, argv); // 创建 Qt 应用对象,管理事件循环 #if QT_VERSION < 0x050000 // Qt4 推荐:设置默认字符串编码为 UTF-8,以便正确显示中文 QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); #endif StructuredDataComparator tool; // 创建主窗口实例 tool.show(); // 显示窗口 return app.exec(); // 启动事件循环,等待用户交互 } 这段代码在加入了多线程处理之后,可能是将代码进行头文件和源文件分离时出现了问题,请帮我将这段代码按头文件和源文件的方式进行分离,并说明引用关系
09-25
// main.cpp - 结构化数据对比工具(Qt4 兼容版 + 线程池支持) // 本程序用于比较两组结构化文本数据(如 CSV 格式)中的对象差异 // 支持 Person 和 Product 两种类型,并使用 QThreadPool 避免界面阻塞 #include // QApplication:管理整个应用程序的核心,包括事件循环、窗口系统等 #include // QWidget:所有 GUI 控件的基类,主窗口继承自它 #include // QVBoxLayout:垂直布局容器,自动将子控件从上到下排列 #include // QHBoxLayout:水平布局容器,自动将子控件从左到右排列 #include // QLabel:显示不可编辑的文字标签,例如说明文字 #include // QTextEdit:多行文本输入/输出框,可用于显示或输入大段文本 #include // QPushButton:可点击的按钮控件,用户通过点击触发操作 #include // QComboBox:下拉选择框,允许用户在多个选项中选择一个 #include // QSplitter:分割窗口区域,用户可以用鼠标拖动调整左右/上下大小 #include // QMessageBox:弹出标准对话框(信息提示、警告、错误等) #include // QFileDialog:打开或保存文件的标准图形界面对话框 #include // QFile:对本地文件进行读写操作的基础类 #include // QTextStream:以流的方式读写文本内容,支持设置编码格式 #include // QDateTime:获取当前时间并格式化为字符串(如 yyyy-MM-dd HH:mm:ss) #include // QClipboard:访问系统剪贴板,实现复制粘贴功能 #include // QThread:提供线程相关功能,用于调试输出当前线程 ID #include // qDebug():打印调试信息到控制台,比 std::cout 更安全跨平台 // 必须包含 QTextCodec 来处理中文字符编码(尤其在 Qt4 中) #include // 多线程相关头文件 #include // QThreadPool:全局线程池,自动管理和复用工作线程 #include // QRunnable:表示一个可在子线程中运行的任务接口 #include // (未直接使用)但常配合 QtConcurrent 使用,监听异步任务状态 #include // QObject:信号与槽机制的基础类,也是 invokeMethod 所需 // C++ 标准库头文件 #include // std::shared_ptr:智能指针,自动管理动态分配的对象生命周期 #include // std::map:基于红黑树的键值对容器,支持按 key 快速查找 #include // std::vector:动态数组,用于存储一组对象指针 #include // std::stringC++ 标准字符串类型,用于保存字段内容 #include // std::stringstream:字符串流,用于将一行 CSV 拆分为多个字段 #include // std::atoi, std::atof:C 风格函数,将字符串转为整数或浮点数 #include // std::fabs:浮点数绝对值函数,用于判断两个 double 是否“近似相等” // ==================== 抽象数据对象接口 ==================== // 所有具体的数据结构(如 Person、Product)都必须继承此类 class DataObject { public: // 定义别名 Ptr,简化 shared_ptr 的书写 typedef std::shared_ptr Ptr; // 虚析构函数:确保通过基类指针删除派生类对象时能正确调用其析构函数 virtual ~DataObject() {} // 获取该对象的唯一标识键(例如 ID 或 SKU),用于匹配 A 和 B 中的同一条记录 virtual std::string getKey() const = 0; // 将对象转换为易读的字符串表示形式,用于输出显示 virtual std::string toString() const = 0; // 比较当前对象与另一个对象之间的差异,返回修改字段描述 virtual std::string diff(const DataObject &other) const = 0; // 克隆方法:返回当前对象的一个深拷贝副本(实现多态复制) virtual Ptr clone() const = 0; }; // ==================== 示例结构体 1: Person ==================== // 表示一个人的信息:ID、姓名、年龄 struct Person : DataObject { int id; // 唯一编号 std::string name; // 姓名 int age; // 年龄 // 构造函数:从一行 CSV 文本创建 Person 对象 explicit Person(const std::string &line) : id(0), age(0) { std::stringstream ss(line); // 创建字符串流来逐个提取字段 std::string field; // 临时变量保存当前字段内容 if (std::getline(ss, field, ',')) id = atoi(field.c_str()); // 第一个字段是 id,使用 atoi 转成整数 if (std::getline(ss, field, ',')) name = field; // 第二个字段是 name,直接赋值 if (std::getline(ss, field, ',')) age = atoi(field.c_str()); // 第三个字段是 age,转成整数 } // 默认构造函数:初始化为空对象 Person() : id(0), age(0) {} // 实现 getKey() 方法:返回唯一的键 "Person:ID" std::string getKey() const { return "Person:" + std::to_string(id); } // 实现 toString() 方法:生成可读的对象描述 std::string toString() const { return "Person{id=" + std::to_string(id) + ", name='" + name + "'" + ", age=" + std::to_string(age) + "}"; } // 实现 diff() 方法:比较当前对象与另一个 Person 是否有不同字段 std::string diff(const DataObject &other) const { const Person& p = static_cast<const Person&>(other); // 安全类型转换(已知类型) std::string changes; // 存储变化信息 if (name != p.name) { changes += " name: '" + name + "' -> '" + p.name + "';"; } if (age != p.age) { changes += " age: " + std::to_string(age) + " -> " + std::to_string(p.age) + ";"; } // 如果没有任何变化,返回“(无变化)”提示 return changes.empty() ? "(无变化)" : changes; } // 实现 clone() 方法:返回一个新的 Person 副本(深拷贝) Ptr clone() const { return std::make_shared<Person>(*this); // 使用智能指针包装新对象 } }; // ==================== 示例结构体 2: Product ==================== // 表示商品信息:SKU 编号、标题、价格 struct Product : DataObject { std::string sku; // 商品唯一编号 std::string title; // 商品名称 double price; // 商品价格(浮点数) // 构造函数:从一行 CSV 文本创建 Product 对象 explicit Product(const std::string &line) : price(0.0) { std::stringstream ss(line); std::string field; if (std::getline(ss, field, ',')) sku = field; // 第一列:SKU if (std::getline(ss, field, ',')) title = field; // 第二列:title if (std::getline(ss, field, ',')) price = atof(field.c_str()); // 第三列:price,使用 atof 转换 } // 默认构造函数 Product() : price(0.0) {} // 返回唯一键:“Product:SKU” std::string getKey() const { return "Product:" + sku; } // 易读格式输出(价格保留两位小数) std::string toString() const { char buf[100]; snprintf(buf, sizeof(buf), "%.2f", price); // 格式化价格 return "Product{sku='" + sku + "', title='" + title + "', price=" + std::string(buf) + "}"; } // 比较两个 Product 是否有字段不同 std::string diff(const DataObject &other) const { const Product& p = static_cast<const Product&>(other); std::string changes; if (title != p.title) { changes += " title: '" + title + "' -> '" + p.title + "';"; } if (fabs(price - p.price) > 1e-5) { // 浮点数不能直接 == 比较,需用误差判断 char oldPrice[20], newPrice[20]; snprintf(oldPrice, sizeof(oldPrice), "%.2f", price); snprintf(newPrice, sizeof(newPrice), "%.2f", p.price); changes += " price: " + std::string(oldPrice) + " -> " + std::string(newPrice) + ";"; } return changes.empty() ? "(无变化)" : changes; } // 返回克隆对象(深拷贝) Ptr clone() const { return std::make_shared<Product>(*this); } }; // ==================== 数据解析器工厂 ==================== // 静态类:根据用户选择的类型将文本解析为对象列表 class DataParser { public: // 定义别名:一组数据对象的列表 typedef std::vectorDataObject::Ptr ObjectList; // 静态方法:将输入文本按指定类型解析成对象列表 static ObjectList parse(const QString &text, const QString &type) { ObjectList result; // 存放成功解析的对象 QStringList lines = text.split('\n', QString::SkipEmptyParts); // 按换行拆分,跳过空行 // 遍历每一行文本 for (int i = 0; i < lines.size(); ++i) { const QString &line = lines[i]; // 当前行 try { if (type == "Person") { // 解析为 Person 类型并加入结果集 result.push_back(std::make_shared<Person>(line.toStdString())); } else if (type == "Product") { // 解析为 Product 类型 result.push_back(std::make_shared<Product>(line.toStdString())); } else { // 不支持的类型,打印调试日志 qWarning("未知类型: %s", type.toLocal8Bit().data()); } } catch (...) { // 捕获任何异常(比如数字转换失败) // 注意:这里不能弹 QMessageBox(非主线程禁止操作 GUI) // 我们改为记录错误并在主线程提示 qDebug() << "[Worker] 解析失败行:" << line; } } return result; // 返回解析完成的对象列表 } }; // ==================== 异步任务类:继承 QRunnable ==================== // 表示一个异步加载并解析文件的任务 class LoadDataTask : public QRunnable { public: // 枚举标识是哪个输入源(A 左侧 / B 右侧) enum Source { SourceA, SourceB }; // 构造函数:传入文件路径、数据类型、来源(A 或 B)、接收结果的对象 LoadDataTask(const QString &filePath, const QString &dataType, Source src, QObject *receiver) : m_filePath(filePath), m_dataType(dataType), m_source(src), m_receiver(receiver) {} // run() 是 QRunnable 的纯虚函数,会被线程池自动调用,在子线程中执行 void run() override { // 输出调试信息:正在处理哪个文件、运行在线程几 qDebug() << "正在子线程中处理文件:" << m_filePath << " | 线程ID:" << QThread::currentThreadId(); // 1. 读取文件内容 QFile file(m_filePath); // 创建文件对象 QString content; // 用于存储读取的全部文本 bool success = false; // 记录是否成功 QString error; // 存储错误信息(如果失败) // 尝试以只读文本模式打开文件 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream stream(&file); // 创建文本流 stream.setCodec(QTextCodec::codecForName("UTF-8")); // 设置编码为 UTF-8(支持中文) content = stream.readAll(); // 一次性读取所有内容 file.close(); // 关闭文件 success = true; // 标记成功 } else { error = "无法打开文件"; // 设置错误消息 } // 2. 如果读取成功,则进行解析(也在子线程) std::vector<DataObject::Ptr> parsedData; if (success) { parsedData = DataParser::parse(content, m_dataType); // 调用解析器解析文本 } // 3. 准备结果对象,封装所有需要传回主线程的数据 AsyncTaskResult result; result.success = success; // 是否成功 result.content = content; // 文件原始内容(用于显示在 QTextEdit) result.parsedData = parsedData; // 解析后的对象列表(用于后续对比) result.source = m_source; // 来源是 A 还是 B result.errorMsg = error; // 错误信息(如果有) result.filePath = m_filePath; // 文件路径(用于错误提示) // 4. 使用元对象系统跨线程发送信号通知主线程 // Qt::QueuedConnection 确保槽函数在目标线程(GUI 主线程)排队执行 QMetaObject::invokeMethod(m_receiver, "onTaskFinished", Qt::QueuedConnection, Q_ARG(AsyncTaskResult, result)); } private: QString m_filePath; // 要加载的文件路径 QString m_dataType; // 当前选择的数据类型(“Person” 或 “Product”) Source m_source; // 来源标识(A 或 B) QObject *m_receiver; // 接收结果的对象(即主窗口 this) // 内部结构体:封装任务执行结果,便于跨线程传递 struct AsyncTaskResult { bool success; // 成功标志 QString content; // 文件内容(QStringstd::vector<DataObject::Ptr> parsedData; // 解析后的对象列表 Source source; // 来源 A/B QString errorMsg; // 错误信息 QString filePath; // 文件路径 }; // 声明该结构体支持 Qt 元对象系统序列化(让 invokeMethod 能传递它) Q_DECLARE_METATYPE(AsyncTaskResult) }; // 手动注册类型到 Qt 元对象系统(必须放在类外) Q_DECLARE_METATYPE(LoadDataTask::AsyncTaskResult) // ==================== 主窗口类声明 ==================== // 继承自 QWidget,作为主界面窗口 class StructuredDataComparator : public QWidget { Q_OBJECT // 必须添加:启用 Qt 的元对象系统(信号槽、moc 工具需要) public: // 构造函数:parent 表示父窗口,默认为空 explicit StructuredDataComparator(QWidget *parent = 0); private slots: // 槽函数:响应按钮点击事件 void onCompare(); // 开始对比 A 和 B 的数据 void onClear(); // 清空所有输入输出内容 void onCopyResult(); // 复制对比结果到系统剪贴板 void onLoadFileA(); // 加载文件 A 到左侧输入框 void onLoadFileB(); // 加载文件 B 到右侧输入框 void onTaskFinished(const LoadDataTask::AsyncTaskResult &result); // 接收子线程完成的结果 private: // 私有方法:初始化用户界面布局和控件 void setupUI(); // 成员变量:GUI 控件指针 QTextEdit *inputA; // 左侧输入框(旧数据) QTextEdit *inputB; // 右侧输入框(新数据) QTextEdit *outputArea; // 下方输出区域(显示对比结果) QPushButton *compareBtn; // “开始对比”按钮 QPushButton *clearBtn; // “清空”按钮 QPushButton *copyBtn; // “复制结果”按钮 QComboBox *dataTypeCombo; // 下拉框选择数据类型(Person / Product) // 缓存最后一次加载成功的解析数据(用于对比,避免重复解析) std::vector<DataObject::Ptr> m_lastParsedA; std::vector<DataObject::Ptr> m_lastParsedB; }; // ========== 主窗口构造函数实现 ========== StructuredDataComparator::StructuredDataComparator(QWidget *parent) : QWidget(parent) // 调用父类 QWidget 的构造函数 { // 注册自定义类型,使它可以跨线程传递(由 QMetaObject::invokeMethod 使用) qRegisterMetaTypeLoadDataTask::AsyncTaskResult(“LoadDataTask::AsyncTaskResult”); setupUI(); // 初始化界面布局和控件 } // ========== setupUI 函数实现:构建整个用户界面 ========== void StructuredDataComparator::setupUI() { setWindowTitle(“🧾 结构化数据对比工具(多线程安全)”); // 设置窗口标题 resize(900, 700); // 设置初始窗口大小(宽900px,高700px) QVBoxLayout *mainLayout = new QVBoxLayout(this); // 创建主垂直布局,作为顶层容器 // --- 第一部分:顶部控件 —— 选择数据类型 --- QHBoxLayout *topLayout = new QHBoxLayout; // 水平布局放置标签和下拉框 topLayout->addWidget(new QLabel("数据类型:")); // 添加说明文字 dataTypeCombo = new QComboBox; // 创建下拉选择框 dataTypeCombo->addItem("Person"); // 添加选项:Person dataTypeCombo->addItem("Product"); // 添加选项:Product topLayout->addWidget(dataTypeCombo); // 将下拉框加入布局 topLayout->addStretch(); // 右侧留白,使控件靠左排列 mainLayout->addLayout(topLayout); // 将顶部布局加入主布局 // --- 第二部分:左右输入区(使用分割器)--- QSplitter *splitter = new QSplitter(Qt::Horizontal); // 创建水平分割器,左右可拖动 // 左侧容器:旧数据输入 QWidget *containerA = new QWidget; // 包裹左侧控件的容器 QVBoxLayout *layoutA = new QVBoxLayout; // 垂直布局 layoutA->addWidget(new QLabel("来源 A(旧数据)")); // 添加标签说明 inputA = new QTextEdit; // 创建多行文本框 inputA->setPlaceholderText("输入格式:\nid,name,age\n1,Alice,30"); // 占位提示文本 layoutA->addWidget(inputA); // 将文本框加入布局 containerA->setLayout(layoutA); // 容器应用此布局 // 右侧容器:新数据输入 QWidget *containerB = new QWidget; QVBoxLayout *layoutB = new QVBoxLayout; layoutB->addWidget(new QLabel("来源 B(新数据)")); inputB = new QTextEdit; inputB->setPlaceholderText("输入格式:\nid,name,age\n2,Bob,25"); layoutB->addWidget(inputB); containerB->setLayout(layoutB); // 将左右两个容器加入分割器 splitter->addWidget(containerA); splitter->addWidget(containerB); mainLayout->addWidget(splitter); // 将分割器加入主布局 // --- 第三部分:按钮行 --- QHBoxLayout *btnLayout = new QHBoxLayout; // 水平布局放置按钮 QPushButton *loadBtnA = new QPushButton("📁 加载 A"); // 加载文件 A 的按钮 QPushButton *loadBtnB = new QPushButton("📁 加载 B"); // 加载文件 B 的按钮 compareBtn = new QPushButton("🔍 开始对比"); // 开始对比按钮 clearBtn = new QPushButton("🗑️ 清空"); // 清空按钮 copyBtn = new QPushButton("📋 复制结果"); // 复制结果按钮 // 按顺序添加按钮到布局 btnLayout->addWidget(loadBtnA); btnLayout->addWidget(loadBtnB); btnLayout->addSpacing(40); // 插入空白间距,美观分隔 btnLayout->addWidget(compareBtn); btnLayout->addWidget(clearBtn); btnLayout->addWidget(copyBtn); mainLayout->addLayout(btnLayout); // 将按钮行加入主布局 // --- 第四部分:输出结果显示区 --- outputArea = new QTextEdit; // 创建输出文本框 outputArea->setReadOnly(true); // 设置只读,防止用户误改 outputArea->setPlaceholderText("对比结果将显示在这里..."); // 提示语 mainLayout->addWidget(outputArea); // 加入主布局 // === 最后:连接信号与槽(使用 Qt4 宏语法)=== connect(compareBtn, SIGNAL(clicked()), this, SLOT(onCompare())); // 点击“开始对比”执行 onCompare connect(clearBtn, SIGNAL(clicked()), this, SLOT(onClear())); // 点击“清空”执行 onClear connect(copyBtn, SIGNAL(clicked()), this, SLOT(onCopyResult())); // 点击“复制”执行 onCopyResult connect(loadBtnA, SIGNAL(clicked()), this, SLOT(onLoadFileA())); // 点击“加载 A”执行 onLoadFileA connect(loadBtnB, SIGNAL(clicked()), this, SLOT(onLoadFileB())); // 点击“加载 B”执行 onLoadFileB } // ========== 槽函数:执行数据对比逻辑 ========== void StructuredDataComparator::onCompare() { QString type = dataTypeCombo->currentText(); // 获取当前选择的数据类型 QString textA = inputA->toPlainText(); // 获取左侧输入的所有文本 QString textB = inputB->toPlainText(); // 获取右侧输入的所有文本 // 检查是否至少有一个输入为空 if (textA.trimmed().isEmpty() || textB.trimmed().isEmpty()) { outputArea->setText("⚠️ 请确保两个输入框都有数据!"); // 提示用户 return; // 提前退出 } // 使用已缓存的 parsed 数据(由线程任务填充) std::vector<DataObject::Ptr> listA = m_lastParsedA; std::vector<DataObject::Ptr> listB = m_lastParsedB; // 如果没有缓存且文本不为空,则临时同步解析一次(适用于手动粘贴的小数据) if (listA.empty() && !textA.isEmpty()) { listA = DataParser::parse(textA, type); } if (listB.empty() && !textB.isEmpty()) { listB = DataParser::parse(textB, type); } // 创建 map:key -> object,便于 O(log n) 查找 std::map<std::string, DataObject::Ptr> mapA, mapB; for (size_t i = 0; i < listA.size(); ++i) mapA[listA[i]->getKey()] = listA[i]; for (size_t i = 0; i < listB.size(); ++i) mapB[listB[i]->getKey()] = listB[i]; // 定义三个向量分别存储新增、删除、修改的对象描述 std::vector<std::string> added; std::vector<std::string> removed; std::vector<std::string> modified; // 在 A 中但不在 B 中 → 被删除 for (std::map<std::string, DataObject::Ptr>::iterator it = mapA.begin(); it != mapA.end(); ++it) { if (mapB.find(it->first) == mapB.end()) { removed.push_back(it->second->toString()); } } // 在 B 中但不在 A 中 → 新增 for (std::map<std::string, DataObject::Ptr>::iterator it = mapB.begin(); it != mapB.end(); ++it) { if (mapA.find(it->first) == mapA.end()) { added.push_back(it->second->toString()); } } // 同时存在于 A 和 B → 检查是否修改 for (std::map<std::string, DataObject::Ptr>::iterator it = mapB.begin(); it != mapB.end(); ++it) { std::string key = it->first; std::map<std::string, DataObject::Ptr>::iterator oldIt = mapA.find(key); if (oldIt != mapA.end()) { std::string diffStr = it->second->diff(*oldIt->second); if (diffStr != "(无变化)") { modified.push_back("🔹 " + key + "\n 修改: " + diffStr); } } } // 生成最终报告文本 QString result; result += "🧾 结构化数据对比报告\n"; result += "==============================\n"; result += QString("📅 时间: %1\n").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")); result += QString("📊 类型: %1\n\n").arg(type); result += QString("🆕 新增 (%1):\n").arg(added.size()); for (size_t i = 0; i < added.size(); ++i) { result += " • " + QString::fromStdString(added[i]) + "\n"; } result += "\n"; result += QString("🗑️ 删除 (%1):\n").arg(removed.size()); for (size_t i = 0; i < removed.size(); ++i) { result += " • " + QString::fromStdString(removed[i]) + "\n"; } result += "\n"; result += QString("✏️ 修改 (%1):\n").arg(modified.size()); for (size_t i = 0; i < modified.size(); ++i) { result += QString::fromStdString(modified[i]) + "\n"; } // 显示在输出框 outputArea->setText(result); } // ========== 清空所有内容 ========== void StructuredDataComparator::onClear() { inputA->clear(); // 清空左侧输入框 inputB->clear(); // 清空右侧输入框 outputArea->clear(); // 清空输出框 m_lastParsedA.clear(); // 清除缓存的解析结果 m_lastParsedB.clear(); } // ========== 复制结果到剪贴板 ========== void StructuredDataComparator::onCopyResult() { // 获取 QApplication 的全局剪贴板对象,并设置当前输出文本 QApplication::clipboard()->setText(outputArea->toPlainText()); // 弹出成功提示对话框 QMessageBox::information(this, “已复制”, “结果已复制到剪贴板!”); } // ========== 加载文件 A(启动线程任务)========== void StructuredDataComparator::onLoadFileA() { // 弹出文件选择对话框,过滤 .csv 和 .txt 文件 QString filePath = QFileDialog::getOpenFileName(this, “加载 A 文件”, “”, “CSV/TXT (*.csv *.txt)”); if (!filePath.isEmpty()) { // 用户未取消选择 // 创建任务:传入文件路径、当前类型、来源 A、接收者(this) LoadDataTask *task = new LoadDataTask(filePath, dataTypeCombo->currentText(), LoadDataTask::SourceA, this); task->setAutoDelete(true); // 任务完成后由线程池自动 delete,避免内存泄漏 // 提交到全局线程池执行(run() 将在后台线程被调用) QThreadPool::globalInstance()->start(task); // 显示加载中提示(用户体验优化) inputA->setPlainText("⏳ 正在加载并解析文件..."); } } // ========== 加载文件 B ========== void StructuredDataComparator::onLoadFileB() { QString filePath = QFileDialog::getOpenFileName(this, “加载 B 文件”, “”, “CSV/TXT (*.csv *.txt)”); if (!filePath.isEmpty()) { // 创建任务:来源为 B LoadDataTask *task = new LoadDataTask(filePath, dataTypeCombo->currentText(), LoadDataTask::SourceB, this); task->setAutoDelete(true); QThreadPool::globalInstance()->start(task); // 显示加载中提示 inputB->setPlainText("⏳ 正在加载并解析文件..."); } } // ========== 接收子线程完成的结果(主线程执行)========== void StructuredDataComparator::onTaskFinished(const LoadDataTask::AsyncTaskResult &result) { // 此函数在主线程执行,可以安全地更新 UI qDebug() << “[MainThread] 收到任务完成通知,来源:” << (result.source == LoadDataTask::SourceA ? “A” : “B”) << " | 线程ID:" << QThread::currentThreadId(); if (result.success) { // 更新对应的输入框文本 if (result.source == LoadDataTask::SourceA) { inputA->setPlainText(result.content); // 显示文件内容 m_lastParsedA = result.parsedData; // 缓存解析结果供对比使用 } else { inputB->setPlainText(result.content); m_lastParsedB = result.parsedData; } // 提示用户加载成功 QMessageBox::information(this, "完成", "文件加载并解析成功!"); } else { // 加载失败则显示错误信息 QString msg = "加载失败:" + result.errorMsg + "\n文件:" + result.filePath; if (result.source == LoadDataTask::SourceA) { inputA->setPlainText(msg); } else { inputB->setPlainText(msg); } // 弹出错误对话框 QMessageBox::critical(this, "错误", msg); } } // ========== MOC 编译支持(必须)========== // Qt 的元对象编译器(moc)需要处理含有 Q_OBJECT 的类 // 此行告诉 moc 处理这个文件中的信号槽 #include “main.moc” // ========== 程序入口点 ========== int main(int argc, char *argv[]) { QApplication app(argc, argv); // 创建 Qt 应用对象,管理事件循环 #if QT_VERSION < 0x050000 // Qt4 推荐:设置默认字符串编码为 UTF-8,以便正确显示中文 QTextCodec::setCodecForCStrings(QTextCodec::codecForName(“UTF-8”)); #endif StructuredDataComparator tool; // 创建主窗口实例 tool.show(); // 显示窗口 return app.exec(); // 启动事件循环,等待用户交互 } 这段代码在加入了多线程处理之后,可能是将代码进行头文件和源文件分离时出现了问题,请帮我将这段代码按头文件和源文件的方式进行分离,并说明引用关系
最新发布
09-25
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值