一、瞎白話
時間過的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