Qt中的测试 枚举与 QFlags详解

本文详细介绍了Qt中的QFlags在C++编程中的应用,如何通过Q_DECLARE_FLAGS和Q_DECLARE_OPERATORS_FOR_FLAGS提升枚举类型的逻辑运算安全性,并展示了如何使用QFlags实现权限管理和位操作,包括权限的定义、操作和示例。

传统的 C++ 编程中,通常使用整数来保存 enum 的逻辑运算结果 (与、或、非、异或等),在进行逻辑运算的时候没有进行类型检查,一个枚举类型可以和其他的枚举类型进行逻辑运算,运算的结果可以直接传递给接收参数为整数的函数。

Qt 中,模板类 QFlags<Enum> 提供了类型安全的方式保存 enum 的逻辑运算结果解决上面的这几个问题,这种方式在 Qt 里很常见,例如设置 QLabel 对齐方式的函数是 QLabel::setAlignment(Qt::Alignment) (typedef QFlags<Qt::AlignmentFlag> Qt::Alignment),这就意味着传给 setAlignment 的参数只能是枚举 Qt::AlignmentFlag 的变量、它们的逻辑运算结果或者 0,如果传入其他的枚举类型或者非 0 值,编译时就会报错:

label->setAlignment(0); 
label->setAlignment(Qt::AlignLeft | Qt::AlignTop); 
label->setAlignment(Qt::WA_Hover); // Error: 编译时报错

想要把我们定义的枚举类型和 QFlags 一起使用,需要用到两个宏:

  • Q_DECLARE_FLAGS(Flags, Enum):
    • Enum 是已经定义好的枚举类型
    • 展开的结果为 typedef QFlags<Enum> Flags

  • Q_DECLARE_OPERATORS_FOR_FLAGS(Flags):
    • Flags 就是类型 QFlags<Enum>
    • 给 Flags 定义了运算符 |,使得 Enum 和 Enum,Enum 和 Flags 能够使用或运算符 |,结果为 Flags

使用 QFlags 时需要留意以下几点:

  • QFlags 其实就是用于位操作,设置它保存的数值的某一位为 1 或者为 0,所以和 QFlags 一起使用的枚举类型,其变量的值需要是 2 的 n 次方,即 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, …,它们的特点是其二进制位中只有一位是 1,其他位全部为 0,这样每一个枚举值只会和 QFlags 中的某一个位对应,不会出现交叉的情况
  • 调用函数 QFlags::setFlag(Enum flag, bool on = true),on 为 true 时设置 flag 对应的位为 1,on 为 false 时设置 flag 对应的位为 0,设置 flag 对应的位为 1 还可以使用运算符 |
  • 调用函数 QFlags::testFlag(Enum flag) 测试 flag 对应的位是否为 1
  • 整数转为 QFlags: 把整数作为 QFlags 构造函数的参数创建一个 QFlags 变量
  • QFlags 转为整数: 调用 int(flags) 把 QFlags 变量转换为整数值

下面就演示一下怎么使用 QFlags:

// Flag 的变量值是 2^n, 每个值的二进制只有一个位是 1,其他全为 0
enum class Flag {
    Js    = 0x01, //  1 : 0000 0001
    Go    = 0x02, //  2 : 0000 0010
    Cpp   = 0x04, //  4 : 0000 0100
    Php   = 0x08, //  8 : 0000 1000
    Java  = 0x10, // 16 : 0001 0000
    Scala = 0x20, // 32 : 0010 0000

};
Q_DECLARE_FLAGS(Flags, Flag)
Q_DECLARE_OPERATORS_FOR_FLAGS(Flags) // 使得 Flag 和 Flag,Flag 和 Flags 能够使用或运算符 |,结果为 Flags

int main() {
    Flags flags(0x08);               // int to Flags
    flags |= Flag::Js;               // 添加 Js
    flags |= Flag::Go;               // 添加 Go
    flags.setFlag(Flag::Cpp, true);  // 添加 Cpp
    flags.setFlag(Flag::Cpp, false); // 删除 Cpp

    qDebug() << flags;                     // 输出: QFlags(0x1|0x2|0x8)
    qDebug() << flags.testFlag(Flag::Js);  // 输出: true
    qDebug() << flags.testFlag(Flag::Cpp); // 输出: false
    qDebug() << int(flags);                // 输出: 11 (Flags to int)

    flags = Flag::Java | Flag::Scala; // 使用 | 同时设置多个 flag
    qDebug() << flags;                // 输出: QFlags(0x10|0x20)

    // flags = Flag::Java | Qt::AlignLeft; // Error: no viable overloaded =

    return 0;
}

在日常开发中,权限也可以使用 QFlags 来实现,下面只演示 3 种权限的使用,实际开发中根据具体业务需求修改枚举类型 Permission 的变量即可:

#include <QMap>
#include <QStringList>

// 权限
enum class Permission {
    Readable  = 0x01,
    Writable  = 0x02,
    Excutable = 0x04,
};

Q_DECLARE_FLAGS(Permissions, Permission)
Q_DECLARE_OPERATORS_FOR_FLAGS(Permissions)

// 权限的值和其对应的 label 的 map
QMap<Permission, QString> PermissionLabelMap {
    { Permission::Readable,  "可读" },
    { Permission::Writable,  "可写" },
    { Permission::Excutable, "可执行" },
};

// 用户
class User {
public:
    User(const QString &username, int psValue) {
        this->username = username;
        this->ps = Permissions(psValue);
    }

    bool hasPermission(Permission p) const {
        return ps.testFlag(p);
    }

    void addPermission(Permission p) {
        ps.setFlag(p, true);
    }

    void addPermissions(Permissions ps) {
        this->ps |= ps;
    }

    void removePermission(Permission p) {
        ps.setFlag(p, false);
    }

    void removePermissions(Permissions ps) {
        this->ps &= Permissions(~(int(ps)));
    }

    Permissions getPermissions() const {
        return ps;
    }

    QStringList getPermissionLabels() const {
        QStringList labels;

        for (auto i = PermissionLabelMap.cbegin(); i != PermissionLabelMap.cend(); ++i) {
            if (ps.testFlag(i.key())) {
                labels << i.value();
            }
        }

        return labels;
    }

private:
    Permissions ps;
    QString username;
};

int main() {
    User user("Bob", 0);
    user.addPermissions(Permission::Readable | Permission::Excutable);
    qDebug() << user.getPermissions();                    // 输出: QFlags(0x1|0x4)
    qDebug() << user.getPermissionLabels();               // 输出: ("可读", "可执行")
    qDebug() << user.hasPermission(Permission::Readable); // 输出: true
    qDebug() << user.hasPermission(Permission::Writable); // 输出: false

    user.addPermission(Permission::Writable);
    qDebug() << user.getPermissions();      // 输出: QFlags(0x1|0x2|0x4)
    qDebug() << int(user.getPermissions()); // 输出: 7

    user.removePermissions(Permission::Readable | Permission::Excutable);
    qDebug() << user.getPermissionLabels(); // 输出: ("可写")

    return 0;
}

又例如:

class CComGeneralTableWidget : public QTableWidget
{
	Q_OBJECT

public:
	CComGeneralTableWidget(QWidget *parent = Q_NULLPTR);
	~CComGeneralTableWidget();

	/* 显示属性枚举(注意:id、设计id、可见性、显示名是所有元件都有且必须在属性对话框中显示的,这里就不再列出了
		,这里仅仅是列出有些元件需要显示的,有些元件不需要显示的,通过枚举值来控制哪些显示,哪些不显示)
		,枚举值必须为0、2的N次方(N >= 0)
	*/
	enum class EmShowProp
	{
		eNotShowAllProp = 0,            // 画笔颜色、画笔宽度、画刷颜色、线型、旋转信息都不显示
		eShowRotateProp = 1,            // 显示旋转信息
		eShowPenColorProp = 2,          // 显示画笔颜色
		eShowPenWidthProp = 4,          // 显示画笔宽度
		eShowBrushColorProp = 8,        // 显示画刷颜色
		eShowLineTypeProp = 16,          // 显示线型

		// 显示所有
		eShowAllProp = eShowRotateProp | eShowPenColorProp | eShowPenWidthProp | eShowBrushColorProp | eShowLineTypeProp
	};
	Q_DECLARE_FLAGS(EmShowProps, EmShowProp)
	

    ....// 类的其它代码
private:
	 
	EmShowProps m_eShowProp{ EmShowProp::eShowAllProp };
};

Q_DECLARE_OPERATORS_FOR_FLAGS(CComGeneralTableWidget::EmShowProps)

注意:通过上述Q_DECLARE_FLAGS声明后, 定义枚举不再是EmShowProp(最原始的用enum声明的、宏的第二个参数)而是 EmShowProps(宏的第一个参数),如果还用enum声明的枚举名定义枚举变量,则会报如下错误:

error C2228: “.testFlag”的左边必须有类/结构/联合

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值