【项目二】基于Qt平台的线性代数运算工具

【一】前言

前言

Linear Algebra Terminator 基于去年编写的C语言代码向C++进行迭代,其中迭代了3个版本,不过都是在控制台上完成的,对用户输入不太友好,其中的矩阵对象主要是依据自定义的Fraction(分数)对象实现的,缺乏多样性,本次Linear Algebra Terminator - version 4.0 基于Qt平台开发,保留了原版本指令模式输入,同时添加了可视化输入和修改的模式,以及将原本的矩阵对象改为了类模板来实现多态。

本次项目的规模较大,在4000行左右,本文就不再粘贴代码文件,只对一些核心逻辑进行解释,我已经将项目的代码文件发布到gitee上了(代码中的注释还算比较详细):

项目二:基于Qt的线性代数运算工具: 基于Qt开发,可以进行一些矩阵的运算 (gitee.com)

【二】项目难点

本次项目的难点主要在于

1.        矩阵的交互窗口输入输出

2.        二叉查找树的相关算法

3.        矩阵的各种运算特别是矩阵求逆和线性方程组的求解,需要一定的线性代数的基础。

4.        由于涉及到主线程和工作线程共同访问一个对象的问题,因此还涉及到线程同步

5.        交互指令逻辑的设置,即如何处理用户的非法输入

【三】涉及到的知识点

1.        类模板与友元函数:非约束型模板友元

2.        二叉查找树的相关算法(包括节点插入,删除,中序遍历)

3.        哈希函数APHash(不是重点,主要是要会用)

4.        Qt基本控件特别是QTreeWidget,QTextBrowser,QTableWidget的基本使用

5.        矩阵的加减乘运算,初等行变换及矩阵求逆等线性代数知识

6.        基于互斥量的线程同步(本文基于QReadWriteLock)

7.        Qt的信号与槽机制

说明:

C++模板友元这一块的内容确实不太好理解,这里简单介绍一下三种模板友元函数的实现方法:

 

 

 

【四】设计

从UI入手考虑所需要的功能:

主窗口可视化输入模块:

  1. QTableWidget对象用于矩阵内容的可视化输入和显示
  2. QAction 对象用于可视化交互
  3. QTreeWidget 对象用于用户定义的矩阵对象的可视化存储
  4. QDockWiget 对象用于显示QListWidget对象,由于该控件是铆接在主窗口上的用户可以选择关闭与否

指令窗口模块:

  1. QTextEdit 对象用于用户输入指令
  2. QTextBrowser对象用于用户指令的显示,以及程序响应信息的显示

核心逻辑的设计:

  1. 指令交互:

指令窗口需要用户进行输入,很明显不能使用一个线程完成任务,因此指令窗口的逻辑需要放在一个工作线程中完成,这里我们对【项目一】的代码进行修改后直接使用。【项目一】中已经重载了基本数据类型double int QString QChar的流提取和流插入运算符,只需要对流提取和流插入运算符重载就可以满足Fraction和Matrix对象的输入与输出。

  1. 运算逻辑:

自定义Fraction对象用于实现分数的运算

定义类模板Matrix 用于实现矩阵的运算

  1. 对象存储:

运行时存储:

使用二叉树和散列函数,将用户输入的矩阵名称通过散列函数(哈希函数)转化成 一串数,按照二叉树的存储规则进行存储,这样可以方便函对不同对象的访问

【五】文件组织

main.cpp 主函数源文件,提供程序的入口

mainwindow.h 主窗口头文件,为主窗口类的相关声明

mainwindow.cpp 主窗口源文件,为类的各种函数定义

helpmessagedialog.h 帮助信息窗口

helpmessagedialog.cpp

versionmessagedialog.h 作者及版本信息对话框头文件

versionmessagedialog.cpp 作者及版本信息对话框源文件

iomsgstream.h 自定义流对象头文件,实现窗口上的输入与输出交互

iomsgstream.cpp 自定义流对象源文件,实现头文件中的函数定义

iothread.h 声明工作线程类,

iothread.cpp 所有的“控制台”交互逻辑在其中实现

fraction.h 分数对象头文件

fraction.cpp 分数对象源文件

matrix.h 矩阵对象的模板,模板的申明和定义都需要在头文件中

objectTree.h 对象树头文件,用于保存运行时矩阵对象

objectTree.cpp 对象树源文件

mainwindow.ui Qt的ui设计文件,设计主窗口的ui设计

versionmessagedialog.ui Qt的ui设计文件,作者及版本信息对话框的ui设计

【六】UI界面的实现

说明:通过Qt Designer设计界面:

控件的命名规则: 控件类型_控件功能(其中控件类型完全小写,控件功能符合大驼峰命名即每个单词首字母大写)

资源文件说明: 为了让界面更加好看,可以在项目中添加资源文件保存需要的图标文件和其他图片文件(这里图标的命名规则为: 图标含义_ICON 例: Exit_ICON.ico)这里推荐一个网址:ByteDance IconPark (oceanengine.com)

运行界面:

 

注意事项: 需要说明的是,状态栏和工具栏的按钮和标签无法在Qt Designer界面设计,因此我们使用代码的方式添加,具体操作在mainwindow.cpp文件中,同时为了方便,菜单栏和工具栏Action的设计在Qt Designer 中实现:

 

在红框处右键新建即可

控件名称较多,为了方便区别在命名的时候最后按一定规则命名,这里命名基本都是英文全称。

【七】窗口输入与输出的实现

我们的思路与【项目一】基本一致,即创建一个工作线程等待用户输入,使用信号与槽机制实现线程之间的交流,不同点在于会添加一些额外的功能,如错误信息输出等以及将工作线程和流对象分成两个文件实现

声明工作线程类和流输入输出对象及换行对象并定义了min和mout流对象实现窗口输入与输出添加了exceptionMsgAppend信号以红色字显示错误信息,该信号由成员函数exceptionMessage发送

这里模仿C++标准输入输出流,在Iomsgstream.h的命名空间MYIO中添加了myin(类型class MsgInStream),  myout(类型class MsgOutStream), endl(类型 class StreamLineEnd) 对应的是std::cin, std::cout, std::endl

值得注意的一点是: 命名空间中我们只能作一个声明如下图:

 

由于命名空间处于头文件中,如果在其中定义变量,在头文件多次引用的过程中该变量会发生重复定义的问题,导致编译报错,因此添加声明”extern”告诉编译器到其他的文件里寻找这些对象的定义,另外在iomsgstream.cpp中定义这些对象的时候要注意作用域,如下是错误的写法(相当于重新定义了一个全局变量):

 

正确的定义写法是:

 

 【八】矩阵对象的实现

分数对象的实现较为简单,详细查看代码注释,这里就不多赘述了

Matrix 对象的实现

我们以模板的方式来实现这样可以实现多态(定义Fraction 矩阵, double 矩阵,int 矩阵)

Matrix 较为复杂,这里对其成员的声明进行简单介绍具体的逻辑和功能可以查看注释:

静态成员函数:

template <class Type>

static void arrayClear(Type*** a,int row,int col);

用于清除堆空间中创建的二维指针数组(清除所有元素)

static void mallocFailed(string message);

用于处理内存分配失败的状况(特殊情况会触发,一般不会触发)

static void logicalProblem(string message);

用于代码中异常部分的逻辑报错(弹出对话框)

static Numtype*** NULL_array(int row,int col);

用于创建空指针数组(未给元素分配内存)

构造函数和析构函数:

Matrix():mx_Array(NULL),row(0),col(0){};

Matrix(Numtype*** a,int r,int c,bool if_deep = true);

Matrix(int r,int c);

创建空的矩阵

Matrix(const Matrix<Numtype>& m,bool if_deep = true);

“if_deep”是用来告诉构造函数是否从另一个数组或矩阵对象以深复制(重新分配内存,将值拷贝过去而不是直接转移指针)创建新的矩阵对象

~Matrix();

删除掉堆区申请的内存(指针数组)

普通成员函数

void pLTFir(int r1,int r2);

第一类初等行变换(成员函数)

void pLTSec(int r,const Numtypevalue);

第二类初等行变化重载

void pLTThi(int r1,int r2,const Numtypevalue);

第三类初等行变换的函数重载

Matrix inverse(bool if_show=false);

成员函数inverse用于求取逆阵,并返回(如果传入的if_show为false则不打印求逆过程)

Numtype det(bool if_show=false);

返回一个Numtype对象,是该矩阵的行列式结果

Matrix lEqutions(bool if_show=false);

若该矩阵是一个增广矩阵,或者系数矩阵,可以求出该矩阵的解

Matrixoperator=(const Matrixm);

赋值运算符的重载

Matrix trans();

矩阵转置

Numtype member(int row_index,int col_index);

传入索引值返回对应位置的元素

int get_rowLens()const;

获取矩阵的总长行数

int get_colLens()const;

获取矩阵的总列数

【九】矩阵对象树的实现

由四个对象嵌套实现:

BstTree

二叉树主体,保存根节点的指针

                BstNode

                二叉树节点,保存一个Label对象和父节点指针

                                Label

                                保存Matri<Type>x对象的指针和对象名的字符串(std::string)

                                                Matrix<Type> 对象

                                                保存矩阵的数据

使用时根据用户定义的矩阵对象名,通过哈希函数APHash转化成无符号整数,按照二叉查找树的插入算法,将包含矩阵对象的节点插入对应的位置

简单介绍一下这前三个类的成员

Label

静态成员:

static unsigned int hashindex(string str)

公有成员:

void hashindex();

按照Label保存的字符串计算出哈希索引并赋值给Label的索 引变量

unsigned int getindex()

返回保存的索引值

string getName()

获取保存的对象名称字符串

Mgetobj();

返回保存的对象指针

    构造函数:

Label(string strMmx);

BstNode

构造函数

Bstnode(string str,Mmx);

Bstnode(Label<M>* lab);

BstTree

静态成员(递归用):

static void findPlaceToInsert(Bstnode<M>* insBstnode<M>* now,boolFlag)

节点插入时调用

static void findPlaceToDelete(Bstnode<M>* now)

节点清除时调用

由于对树进行操作时为避免对空指针访问,需要作判断因此这里将递归部分和判断部分分开

公有成员:

Bstnode<M>* treeroot();

返回根节点指针

Bstnode<M>* nodeSearch(string str);

节点搜索函数

void nodeInsert(Label<M>* lab);

插入节点(节点由传入的lab对象创建)

void inTraversal(Bstnode<M>* nodeNow);

中序遍历,会将index值按照从小到大的顺序打印

void deleteNode(Bstnode<M>* node);

删除节点

void clearTree()

清除二叉树

构造函数:

BstTree(Bstnode<M>* r);

BstTree();

【十】指令系统和窗口交互的设计

指令逻辑主要在ioThread的虚函数run中实现,一下进行该系统的设计:

并且由于主线程和工作线程都需要访问对象树所以需要线程同步,我们这里在所有的指令响应函数中都设置为只写保护,当主线程发送的信号响应槽尝试锁定时,弹出提示窗口直到工作线程的响应函数执行结束,解除锁定。其中这个互斥量设置为全局变量,在主线程中定义。指令介绍:

说明: 4.0目前只支持一元,二元的简单运算表达式,即以上的包含两个运算对象的运算 表达式,其他情况会提示指令错误定义矩阵的名称时一些指令的关键次不被允许使用 , 表达式各部分之间应该用空格隔开。

在用户输入指令的时候会经过两个阶段的解析,然后执行相关的功能:

阶段一:

输入头部关键词,如define, new, help, exp,assign等,根据头部关键词转到不同的函数中处理后续的输入,也就是进入阶段二

阶段二:

经过阶段一的初步筛选,为了方便代码的维护,阶段二的功能封装成代码,主要是对同类关键词完成后续的输入和功能实现

根据头部关键词可以将指令分为以下几类:

  1. 以包含”new”的赋值表达式,和非赋值加减乘运算式(exp),还有assign为代表的第一类指令

这一类指令都是含有矩阵对象名的并且表达式比较长,需要考虑的逻辑就是关键词和变量名的区分,我们这里先判断是否是关键词,再判断是否是变量名,当然也要考虑重名冲突的问题。

  1. 以define系列和check,delete等这类指令也包含对象名,不过比较短,难点在于判断 对象的重名冲突问题
  2. 以trans等为代表的基于矩阵运算的指令,这类指令即能单独存在也能包含在new指令 中,比较特殊,所以它们的逻辑实现通过重载函数完成,一部分重载用在单独指令的时 候,另一部分重载用在包含new的指令中,由执行new指令的函数调用

以下对比较复杂的指令进行设计(可能会与实际代码有些许出入但核心是一致的):

 new指令

define 指令

 

可视化界面的交互逻辑

 

工作线程创建的对象要在主线程中显示

 

 用户在主线程中创建对象

 【十一】总结

第一次写这么大的项目,期间前前后后花了半个多月的时间,也出现了一些以前从未见过的问题,比如说在debug模式编译下代码可能没有问题,但是到了发布的时候使用release编译就有可能导致程序出问题,我这次遇到的是release下会对代码进行优化,优化掉一些编译器认为无效的内容,比如我们的流对象中用来阻塞工作线程的while循环就被优化掉了,我在以下博客中找到了问题,对release的优化程度进行修改问题就解决了

(72条消息) 如何在qt5中release版本中修改优化等级,使循环语句不至于被优化_xujianjun229的博客-CSDN博客

这个问题相当不好发现由于release不好调试,我是用了profile进行调试最后发现由于流对象对工作线程的阻塞失效,导致工作线程频繁地发射信号,使得程序卡死

另一点就是编写大型的项目一定要提前充分地做好设计,不能只是做一个大概的框架,我在做这次项目的时候就遇到了这种问题,由于最初设计不够精细没有考虑到一些细节问题最后由于原因要推倒重来,浪费了不少时间

本人水平有限,可能存在的问题希望大家多多指正,让我们共同学习,共同进步!

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
课程的背景 Qt是一个1991年由奇趣科技开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程式,也可用于开发非GUI程式,比如控制台 工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,易于扩展,允许 组件编程。2008年,奇趣科技被诺基亚公司收购,QT也因此成为诺基亚旗下的编程语言工具。2012年,Qt被Digia收购。 1.2、课程内容简介 该课程主要分为三个部分: 第一部分:从QT的基础理论知识入门 第部分:通过QT媒体播放器项目实战训练巩固 第三部分:讲解QT系统移植 1.3、课程大纲 第一讲:QT简介、课程介绍、windows下QT开发环境搭建 第讲:Linux下QT开发环境搭建1:VMWare安装及使用、Linux安装及使用 第三讲:Linux下QT开发环境搭建2:QT X11编译、安装、使用 第四讲:Linux下QT开发环境搭建3:QTEmbedded交叉编译 第五讲:Linux下C++快速串讲1:类与对象 第六讲:Linux下C++快速串讲2:继承与多态 第七讲:QT基本图形组件 第八讲:QT信号和槽 第九讲:纯代码打造QT界面 第十讲:QT消息框和对话框 第十一讲:QT菜单 第十讲:QT工具栏和状态栏 第十三讲:PMP媒体播放器项目实战1:需求分析与关键技术 第十四讲:PMP媒体播放器项目实战2:音频播放 第十五讲:PMP媒体播放器项目实战3:音乐切换 第十六讲:PMP媒体播放器项目实战4:添加和删除播放列表 第十七讲:PMP媒体播放器项目实战5:音量控制和播放方式 第十八讲:系统移植1:系统移植概述及UBoot移植 第十九讲:系统移植2:内核和根文件系统移植 第十讲:系统移植3:Madplay移植和QT移植 第十一讲:PMP项目移植与远程视频监控系统 第讲:项目完善:视频播放和系统设置
您好!基于QT的IOT物联网MQTT工具可以通过以下步骤来实现: 1. 首先,确保您已经安装了QT开发环境。您可以从QT官网下载并安装适合您操作系统的版本。 2. 创建一个新的QT项目。在QT Creator中,选择"File"->"New File or Project",然后选择"QT Widgets Application"。 3. 配置MQTT库。在QT Creator中,选择"Projects"->"Your Project Name"->"Build & Run",然后在"Build"选项卡中添加MQTT库的路径。您可以使用开源的MQTT库,如Eclipse Paho MQTT C/C++库。 4. 在项目中添加MQTT连接。您可以使用MQTT库提供的API来创建一个MQTT连接并设置连接参数,如服务器地址、端口、客户端ID等。 5. 实现MQTT订阅和发布功能。使用MQTT库提供的API,您可以实现订阅特定主题的消息和发布消息到特定主题的功能。 6. 添加UI界面。根据您的需求,设计并添加相应的UI界面元素,如按钮、文本框等。通过这些界面元素,用户可以输入和显示MQTT消息。 7. 连接UI界面和MQTT功能。在QT中,您可以使用信号与槽机制来连接UI界面元素和MQTT功能。通过这种方式,当用户点击按钮或输入文本时,相应的MQTT操作将被触发。 8. 编译和运行项目。在QT Creator中,选择"Build"->"Build Project",然后运行项目。 以上是一个基本的步骤,用于基于QT开发IOT物联网MQTT工具。根据您的具体需求,您可能还需要添加其他功能或进行更详细的实现。希望这对您有所帮助!如果您有任何问题,请随时向我提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学艺不精的Антон

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值