Qt中的枚举变量,Q_ENUM,Q_FLAG,Q_NAMESPACE,Q_ENUM_NS,Q_FLAG_NS以及其他
Qt中的枚舉變量,Q_ENUM,Q_FLAG,Q_NAMESPACE,Q_ENUM_NS,Q_FLAG_NS以及其他
- 前言
- Q_ENUM的使用
- Q_FLAG的引入解決什么問題?
- Q_NAMESPACE,Q_ENUM_NS和Q_FLAG_NS
- 新舊對比
- 結語
前言
之前做一個比較大工程,核心數據里面有很多是枚舉變量,需要頻繁地使用枚舉量到字符串和字符串到枚舉量的操作,為了實現這些操作,我把每個枚舉類型后面都附加了兩個類似Enum_to_String()和String_to_Enum()的函數,程序顯得很臃腫。這時候就非常羨慕C#或者java等兄弟語言,內核內置了枚舉量和字符串轉換的方法。
最近讀Qt文檔時偶然間發現,Qt內核其實已經提供了這個轉換機制,使得我們能用很少的代碼完成枚舉量和字符串的轉換,甚至還能實現其他更酷更強大的功能,下面我們就來看看如何使用Qt的這個功能。
簡單來講,Qt還是使用一組宏命令來完成枚舉量擴展功能的(正如Qt的其他核心機制一樣),這些宏包括Q_ENUM,Q_FLAG,Q_ENUM_NS,Q_FLAG_NS,Q_DECLARE_FLAGS,Q_DECLARE_OPERATORS_FOR_FLAGS,
這些宏的實現原理和如何展開如何注冊到Qt內核均不在本文的講解范圍,本文只講應用。
Q_ENUM的使用
首先講解最簡單明了的宏Q_ENUM,先看代碼:
?
這就是在類中定義了一個普通的枚舉變量之后,額外加入了Q_ENUM(枚舉類型名)這樣的一個宏語句,那么加入了這個Qt引入的宏語句后,我們能得到什么收益呢??
qDebug()<< MyEnum::High<< MyEnum::Low; //qDebug()可以直接打印出枚舉類值的字符串值 QMetaEnum m = QMetaEnum::fromType<MyEnum::Priority>(); //since Qt5.5 qDebug()<< "keyToValue:"<< m.keyToValue("VeryHigh"); qDebug()<< "valueToKey:"<< m.valueToKey(MyEnum::VeryHigh); qDebug()<< "keyCount:"<< m.keyCount(); qDebug() << m.scope() << m.name();輸出結果如下:
注意:如果沒有用Q_ENUM(Priority),則會報如下錯誤:
且此時qDebug() << MyEnum::High << MyEnum::Low;? ? ? 輸出的是枚舉類型表示的數值即1和2而不是字符串。??m.scope()返回枚舉所在類名,本類中的枚舉在MyEnum類的內部定義,所以返回的是MyEnum,m.name()返回是枚舉名,本例的枚舉名是Priority,如果將枚舉定義放到內的外部,則m.scope()返回空。另外對于Qt的Qt's Property System、Meta-Object System默認是不支持枚舉類型的,即如下代碼:
Q_PROPERTY(EnFlowOrient floworient READ flowOrient WRITE setFlowOrient STORED true DESIGNABLE true)中的EnFlowOrient枚舉定義如下:
// 油液流動的方向enum class EnFlowOrient{eRightOrDownFlow, eLeftOrUpFlow, };?默認是不支持的,要使其支持枚舉,也必須用用Q_ENUM(EnFlowOrient);聲明且枚舉必須放在類內部聲明且必須是public才行。
可見,使用Q_ENUM注冊過的枚舉類型,可以不加修飾直接被qDebug()打印出來,另外通過靜態函數QMetaEnum::fromType()可以獲得一個QMetaEnum 對象,以此作為中介,能夠輕松完成枚舉量和字符串之間的相互轉化。這一點恐怕是引入Q_ENUM機制最直接的好處。
除此以外,QMetaEnum還提供了一個內部的索引,從1開始給每個枚舉量按自然數順序編號(注意和枚舉量本身的數值是兩回事),提供了int value(int index) 和const char *key(int index)
兩個便捷函數分別返回枚舉量對應的數值和枚舉量對應的字符串,配合keyCount() 函數可以實現枚舉量的遍歷:
?
輸出:
Priority : High 1 Low 2 VeryHigh 4 VeryLow 8其中name()函數返回枚舉類型名字。
Q_ENUM使用起來很很簡單,對不對?但是還是有幾個注意事項需要說明:
Q_ENUM只能在使用了Q_OBJECT或者Q_GADGET的類中,類可以不繼承自QObject,但一定要有上面兩個宏之一(Q_GADGET是Q_OBJECT的簡化版,提供元對象的一部分功能,但不支持信號槽);
Q_ENUM宏只能放置于所包含的結構體定義之后,放在前面編譯器會報錯,結構體定義和Q_ENUM宏之間可以插入其他語句,但不建議這樣做;
一個類頭文件中可以定義多個Q_ENUM加持的結構體,結構體和Q_ENUM是一一對應的關系;
Q_ENUM加持的結構體必須是公有的;
Q_ENUM宏引入自Qt5.5版本,之前版本的Qt請使用Q_ENUMS宏,但Q_ENUMS宏不支持QMetaEnum::fromType()函數(這也是Q_ENUMS被棄用的原因)。
Q_FLAG的引入解決什么問題?
除了Q_ENUM,Qt中還有另一個類似的宏——Q_FLAG,著力彌補C++中結構體無法組合使用,和缺乏類型檢查的缺點,怎么理解呢?我們看一個例子:
在經典C++中,如果我們要定義一個表示方位的結構體:
注意這里枚舉值被定義成等比數列,這個技巧給使用"|“操作符擴展留下了空間,比如,左上可以用Up | Left來簡單表示,但是這里也帶來了一個問題,Up | Left值是一個整型,并不在枚舉結構Orientation中,如果函數使用Orientation作為自變量,編譯器是無法通過的,為此往往把函數自變量類型改為整型,但因此也就丟掉了類型檢查,輸入量有可能是其他無意義的整型量,在運行時帶來錯誤。
Qt引入QFlags類,配合一組宏命令完美地解決了這個問題。
QFlags是一個簡單的類,可以裝入一個枚舉量,并重載了與或非等運算符,使得枚舉量能進行與或非運算,且運算結果還是一個QFlags包裝的枚舉量。一個普通的枚舉類型包裝成QFlags型,需要使用Q_DECLARE_FLAGS宏,在全局任意地方使用”|"操作符計算自定義的枚舉量,需要使用Q_DECLARE_OPERATORS_FOR_FLAGS宏。
再看一段代碼:
?
?上面這段代碼展示了使用Q_FLAG包裝枚舉定義的方法,代碼中Q_DECLARE_FLAGS(Flags, Enum)實際上被展開成typedef QFlags< Enum > Flags,所以Q_DECLARE_FLAGS實際上是QFlags的定義式,之后才能使用Q_FLAG(Flags)把定義的Flags注冊到元對象系統。Q_FLAG完成的功能和Q_ENUM是類似的,使得枚舉量可以被QMetaEnum::fromType()調用。
看一下使用代碼:
?
執行結果:
QFlags<MyEnum::Orientation>(Up|Down) keyToValue: -1 valueToKey: keysToValue: 3 valueToKeys: "Up|Down" isFlag: true name: OrientationFlags enumName: Orientation scope: MyEnum?可以看到,經過Q_FLAG包裝之后,QMetaEnum具有了操作復合枚舉量的能力,注意這時應當使用keysToValue()和valueToKeys()函數,取代之前的keyToValue()和valueToKey()函數。另外,isFlag()函數返回值變成了true,name()和enumName()分別返回Q_FLAG包裝后和包裝前的結構名。
實際上此時類中是存在兩個結構體的,如果在定義時加上了Q_ENUM(Orientation),則Orientation和OrientationFlags都能被QMetaEnum識別并使用,只不過通常我們只關注Q_FLAG包裝后的結構體。
這樣我們順便明白了Qt官方定義的許多枚舉結構都是成對出現的原因,比如
?
再總結下Q_FLAG以及Q_DECLARE_FLAGS、Q_DECLARE_OPERATORS_FOR_FLAGS使用的要點吧:
Q_DECLARE_FLAGS(Flags, Enum)宏將普通結構體Enum重新定義成了一個可以自由進行與或非操作的安全的結構體Flags。Q_DECLARE_FLAG出現在Enum定義之后,且定義之后Enum和Flags是同時存在的;
Q_DECLARE_OPERATORS_FOR_FLAGS(Flags)賦予了Flags一個全局操作符“|”,沒有這個宏語句,Flags量之間進行與操作后的結果將是一個int值,而不是Flags值。Q_DECLARE_OPERATORS_FOR_FLAGS應當定義在類外;
Q_DECLARE_OPERATORS_FOR_FLAGS只提供了“或”操作,沒有提供“與”“非”操作;
Q_DECLARE_FLAGS和Q_DECLARE_OPERATORS_FOR_FLAGS都是和元對象系統無關的,可以脫離Q_FLAG單獨使用,事實上這兩個宏在Qt4就已經存在(不確定更早是否存在),而Q_FLAG是在Qt5.5版本才加入的;
如果在我們的程序中不需要枚舉變量的組合擴展,那么只用簡單的Q_ENUM就好了。
Q_NAMESPACE,Q_ENUM_NS和Q_FLAG_NS
在Qt5.8之后,Qt引入了Q_NAMESPACE宏,這個宏能夠讓命名空間具備簡化的元對象能力,但不支持信號槽(類似類里的Q_GADGET)。
在使用了Q_NAMESPACE的命名空間中,可以使用Q_ENUM_NS和Q_FLAG_NS,實現類中Q_ENUM和Q_FLAG的功能。
看一個例子:
?命名空間中Q_ENUM_NS和Q_FLAG_NS的使用和之前相類似,不再贅述。Q_DECLARE_OPERATORS_FOR_FLAGS則需要定義在命名空間之內。
使用代碼:
運行結果:
QFlags<MyNamespace::Priority>(High|Low) keyToValue: -1 valueToKey: keysToValue: 3 valueToKeys: "High|Low" isFlag: true name: Prioritys enumName: Priority scope: MyNamespace可以看到,從定義到使用,和之前Q_FLAG幾乎一模一樣。
在命名空間中使用Q_ENUM_NS或者Q_FLAG_NS,能讓枚舉結構體定義不再局限在類里,使我們有更多的選擇。另外,在今后Qt的發展中,相信Q_NAMESPACE能帶來更多的功能,我們拭目以待。
新舊對比
Qt一直是一個發展的框架,不斷有新特性加入,使得Qt變得更易用。
本文介紹的內容都是在Qt5.5版本以后才引入的,Q_NAMESPACE的內容甚至要到Qt5.8版本才引入,在之前Qt中也存在著枚舉量的擴展封裝——主要是Q_ENUMS和Q_FLAGS,這套系統雖然已經不建議使用,但是為了兼容性,還是予以保留。我們看看之前的系統如何使用的:
枚舉量定義基本一致,就是Q_ENUMS(Enum)宏放到定義之前,代碼從略。
使用上:
對比改進后的:
QMetaEnum m = QMetaEnum::fromType<MyEnum::Orientation>(); //since Qt5.5不僅僅是3行代碼簡化成1行,更重要的是Qt程序員終于不用再顯式調用元對象QMetaObject了。改進的代碼元對象機制同樣在起著作用,但卻變得更加隱蔽,更加沉默,使得程序員可以把精力更多放到功能的實現上,這大概就是Qt發展的方向。
結語
很多Qt程序員喜歡用舊版本編程,但是我是堅定的新版本擁躉,在給程序編寫帶來便利的同時,還能滿足自己的好奇心,何樂而不為呢?
?
原文鏈接:https://blog.csdn.net/qq_36179504/article/details/100895133
總結
以上是生活随笔為你收集整理的Qt中的枚举变量,Q_ENUM,Q_FLAG,Q_NAMESPACE,Q_ENUM_NS,Q_FLAG_NS以及其他的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: h文件和c文件的关系是什么
- 下一篇: 创造与魔法罕见肉在哪