使用场景:
串口收到数据,会将所需要的字段写入FieldManager模块中即更新数据。可能多个接收的报文中分散多个不同字段的结果,实现了解耦。
再一个定时进程中从FieldManager中调用数值,并将数值写入log中实现log的记录。
✅ 一、文件结构
YourProject/
├── FieldManager.h ✅ 字段管理器头文件(线程安全版)
├── main.cpp ✅ 主函数
├── mainwindow.h/cpp ✅ 主窗口类
├── mainwindow.ui ✅ 界面设计(按钮 + 文本框)
├── YourProject.pro ✅ Qt 项目文件
✅ 二、1. FieldManager.h
线程安全版本 FieldManager.h
cpp
#ifndef FIELDMANAGER_H
#define FIELDMANAGER_H
#include <string>
#include <unordered_map>
#include <any>
#include <mutex>
#include <stdexcept>
#include <type_traits>
enum class FieldType {
U8, S8, U16, S16, U32, S32, FLOAT, DOUBLE
};
class FieldManager {
public:
static FieldManager& getInstance() {
static FieldManager instance;
return instance;
}
// 注册字段
void ADD(const std::string& tag, FieldType type) {
std::lock_guard<std::mutex> lock(mtx);
if (fields.find(tag) != fields.end()) return;
switch (type) {
case FieldType::U8: fields[tag] = uint8_t(0); break;
case FieldType::S8: fields[tag] = int8_t(0); break;
case FieldType::U16: fields[tag] = uint16_t(0); break;
case FieldType::S16: fields[tag] = int16_t(0); break;
case FieldType::U32: fields[tag] = uint32_t(0); break;
case FieldType::S32: fields[tag] = int32_t(0); break;
case FieldType::FLOAT: fields[tag] = float(0); break;
case FieldType::DOUBLE: fields[tag] = double(0); break;
}
}
// 设置字段值
template<typename T>
void SET(const std::string& tag, FieldType type, T value) {
checkTypeMatch<T>(type);
std::lock_guard<std::mutex> lock(mtx);
if (fields.find(tag) == fields.end()) throw std::runtime_error("标签未注册");
fields[tag] = value;
}
// 获取字段值
template<typename T>
T GET(const std::string& tag, FieldType type) {
checkTypeMatch<T>(type);
std::lock_guard<std::mutex> lock(mtx);
if (fields.find(tag) == fields.end()) throw std::runtime_error("标签未注册");
return std::any_cast<T>(fields[tag]);
}
private:
std::unordered_map<std::string, std::any> fields;
std::mutex mtx; // 互斥锁保护字段读写
FieldManager() {}
FieldManager(const FieldManager&) = delete;
FieldManager& operator=(const FieldManager&) = delete;
// 编译期类型检查
template<typename T>
void checkTypeMatch(FieldType type) {
bool match = false;
switch (type) {
case FieldType::U8: match = std::is_same_v<T, uint8_t>; break;
case FieldType::S8: match = std::is_same_v<T, int8_t>; break;
case FieldType::U16: match = std::is_same_v<T, uint16_t>; break;
case FieldType::S16: match = std::is_same_v<T, int16_t>; break;
case FieldType::U32: match = std::is_same_v<T, uint32_t>; break;
case FieldType::S32: match = std::is_same_v<T, int32_t>; break;
case FieldType::FLOAT: match = std::is_same_v<T, float>; break;
case FieldType::DOUBLE: match = std::is_same_v<T, double>; break;
}
if (!match) throw std::invalid_argument("类型与FieldType不匹配");
}
};
#endif // FIELDMANAGER_H
✅ 示例测试(支持多线程)
cpp
#include "FieldManager.h"
#include <thread>
#include <iostream>
void threadFunc() {
auto& fm = FieldManager::getInstance();
fm.SET<float>("电压", FieldType::FLOAT, 3.14f);
std::cout << "线程中设置电压: " << fm.GET<float>("电压", FieldType::FLOAT) << std::endl;
}
int main() {
auto& fm = FieldManager::getInstance();
fm.ADD("电压", FieldType::FLOAT);
std::thread t1(threadFunc);
std::thread t2(threadFunc);
t1.join();
t2.join();
std::cout << "主线程读取电压: " << fm.GET<float>("电压", FieldType::FLOAT) << std::endl;
return 0;
}
✅ 输出示例
cpp
线程中设置电压: 3.14
线程中设置电压: 3.14
主线程读取电压: 3.14
如何使用:
确保你已经在文件顶部添加:
cpp
#include <any> // ✅ 必需
#include <mutex>
#include <unordered_map>
#include <string>
并确保 Qt 项目启用了 C++17。
CONFIG += c++17
✅ 三、2. mainwindow.ui 设计界面
在 Qt Designer 中拖入以下控件:
控件类型 | ObjectName | 说明 |
---|---|---|
QPushButton | btnSet |
设置字段按钮 |
QPushButton | btnGet |
获取字段按钮 |
QLineEdit | lineEditValue |
输入值 |
QLabel | labelResult |
显示读取结果 |
✅ 四、3. mainwindow.h
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
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_btnSet_clicked();
void on_btnGet_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
✅ 五、4. mainwindow.cpp
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "FieldManager.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this);
// 注册一个字段 "电压",类型为 FLOAT
FieldManager::getInstance().ADD("电压", FieldType::FLOAT);
connect(ui->btnSet, &QPushButton::clicked, this, &MainWindow::on_btnSet_clicked);
connect(ui->btnGet, &QPushButton::clicked, this, &MainWindow::on_btnGet_clicked);
}
MainWindow::~MainWindow() {
delete ui;
}
void MainWindow::on_btnSet_clicked() {
bool ok;
float value = ui->lineEditValue->text().toFloat(&ok);
if (ok) {
FieldManager::getInstance().SET<float>("电压", FieldType::FLOAT, value);
ui->labelResult->setText("设置成功");
} else {
ui->labelResult->setText("请输入有效数字");
}
}
void MainWindow::on_btnGet_clicked() {
try {
float v = FieldManager::getInstance().GET<float>("电压", FieldType::FLOAT);
ui->labelResult->setText(QString("读取值: %1").arg(v));
} catch (std::exception& e) {
ui->labelResult->setText("读取失败: " + QString(e.what()));
}
}
✅ 六、5. main.cpp
cpp
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
✅ 七、6. YourProject.pro(开启 C++17)
pro
QT += core gui widgets
greaterThan(QT_MAJOR_VERSION, 5): QT += widgets
TARGET = YourProject
TEMPLATE = app
CONFIG += c++17
SOURCES += main.cpp \
mainwindow.cpp
HEADERS += mainwindow.h \
FieldManager.h
FORMS += mainwindow.ui
✅ 八、编译运行效果
- 输入一个数字点击【设置】 → 会将其存入
FieldManager
的"电压"
字段。 - 点击【获取】 → 会显示之前设置的值。