一、瞎白话

时间过的ZTMK,距离上一篇文章已经小半年过去了。为了安家、装修和结婚,搞得自己焦头烂额,这不是也正好赶上过年,一直没有时间写篇文章,最近终于慢慢回归正轨,所以决定写下这一篇文章,记录工作中的一些经验和内容。对于写文章这件事,我是这么认为的:一个是回顾自己的工作内容;另一方面也是为了能让有同样需求的同学用于借鉴。同时这也是我对自己的一个要求,每个阶段都应该有所输出,并有所记录,倘若若干年后,有机会再次看到这些东西的时候,能有一丝感动。。。

废话不多说了,那我们直接进入今天要分享的内容,怎么去自动生成Qt的信号声明

二、背景

用过Qt的人应该都知道,Qt中的每个控件都有很多信号,基于这些信号,我们可以实现我们自己的响应函数,也就是槽函数,通常槽函数和信号是通过connect连接起来的。除此之外呢,还有一种书写槽的方式,我们可以不用connect来连接,那就是我们的槽函数名称需要满足一定的规律,比如我们要实现一个名称为pushButton_ok的按钮点击事件,那么我们的槽函数声明可能会像下面这样:

  1. private slots:
  2. void on_pushButton_ok_clicked();

用过QtCreator写代码的人可能都知道,上述的代码可以通过QtCreator内嵌的界面设计工具直接生成,但是当我们直接用QtDesigner工具编辑时,没有了转到槽这个菜单,如下图左侧的截图所示

上图中的右侧截图是我们修改过后的截图,当我们直接编辑UI文件时,也可以转到槽,不同的是我们需要自己去相应的.h和.cpp文件中去把声明和实现添加上,本篇文章我们先分析怎么添加函数声明,下篇文章在分析怎么添加函数实现定义,函数声明和定义是怎么构造的,这里不会讲解,Qt源码中都有,有兴趣的同学可以自己去了解下。

三、思路分析

既然我们的函数声明已经有了,我们只需要打开ui文件对应的头文件,然后把代码插入到合适的位置上即可,这里有2种方式实现。

  • 方式一:直接查找指定类的指定作用域标识符,插入到标识符之后
  • 方式二:分析头文件,解析类和其他有用信息,在内存中把类描述出来,插入时更灵活

以上两种方式,各有利弊,第一种方式简单粗暴,比较容易实现功能,但扩展性差,比如说要插入到指定域的所有函数之后,就比较难;第二种方式实现起来比较复杂,解析头文件是一个比较大的活,但是一旦文件解析成功后,插入工作就变得很简单。这里我选择了第二种方式来实现这个功能。

首先,解析头文件,我画了一个大致的流程图,主要是为了理解起来方便,并不是特别专业,凑合着看下



头文件解析时,主要的规则还是按行读取代码,然后去检测是否满足某一个类型条件,比如说已\\开头的我们认为是注释。

当满足条件时,我们去更新相应的内存结构,然后继续往下读,有时候我们可能需要连续读取好几行才能知道当前的内容是什么,

  1. class
  2. A
  3. ;

如上述代码所示,是一个不标准的C++类预声明,我们只有读到;时,才知道这是一个类预声明,而不是一个类声明,有点儿绕口,但是这个很重要。

图中对于解析一个类模块,只是简单的用了一个块来表示,实际上解析一个类也是比较费劲的。当我们解析完类文件之后,就是简单的插入操作了,插入流程如下图所示

四、代码讲解

1、类图

类图中总共有3个模块:对外暴露的QtGrammaAnalysis类,提供给用户操作;QtFileCache是文件缓存类,供QtGrammaAnalysis类调用,文件缓存类可以有多个,主要是为了分析不同类型的文件;QtHeaderDescription是真正的文件描述类,所有的实际操作都是通过这个类来进行的。

2、内存结构声明

哈哈哈,好的代码自带注释,下面的结构体是为了我们解析头文件而声明的,每个重要的字段都有注释,这里不在做解释

  1. struct OffsetItem
  2. {
  3. int start;//偏移起始行
  4. int number;//偏移大小
  5. };
  6. struct BaseItem
  7. {
  8. BaseItem() :start(0), end(0) {}
  9. QString name;//项目名称 列如注释内容、块名称等
  10. int start;//标记代码所在起始行
  11. int end;//标记代码所在结束行
  12. BaseItem & BaseItem::operator += (const OffsetItem &);
  13. };
  14. struct ScopePiece : public BaseItem
  15. {
  16. ScopePiece() :g_end(-1) {}
  17. int g_end;//作用域下所有代码结束
  18. QList<BaseItem> funcations;//函数(变量)列表
  19. ScopePiece & ScopePiece::operator += (const OffsetItem & offset);
  20. };
  21. struct ClassDescription : public BaseItem
  22. {
  23. ClassDescription() :g_end(-1) {}
  24. int g_end;//作用域下所有代码结束
  25. QList<QString> parents;//父类
  26. QMap<int, ScopePiece> pieces;//作用域列表 行号:域
  27. QMap<int, ScopePiece> pieceIndexs;//快速访问索引 域类型:域
  28. void RowNumber(const OffsetItem &);
  29. };
  30. struct HeaderFile
  31. {
  32. QString name;//文件名
  33. QList<BaseItem> note;//注释
  34. QList<BaseItem> macros;//宏定义
  35. QList<BaseItem> includeHeader;//包含头文件
  36. QList<BaseItem> predeclaration;//类预声明
  37. QMap<QString, ClassDescription> classDeclare; //类列表
  38. QMap<int, QString> classOrder; //类顺序
  39. QList<BaseItem> cStyleFuncations;//C函数(全局变量)
  40. void CleanUp()
  41. {
  42. note.clear();
  43. macros.clear();
  44. includeHeader.clear();
  45. predeclaration.clear();
  46. classDeclare.clear();
  47. cStyleFuncations.clear();
  48. }
  49. void RowNumber(int, int);
  50. };

当我们把程序运行起来后,解析一个类文件时,他的内存描述可能会像这样

3、QtHeaderDescription

QtHeaderDescription是解析头文件的真正实现类,代码比较多,这了我讲下每个函数声明的作用

  1. void SetFile(const QString &); 设置头文件
  2. void Refrush(); 刷新内存结构
  3. void CleanUp(bool = true); 清空内存结构
  4. void GenerateFuncationCode(FuncType, const QString &, const QString & = ""); 插入指定代码在某个作用域
  5. int GetClassStart(const QString & = "") const; 获取类的开始行
  6. int GetClassEnd(const QString & = "") const; 获取类的结束行
  7. int GetScopePieceStart(FuncType, const QString & = "") const; 获取作用域的开始行
  8. int GetScopePieceEnd(FuncType, const QString & = "") const;
  9. void DeleteRow(int); 获取作用域的结束行
  10. QString GetDefaultClass() const { return m_strDefaultClass; }获取默认的插入类名称
  11. void Save(); 保存新的文件

4、私有函数讲解

  1. StatementType GuessType(int); 预测当前行类型,可能是注释、类或者函数等
  2. void ReadFile(); 读取一个文件到内存
  3. void AnalysisFile(); 分析内存中的文件到指定结构中
  4. void AnalysisOne(int &); 分析一行代码
  5. void ReadSingleRow(int); 插入到内存中
  6. void ReadMutilRows(int, int); 插入多行到内存中
  7. void ReadClass(int, int); 读取一个类
  8. void AnalysisClass(int &); 分析一行代码(在类中)
  9. void ReadClassRows(int, int); 插入多行到内存(在类中)
  10. void ReadClassScope(const BaseItem &); 插入域(在类中)
  11. void ReadClassFuncation(const BaseItem &); 插入函数(在类中)
  12. void ReadClassEnd(int, int); 更新类结束标致
  13. QString GenerateString(int start, int end); 根据行号生成串

五、分析结果

  1. QtGrammaAnalysis analysis;
  2. QString oldFilePath = fileInfo.absoluteFilePath();
  3. analysis.SetHeaderFile(oldFilePath);
  4. analysis.GenerateDeclaration("\tvoid test1();");
  5. analysis.SetScopeType(FT_PROTECT_SLOT);
  6. analysis.GenerateDeclaration("\tvoid test1_1();");
  7. analysis.SetScopeType(FT_PUBLIC_SLOT);
  8. analysis.GenerateDeclaration("\tvoid test1_2();");
  9. analysis.Save();

执行如上插入操作后,如下图所示

六、下载

代码下载地址:C++解析头文件-Qt自动生成信号声明


转载声明:本站文章无特别说明,皆为原创,版权所有,转载请注明:朝十晚八 or Twowords


C++解析头文件-Qt自动生成信号声明的更多相关文章

  1. C++解析头文件-Qt自动生成信号定义

    目录 一.概述 二.实现思路 三.代码讲解 1.类图 2.QtCppDescription 3.测试 四.源代码 一.概述 上一篇文章C++解析头文件-Qt自动生成信号声明我们主要讲解了怎么去解析C+ ...

  2. 用shell脚本新建shell文件并自动生成头说明信息

    目标: 新建文件后,直接给文件写入下图信息 代码实现: [root@localhost test]# vi AutoHead.sh #!/bin/bash#此程序的功能是新建shell文件并自动生成头 ...

  3. 用shell脚本新建文件并自动生成头说明信息

    目标: 新建文件后,直接给文件写入下图信息 代码实现: [root@localhost test]# vi AutoHead.sh #!/bin/bash #此程序的功能是新建shell文件并自动生成 ...

  4. Eclipse中R文件不能自动生成

       R文件不能自动生成主要是因为编译有错误,这时你想什么办法都是没有用的,clean, fix properties,都不是从根上解决问题.    R文件主要是自动生成资源文件的id的,里边静态子类 ...

  5. 安装Ruby、Sass在WebStrom添加Watcher实现编辑scss文件时自动生成.map和压缩后的.css文件

    前言 这段时间一直在看Bootstrap,V3官方直接提供了Less版本的源码,就先将Less学完了,很简单的语法,学习写Demo都是在Webstorm里写的,配置了Watcher自动编译(详见< ...

  6. R.java文件无法自动生成的问题

    如果出现R.java文件无法自动生成的问题,同时Console窗口提示下列信息: Android requires compiler compliance level 5.0 or 6.0. Foun ...

  7. webstorm创建js文件时自动生成js注释

    设置webstorm创建js文件时自动生成js注释 settings--Editor--File and Code Temlates 黑色框框里的内容自己填写上去,以下是参考的代码块: /** * @ ...

  8. C++ 多文件编译简述:头文件、链接性、声明与定义

    目录 Commen Sense 头文件 链接性 static 与链接性控制 extern 与外部链接性 Reference Commen Sense C++ 在编译时对每个翻译单元(Translati ...

  9. Qt自动生成.rc文件并配置对应属性 程序图标 版本 描述等

    Qt项目配置文件pro里需要如下配置,进行qmake,build后会自动生成.rc文件,并将对应的信息写入文件中 VERSION = 1.0.0.1 RC_ICONS = "http.ico ...

随机推荐

  1. SQL性能分析之执行计划

    一直想找一些关于SQL语句性能调试的权威参考,但是有参考未必就能够做好调试的工作.我深信实践中得到的经验是最珍贵的,书本知识只是一个引导.本篇来源于<Inside Microsoft SQL S ...

  2. 清理out的浏览器收藏夹发现的

    刚才清理了一下自己的浏览器书签,其实好几年不做收藏了,常用的直接放到书签栏里就行了. 发现不少之前的技术内容域名都被色情病毒经营者续费利用,相关技术内容都是VB.SQL.XMAPP这些过期的玩意,其中 ...

  3. currval of sequence "follow_id_seq" is not yet defined in this session

    postgresql上使用 select currval('follow_id_seq'); 报错: currval of sequence "follow_id_seq" is  ...

  4. 基类包括字段“ScriptManager1”,但其类型(System.Web.UI.ScriptManager)与控件(System.Web.UI.ScriptManager)的类型不兼容

    首先说下原先的情况,就是原本老项目的Web解决方案是使用.net framework 2.0的老版本, 所以机器也安装过Microsoft ASP.NET 2.0 AJAX Extensions..A ...

  5. Kali学习笔记4:Wireshark详细使用方法

    Kali Linux自带Wireshark工具使用介绍: 1.进入界面 这里Lua脚本报错,无需关注 开始使用: 双击第一个eth0:以太网0,开始抓包: 点击上边的这个按钮可以设置: 这里注意:需要 ...

  6. [ Java面试题 ] 框架篇

    1.谈谈你对Struts的理解. 1. struts是一个按MVC模式设计的Web层框架,其实它就是一个Servlet,这个Servlet名为ActionServlet,或是ActionServlet ...

  7. Python黑客泰斗利用aircrack-ng破解 wifi 密码,超详细教程!

    开始前,先连上无线网卡,因为虚拟机中的kali系统不用调用笔记本自带的无线网卡,所以需要一个外接无线网卡,然后接入kali系统. 输入 ifconfig -a 查看网卡,多了个 wlan0,说明网卡已 ...

  8. Java构造器:级联调用,调用兄弟构造器

    级联调用: class Father{ Father(){ System.out.println("Father birth"); } public void announce() ...

  9. 初探Margin负值(转)

    相对而言,margin 负值的使用机率在布局中似乎很少,但是我相信一旦你开始掌握就会着迷,接下来我们看看关于margin负值的一些资料: 它是一个有效的属性,至少w3c中明确描述如下:”Negativ ...

  10. Robot Framework之测试用例分层实战

    1.1  测试用例的第一层(交互层) 1. 创建项目资源(Resource). 操作步骤: 点”项目名称”->右键,选New Resource,在弹窗Name 输入框输入资源名称 mykeywo ...