Qt开发总结(2)——Qt核心技术
阅读原文时间:2021年04月24日阅读:1

Qt在C++标准的基础上添加了一些特性,也即属于Qt自己的核心技术。这些核心技术在Qt Core模块中实现。这些特性主要包括:

  • 非常强大的无缝连接通信机制,称为信号和槽;
  • 可查询和可设计的对象属性;
  • 层次化并可查询的对象树;
  • 用保护指针(QPointer)通过很自然的方式实现对象的所有权管控;
  • 跨库工作的动态投射;

一、元对象系统(The Meta-Object System)

Qt的元对象系统提供了 对象之间通信的信号和槽机制、运行时的类型信息和动态属性系统。元对象系统基本构成为:

1.QObject类。该类是所有元对象系统类的基类。

2.Q_OBJECT宏。在类的成员变量开始的地方声明该宏,就可以使这个类具有元对象的特性,如动态属性和信号与槽。

3.元对象编译器(MOC)。MOC工具在编译时,将含有Q_OBJECT宏的类解释为标准C++源文件,使得通用编译器可以编译Qt项目。

元对象系统是Qt 的核心,我们在开发过程中必然会用到QObject类,Q_OBJECT宏,在编译时也会在VS的输出框看到MOC过程。但是,元对象系统的功能不仅仅如此,它与后面的属性系统,对象模型以及信号与槽有这密切联系,或者说,是它们的基础。

标准C++对象模型为对象范例提供了非常有效的运行效率支持。但GUI编程是一个既需要运行效率,又需要高度灵活性的领域。Qt则通过对象模型则非常好的结合了C+++的速度和灵活性。许多的Qt特性是通过继承QObject类,用标准C++技术实现的。其他的如对象通信机制和动态属性系统,需要Qt自己的MOC来实现。总之,元对象系统时一个C++扩展,使得Qt语言更适合于GUI编程开发。

下面列举一些元对象模型中的设计的基础类:

说明

QMetaClassInfo

Additional information about a class

QMetaEnum

Meta-data about an enumerator

QMetaMethod

Meta-data about a member function

QMetaProperty

Meta-data about a property

QMetaType

Manages named types in the meta-object system

QObject

The base class of all Qt objects

QSignalBlocker

Exception-safe wrapper around QObject::blockSignals()

QObjectCleanupHandler

Watches the lifetime of multiple QObjects

QMetaObject

Contains meta-information about Qt objects

QPointer

Template class that provides guarded pointers to QObject

QSignalMapper

Bundles signals from identifiable senders

QVariant

Acts like a union for the most common Qt data types

二、属性系统(The Property System)

Qt提供了一个复杂的属性系统,就像一些编译器开发商提供的属性系统一样。但是,作为一个独立于编译器和开发平台的类库,Qt不依赖非标准的编译指令比如__property or [property].Qt的工程可以在任何Qt支持的平台上用标准C++编译器编译。这个技术是通过上述的元对象系统实现的。

在QObject的派生类中,用宏Q_PROPERTY()定义属性,其格式如下:

Q_PROPERTY(type name

             (READ getFunction [WRITE setFunction] |

              MEMBER memberName [(READ getFunction | WRITE setFunction)])

             [RESET resetFunction]

             [NOTIFY notifySignal]

             [REVISION int]

             [DESIGNABLE bool]

             [SCRIPTABLE bool]

             [STORED bool]

             [USER bool]

             [CONSTANT]

             [FINAL])

其中的一些主要关键字意义:

READ:指定一个读取属性值的函数。

WRITE:指定一个设定属性值的函数。

MEMBER:指定一个成员变量与属性关联。

RESET:指定一个设置属性缺省值的函数,可选。

NOTIFY:设置一个信号,当属性值发生变化时触发。可选。

Q_PROPERTY宏定义声明一个返回值类型为Type,名称为name的属性,用READ和WRITE关键字定义属性的读取和写入函数。它看上去更像是类的成员变量。如下述的例子:

class MyClass : public QObject
{
      Q_OBJECT
      Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged)

  public:
      MyClass(QObject *parent = 0);
      ~MyClass();

      enum Priority { High, Low, VeryHigh, VeryLow };
      Q_ENUM(Priority)

      void setPriority(Priority priority)
      {
          m_priority = priority;
          emit priorityChanged(priority);
      }
      Priority priority() const
      { return m_priority; }

  signals:
      void priorityChanged(Priority);

  private:
      Priority m_priority;
};

三、对象树和所有权(Object Trees & Ownership)

当存在多个关联的对象时,这些对象可以在树中组织自己。比如,当您创建一个派生类的对象时,它将被添加到父类对象的子对象列表中,并在父对象释放时同时被释放。这个技术实现了在开发Qt项目时,你可能不需要逐个释放你new出来的GUI控件,因为他们继承于QObject类,该类的父对象被删除析构时,这些所有的GUI控件都将删除析构,不会造成内存泄漏。当然你也可以手动删除这些控件,当你delete时实际上也从其父类对象中删掉了该子对象。

看下面的例子:

int main()
{
    QWidget window;
    QPushButton quit("Quit", &window);
    ...
}

复习下C++的知识,分析下上述代码的析构过程。函数内部的临时变量(局部变量)是在栈上分配控件,析构则是相反顺序进行(先进后出)。quit是类QPushButton的对象,代码指定了它的父对象是window。析构时先析构quit,同时将该对象从window的子对象列表中删除,再析构父对象window。这个过程是正确的。如果是下面的这种情况:

int main()
{
    QPushButton quit("Quit");
    QWidget window;

    quit.setParent(&window);
    ...
}

先析构windows会将其所有的子对象析构,包括quit。当再析构quit时,相当于对同一对象执行两次delete,我们知道这在C++标准中是不允许的,程序会“崩溃”。

在开发Qt是几乎每个官方类和自定义类都会有父类,指定其parent。理解好对象树和所有权的概念是至关重要的。

四、信号与槽(Signals & Slots)

信号与槽是Qt的核心的核心,它是区别于其他框架的重要特性。信号与槽是对象间通信的机制,需要Qt元对象系统的支持。

信号与槽隐藏了复杂的底层实现,完成信号和槽的关联后,发射信号并不需要知道Qt是如何找到槽函数的。这类似于一些嵌入式设备的回调函数,但与回调函数相比,信号与槽稍微慢一些,但它的灵活性比回调函数强很多。

信号在某个事件发生的时候发射出去,槽是一个回应这个信号的函数。把这两者联系起来用到connect函数。

connect函数的参数有多种方式,一种是:

connect(sender, SIGNAL(signal(int)), receiver, SLOT(slot(int)));

其中用宏SIGNAL和SLOT指定信号和槽函数,如果信号需要传递参数,那么必须在函数内注明参数类型。

另一种方式是:

connect(sender, QSender::signal, receiver, QSlot::slot);

对具有默认参数的信号和槽,即信号名称是唯一的,没有参数不同而同名的两个信号,可以使用这种方式。

举个自定义信号的例子:

class MyClass:public QObject
{
    Q_Object
private:
    int counter = 0;
public:
    MyClass(QObject *parent = 0){};
    ~MyClass();
    int add();
signals:
    void valChanged(int val);
}
void MyClass::add()
{
    counter++;
    emit valChanged(counter);
}
//mainwindow.h
class QtGuiApplication1 : public QMainWindow
{
    Q_OBJECT

public:
    QtGuiApplication1(QWidget *parent = Q_NULLPTR);

private:
    Ui::QtGuiApplication1Class ui;
private slots:
    void onValChanged(int val);
};

//mainwindow.cpp
MainWinow::MainWindow (QWidget *parent)
    : QMainWindow(parent)
{
    m_class = new MyClass(this);
    connect(m_calss, SIGNAL(valChanged(int)), this, SLOT(onValChanged(int)));
}
void QtGuiApplication1::onValChanged(int val)
{
//do sth for val

}

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章