目录
一, 瞎白话
二, 背景
三, 思路分析
四, 代码讲解
1, 类图
2, 内存结构声明
3,QtHeaderDescription
4, 私有函数讲解
五, 分析结果
六, 下载
一, 瞎白话
时间过的 ZTMK, 距离上一篇文章已经小半年过去了. 为了安家, 装修和结婚, 搞得自己焦头烂额, 这不是也正好赶上过年, 一直没有时间写篇文章, 最近终于慢慢回归正轨, 所以决定写下这一篇文章, 记录工作中的一些经验和内容. 对于写文章这件事, 我是这么认为的: 一个是回顾自己的工作内容; 另一方面也是为了能让有同样需求的同学用于借鉴. 同时这也是我对自己的一个要求, 每个阶段都应该有所输出, 并有所记录, 倘若若干年后, 有机会再次看到这些东西的时候, 能有一丝感动...
废话不多说了, 那我们直接进入今天要分享的内容, 怎么去自动生成 Qt 的信号声明
二, 背景
用过 Qt 的人应该都知道, Qt 中的每个控件都有很多信号, 基于这些信号, 我们可以实现我们自己的响应函数, 也就是槽函数, 通常槽函数和信号是通过 connect 连接起来的. 除此之外呢, 还有一种书写槽的方式, 我们可以不用 connect 来连接, 那就是我们的槽函数名称需要满足一定的规律, 比如我们要实现一个名称为 pushButton_ok 的按钮点击事件, 那么我们的槽函数声明可能会像下面这样:
- private slots:
- void on_pushButton_ok_clicked();
用过 QtCreator 写代码的人可能都知道, 上述的代码可以通过 QtCreator 内嵌的界面设计工具直接生成, 但是当我们直接用 QtDesigner 工具编辑时, 没有了转到槽这个菜单, 如下图左侧的截图所示
上图中的右侧截图是我们修改过后的截图, 当我们直接编辑 UI 文件时, 也可以转到槽, 不同的是我们需要自己去相应的. h 和. cpp 文件中去把声明和实现添加上, 本篇文章我们先分析怎么添加函数声明, 下篇文章在分析怎么添加函数实现定义, 函数声明和定义是怎么构造的, 这里不会讲解, Qt 源码中都有, 有兴趣的同学可以自己去了解下.
三, 思路分析
既然我们的函数声明已经有了, 我们只需要打开 ui 文件对应的头文件, 然后把代码插入到合适的位置上即可, 这里有 2 种方式实现.
方式一: 直接查找指定类的指定作用域标识符, 插入到标识符之后
方式二: 分析头文件, 解析类和其他有用信息, 在内存中把类描述出来, 插入时更灵活
以上两种方式, 各有利弊, 第一种方式简单粗暴, 比较容易实现功能, 但扩展性差, 比如说要插入到指定域的所有函数之后, 就比较难; 第二种方式实现起来比较复杂, 解析头文件是一个比较大的活, 但是一旦文件解析成功后, 插入工作就变得很简单. 这里我选择了第二种方式来实现这个功能.
首先, 解析头文件, 我画了一个大致的流程图, 主要是为了理解起来方便, 并不是特别专业, 凑合着看下
头文件解析时, 主要的规则还是按行读取代码, 然后去检测是否满足某一个类型条件, 比如说已 \\ 开头的我们认为是注释.
当满足条件时, 我们去更新相应的内存结构, 然后继续往下读, 有时候我们可能需要连续读取好几行才能知道当前的内容是什么,
- class
- A
- ;
如上述代码所示, 是一个不标准的 C++ 类预声明, 我们只有读到; 时, 才知道这是一个类预声明, 而不是一个类声明, 有点儿绕口, 但是这个很重要.
图中对于解析一个类模块, 只是简单的用了一个块来表示, 实际上解析一个类也是比较费劲的. 当我们解析完类文件之后, 就是简单的插入操作了, 插入流程如下图所示
四, 代码讲解
1, 类图
类图中总共有 3 个模块: 对外暴露的 QtGrammaAnalysis 类, 提供给用户操作; QtFileCache 是文件缓存类, 供 QtGrammaAnalysis 类调用, 文件缓存类可以有多个, 主要是为了分析不同类型的文件; QtHeaderDescription 是真正的文件描述类, 所有的实际操作都是通过这个类来进行的.
2, 内存结构声明
哈哈哈, 好的代码自带注释, 下面的结构体是为了我们解析头文件而声明的, 每个重要的字段都有注释, 这里不在做解释
- struct OffsetItem
- {
- int start;// 偏移起始行
- int number;// 偏移大小
- };
- struct BaseItem
- {
- BaseItem() :start(0), end(0) {}
- QString name;// 项目名称 列如注释内容, 块名称等
- int start;// 标记代码所在起始行
- int end;// 标记代码所在结束行
- BaseItem & BaseItem::operator += (const OffsetItem &);
- };
- struct ScopePiece : public BaseItem
- {
- ScopePiece() :g_end(-1) {}
- int g_end;// 作用域下所有代码结束
- QList<BaseItem> funcations;// 函数 (变量) 列表
- ScopePiece & ScopePiece::operator += (const OffsetItem & offset);
- };
- struct ClassDescription : public BaseItem
- {
- ClassDescription() :g_end(-1) {}
- int g_end;// 作用域下所有代码结束
- QList<QString> parents;// 父类
- QMap<int, ScopePiece> pieces;// 作用域列表 行号: 域
- QMap<int, ScopePiece> pieceIndexs;// 快速访问索引 域类型: 域
- void RowNumber(const OffsetItem &);
- };
- struct HeaderFile
- {
- QString name;// 文件名
- QList<BaseItem> note;// 注释
- QList<BaseItem> macros;// 宏定义
- QList<BaseItem> includeHeader;// 包含头文件
- QList<BaseItem> predeclaration;// 类预声明
- QMap<QString, ClassDescription> classDeclare; // 类列表
- QMap<int, QString> classOrder; // 类顺序
- QList<BaseItem> cStyleFuncations;//C 函数(全局变量)
- void CleanUp()
- {
- note.clear();
- macros.clear();
- includeHeader.clear();
- predeclaration.clear();
- classDeclare.clear();
- cStyleFuncations.clear();
- }
- void RowNumber(int, int);
- };
当我们把程序运行起来后, 解析一个类文件时, 他的内存描述可能会像这样
3,QtHeaderDescription
QtHeaderDescription 是解析头文件的真正实现类, 代码比较多, 这了我讲下每个函数声明的作用
void SetFile(const QString &); 设置头文件
void Refrush(); 刷新内存结构
void CleanUp(bool = true); 清空内存结构
void GenerateFuncationCode(FuncType, const QString &, const QString & = ""); 插入指定代码在某个作用域
int GetClassStart(const QString & = "") const; 获取类的开始行
int GetClassEnd(const QString & = "") const; 获取类的结束行
int GetScopePieceStart(FuncType, const QString & = "") const; 获取作用域的开始行
int GetScopePieceEnd(FuncType, const QString & = "") const;
void DeleteRow(int); 获取作用域的结束行
QString GetDefaultClass() const { return m_strDefaultClass; }获取默认的插入类名称
void Save(); 保存新的文件
4, 私有函数讲解
StatementType GuessType(int); 预测当前行类型, 可能是注释, 类或者函数等
void ReadFile(); 读取一个文件到内存
void AnalysisFile(); 分析内存中的文件到指定结构中
void AnalysisOne(int &); 分析一行代码
void ReadSingleRow(int); 插入到内存中
void ReadMutilRows(int, int); 插入多行到内存中
void ReadClass(int, int); 读取一个类
void AnalysisClass(int &); 分析一行代码(在类中)
void ReadClassRows(int, int); 插入多行到内存(在类中)
void ReadClassScope(const BaseItem &); 插入域(在类中)
void ReadClassFuncation(const BaseItem &); 插入函数(在类中)
void ReadClassEnd(int, int); 更新类结束标致
QString GenerateString(int start, int end); 根据行号生成串
五, 分析结果
- QtGrammaAnalysis analysis;
- QString oldFilePath = fileInfo.absoluteFilePath();
- analysis.SetHeaderFile(oldFilePath);
- analysis.GenerateDeclaration("\tvoid test1();");
- analysis.SetScopeType(FT_PROTECT_SLOT);
- analysis.GenerateDeclaration("\tvoid test1_1();");
- analysis.SetScopeType(FT_PUBLIC_SLOT);
- analysis.GenerateDeclaration("\tvoid test1_2();");
- analysis.Save();
执行如上插入操作后, 如下图所示
六, 下载
代码下载地址: C++ 解析头文件 - Qt 自动生成信号声明
转载声明: 本站文章无特别说明, 皆为原创, 版权所有, 转载请注明: 朝十晚八 or Twowords
来源: https://www.cnblogs.com/swarmbees/p/10784399.html