QFlags类详解

QFlags模板类详解

本文为原创文章,转载请注明出处,或注明转载自“黄邦勇帅(原名:黄勇)

本文出自本人原创著作《Qt5.10 GUI完全参考手册》网盘地址:
https://pan.baidu.com/s/1iqagt4SEC8PUYx6t3ku39Q
《C++语法详解》网盘地址:https://pan.baidu.com/s/1dIxLMN5b91zpJN2sZv1MNg

若对C++语法不熟悉,建议参阅本人所著《C++语法详解》一书,电子工业出版社出版,该书语法示例短小精悍,对查阅C++知识点相当方便,并对语法原理进行了透彻、深入详细的讲解,可确保读者彻底弄懂C++的原理,彻底解惑C++,使其知其然更知其所以然。此书是一本全面了解C++不可多得的案头必备图书。

1、QFlags的本质
QFlags(通常称为标志)是Qt中内置的模板类,其主要作用是为枚举值及其组合运算提供类型安全的算法。比如有如下枚举
enum E{a=1, b=2, c=3};
则void f(E);可接受枚举类型E的成员值,但是不能接受其组合运算的值,比如f(a|b);是错误的,因为a|b的结果是int型而不是类型E。若把函数声明为void f(int);则可以接受枚举值的组合运算,但是同时还能接受整型等其他类型,这是类型不安全的行为。
QFlags类就是为了解决以上问题的,比如f(QFlags< E>); 则f就只能接受类型为E的成员值的组合运算。由此可见,QFlags通常与枚举类型是一起使用的。
在Qt中通常把标志使用typedef进行重新命名(因为QFlags是模板类其类型名太长),重命名之后的名字与对应的枚举类型名字通常只在末尾相差一个字母s,比如与枚举类型E对应的标志通常被重命名为Es,其语法为typedef QFlags< E> Es;
综上所述,在Qt中,若函数的形参是标志,则实参可以是多个按位“或”的枚举类型值,若函数的参数是枚举,则只能指定该枚举类型的单个值,不能使用按位“或”运算符。
2、QFlags源码简介
QFlags模板类的源代码位于qflags.h头文件内,路径为:

F:\app\Qt5.10.1\5.10.1\mingw53_32\include\QtCore

在Qt Creator中,要查看某个标识符的定义,可以先选中该标识符,然后右击,再选择“Follow Symbol Under Cursor”,或选中标识符后再按F2,则就会自动跳到该标识符的定义处,比如要查看qflags.h头文件的内容,先在Qt Creator编辑窗口中输入#include <qflags.h>然后选中“qflags.h”,按下F2,此时会自动跳到该头文件内,此时在图3-5所示位置即可看到该头文件的具体路径
在这里插入图片描述

下面对与QFlags源代码相关的类QFlag作一解释,代码如下

Q_DECL_CONSTEXPR inline QFlag(int ai) Q_DECL_NOTHROW : i(ai) {}

 Q_DECL_CONSTEXPR:按F2之后可看到,这个宏什么也不表示,因此可省略。
 Q_DECL_NOTHROW:按下F2之后可看到,这是一个与是否抛出异常有关的宏,该宏对于理解QFlags模板类的工作原理不是关键,因此也可忽略。
 忽略以上两个宏和inline之后,以上代码其实就是 QFlag(int ai):i(ai){}
 注意:在Qt中,很多标识符就只相差一个字符s,因此要注意观看,比如在qflags.h头文件中的源码就有两个类QFlag和QFlags,这是两个不同的类。
3、模仿QFlags模板类的实现原理
示例3.2模仿了QFlags模板类的实现原理,明白该例代码后,就能理解源代码的工作原理了,为简化程序,以下示例仅对QFlags的其中一种操作符进行了模仿,其他操作符的原理是相同的。

示例3.2:QFlags类实现原理
1  #include<iostream>
2  using namespace std;
3  class B{public:  /*此类专门用于类型转换,即用于把类型B转换为int和把int转换为类型B。此类相当于源代码中的QFlag,以下两个函数都是转换函数,详见(<C++语法详解>12.3节)*/
4		int i;
5		B(int f):i(f){cout<<"B"<<endl;}     //❻
6		operator int(){cout<<"Bi"<<endl; return i;}		};    //❽,类B结束
7  template<class T> class A{ public:  //模板类A,类似于源代码中的QFlags
8		int i;
9		typedef T T1;
10		A(T t):i(int(t)){cout<<"AA"<<endl;}    //❸
11       A(B f):i(f){cout<<"AB"<<endl;}  /*类A的单形参构造函数不能为int类型以把int类型转换为类A 类型,这样可避免直接使用整型实参调用f函数  ❼*/   
12		A operator|(T t){cout<<"AC"<<endl;     //❹
13				return A(B(i|(int)t));	}    //❺
14		A operator|(A t)  //C++语法,形参中的A t相当于A<T> t;详见《C++语法详解》15.6节
15				{cout<<"AD"<<endl;return A(B(i|t.i));}		
};   //类A结束
16  #define D(t,E) typedef A<E> t;   //宏,相当于源码中的宏Q_DECLARE_FLAGS(Flags,Enum)
/*宏P相当于源代码中的Q_DECLARE_OPERATORS_FOR_FLAGS(Flags),该宏展开后的两个全局函数用于两枚举类型进行相加。*/
17  #define P(t) \
18  A<t::T1> operator|(t::T1 f1,t::T1 f2){cout<<"E"<<endl;return A<t::T1>(f1)|f2;}\
19  A<t::T1> operator|(t::T1 f1,A<t::T1> f2){cout<<"F"<<endl;return f2|f1;}
20  enum EE{a=1,b,c,d};
21  D(ss,EE); //展开后为:typedef A<EE> ss;
22  P(ss);   /*展开宏P后,相当于在此处定义了两个对“|”重载的全局函数,展开后的最终代码如下:
              A<EE> operator|(EE f1,EE f2){cout<<"E"<<endl;return A<EE>(f1)|f2;}  ❷
A<EE> operator|(EE f1,A<EE> f2){cout<<"F"<<endl;return f2|f1;}*/
23  void f(ss j){cout<<"G="<<j.i<<endl; }    //❾,类型ss保证传递给f函数的参数的类型安全性。
24  enum XX{x,y,z};
25  int main(){	
26 		EE me=c;
		//验证,f函数只能接受枚举类型EE的成员
27		f(a);   //正确,输出G=1,a的类型为EE
28		f(a|b);  //正确,输出G=3,a|b的结果为EE类型  ❶
29		f(me|d); //正确,输出G=7,原因同上
30		//f(1|b);  //错误。因为无法把1|b的结果转换为类型EE
31		//f(b|2);  //错误。同上。
32		//f(1|2);  //错误,因为1|2的结果类型不是EE
33		//f(x|y);  //错误,f只能接受枚举类型EE的成员
		return 0;	}

程序运行过程分析(C++语法本文不会讲解,语法请参阅《C++语法详解》):
1)、第21行,宏D的展开比较简单,直接替换即可,略
2)、第22行的宏P展开步聚为(以第18行代码的函数头为例进行说明):
在这里插入图片描述
3)、第28行f(a|b)执行步骤的分析(见图3-6):
在这里插入图片描述
本文作者:黄邦勇帅(原名:黄勇)

在这里插入图片描述

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值