发布于 

我的 QT 学习记录 02

一、对象树

  1. 对象树,也叫对象模型,是把子类依次绑定父类,形成的树状结构

  2. 对象树Qt 简化内存回收的模型,析构一个对象时会依次递归析构其子对象,然后把自己从 parentchildren() 里面删除

  3. 栈上可能遇到析构的问题示例,解决方案是建议堆上进行构造

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 该段代码无问题,因为栈一般先构造的后析构
    // 这里先析构 button,并从 window 的子对象列表中删除
    // 然后析构 window(此时已经无子对象)
    Qwidget window;
    OPushButton button = QPushButton ("退出",&window);

    // 该段代码有问题:因为栈一般先构造的后析构,导致重复析构,程序崩溃。
    // 下面程序是先析构 window,析构 window 及其子对象(quit),然后析构quit
    QPushButton quit("Quit");
    Qwidget window;
    quit.setParent(&window);

二、信号与槽机制

  1. 信号槽Qt 特色,某个事件发生后,会广播一个信号(signal),如果有对象需要对这个信号反馈,就会使用连接函数(connect),即为将要处理的信号与自己的反馈函数(称为槽slot)绑定来处理这个信号。也就是说,信号被发出后,被连接的槽函数会自动被回调,类似观察者模式

  2. 连接函数 connect 的使用

    1. 基本用法:connect(sender,signal,receiver,slot)
    2. 发送者,信号,接收者,槽函数
    3. 可以参考帮助手册上需要的类及其父类的 Public SlotsSignals
  3. 使用预设的信号槽

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // widget.cpp
    #include "widget.h"
    #include <QPushButton>
    Widget::Widget(QWidget *parent)
    : QWidget(parent)
    {
    // 创建按钮
    QPushButton *btn=new QPushButton("点击关闭窗口",this);
    this->resize(600,400);
    // 绑定信号
    connect(btn,&QPushButton::clicked,this,&Widget::close);
    }
    Widget::~Widget()
    {
    }
  4. 自定义信号槽

    1. 大致思路

      1. 添加信号发送类,接收类
      2. 在发送类中声明信号,在接收类中声明并实现槽函数
    2. 注意

      1. 信号 在头文件的 signals声明,返回值 void ,可以有参数,仅声明不实现,可重载
      2. 槽函数 在头文件的 public slots 里面声明,需要实现,返回值 void,可以有参数,可重载。对于一些高版本的来说,也可以写到 public 或者全局。
    3. 简易示例,借助 QDebug

      1. 项目结构

        1. widget.h, widget.cpp, main.cpp, 按照预设
        2. mysender.h, mysender.cpp,继承自 QObject
        3. myreceiver.hmyreceiver.cpp, 继承自 QObject
      2. 信号与槽的定义实现

        1. mysender.h 添加内容

          1
          2
          signals: // 信号声明
          void here_a_signal();
        2. myreceiver.h 添加内容

          1
          2
          public slots:  // 槽函数声明
          void slots_ok();
        3. myreceiver.cpp 添加内容

          1
          2
          3
          4
          5
          #include <QDebug> // 槽函数实现
          void MyReceiver::slots_ok()
          {
          qDebug()<<"这是槽函数,已经收到信号";
          }
      3. 配置与连接

        1. widget.h

          1
          2
          #include "mysender.h"
          #include "myreceiver.h"
          1
          2
          3
          public:
          MySender *sdr;
          MyReceiver *rcvr;
        2. widget.cpp

          1
          2
          3
          4
          5
          // 自定义信号与槽
          this->sdr = new MySender();
          this->rcvr =new MyReceiver();
          // 连接信号槽
          connect(sdr,&MySender::here_a_signal,rcvr,&MyReceiver::slots_ok);
      4. 信号产生

        1. widget.h

          1
          2
          pubic:
          void signal_generate();
        2. widget.cpp

          1
          2
          3
          4
          void Widget::signal_generate() // 信号产生函数
          {
          emit sdr->here_a_signal();// 触发信号
          }
          1
          2
          // 发出信号
          signal_generate();
      5. 程序现象

        1
        2
        3
        10:01:14: Starting Qtlearning01.exe...
        >>这是槽函数,已经收到信号
        10:01:20: Qtlearning01.exe 退出,退出代码: 0
      6. 示例更进一步-绑定按钮

        1. 方式一,信号触发函数

          1
          connect(button,&QPushButton::clicked,this,&Widget::signal_generate);
        2. 方式二,信号连接信号

          1
          connect(button,&QPushButton::clicked,sdr,&MySender::here_a_signal);
    4. 槽函数参数重载 参考CSDN

      1. 在Qt中信号和槽都可以是函数指针,即 &函数名,即可得到函数所在的地址,但是如果遇到重载的函数,如果不区分就会报错

      2. 重载的两种方式

        1. 使用 QOverload

          1
          2
          3
          4
          connect(comboBox, QOverload<int>::of(&QComboBox::activated),
          [=](int index){ /* ... */ });
          connect(comboBox, QOverload<const QString &>::of(&QComboBox::activated),
          [=](const QString &text){ /* ... */ });
        2. 借助函数指针的思想

          1
          2
          3
          4
          5
          6
          void (QComboBox:: * activatedInt)(int) = &QComboBox::activated;
          void (QComboBox:: * activatedString)(QString) = &QComboBox::activated;
          connect(comboBox, activatedInt,
          [=](int index){ /* ... */ });
          connect(comboBox, activatedString,
          [=](const QString &text){ /* ... */ });
      3. C++ 补充示例-函数指针遇上函数重载 参考CSDN

        1. 确定类型指针

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          void print(int a){
          cout << "a is " << a << endl;
          }
          void print(){
          cout << "hello world" << endl;
          }

          typedef void (* Fun)(int);
          typedef void (* Fun2)();

          int main()
          {
          Fun pPrint = print;
          Fun2 pPrint2 = print;
          pPrint(12);
          pPrint2();
          return 0;
          }

          程序运行结果

          1
          2
          >>a is 12
          >>hello world
        2. 非确定类型指针

          重载函数作为参数传递时,特别形参的类型不是确定的函数指针类型时,如 void *,例如 Qt 中的QObject::connect() 函数,重载的信号或槽传入到 connect 时,可以使用 static_cast<> 来区别重载函数:

          1
          connect(subWidget, static_cast<void(SubWidget::*)()>(&SubWidget::switchWin), this, &MainWidget::switchWinSlot);
      4. 一般方法 示例

        1. 重写信号声声明(带参数)

          1
          2
          3
          signals:
          void here_a_signal();
          void here_a_signal(QString content);
        2. 重写槽函数声明与定义(带参数)

          1
          2
          3
          public slots:
          void slots_ok();
          void slots_ok(QString content);
          1
          2
          3
          4
          void MyReceiver::slots_ok(QString content)
          {
          qDebug()<<"这是重载的槽函数,信号内容:"<<content;
          }
          1
          2
          3
          public:
          void signal_generate();
          void signal_generate(QString qstr);
          1
          2
          3
          4
          void Widget::signal_generate(QString qstr)
          {
          emit sdr->here_a_signal(qstr);// 触发信号
          }
        3. 由于函数重载了,这里利用函数指针指向函数地址,然后再作连接

          1
          2
          3
          4
          5
          6
          7
          8
          // 重载的信号与槽
          // ***函数指针指向重载的函数
          void (MySender::*myowsgnal)(QString)=&MySender::here_a_signal;
          void (MyReceiver::*myowslot)(QString)=&MyReceiver::slots_ok;
          // ***信号连接
          connect(sdr,myowsgnal,rcvr,myowslot);
          // 信号触发
          signal_generate("这是一个信号");
        4. 程序现象

          1
          2
          3
          11:18:41: Starting Qtlearning01.exe...
          >>这是重载的槽函数,信号内容: "这是一个信号"
          11:18:50: Qtlearning01.exe 退出,退出代码: 0
        5. 补充-关于 QString

          1. QString 直接输出是有双引号的,如果要去掉双引号,可以按照以下途径进行转换

            1
            // QString->QByteArray->char*
            1
            qDebug()<<"信号内容:"<<content.toUtf8().data();
          2. 此时两个输出之间会多一个空格,若要取消空格,可以使用 nospace() 方法

            1
            qDebug().nospace()<<"信号内容:"<<content.toUtf8().data();
        6. 补充-关于 Qt4 的信号与槽

          1. 使用示例

            1
            connect(sender,SIGNAL(here_a_signal(QString)),receiver,SLOT(slot_ok(QString)));
          2. 其中用到了 SIGNALSLOT 两个宏,把信号和槽函数转为字符串,但是这个过程编译时不会检查拼写错误,故容易出错

三、信号与槽的总结

  1. 注意事项

    1. 信号发送者接收者都是 QObject 的子类,除非使用全局槽函数lambda 函数
    2. 信号和槽函数返回值都是 void
    3. 信号只声明,槽函数声明并实现
    4. 槽函数是普通的成员函数,会受到 public , privace , protect 的影响
    5. 可以使用 emit 在恰当位置发送信号
    6. 使用 connect() 连接信号与槽
    7. 任何成员函数、static 函数、全局函数和 lambda 表达式都可以作为槽函数
    8. 信号槽要求信号槽函数参数一致。如果不一致,允许的情况是槽函数参数比信号少,但类型和顺序应当一致,这样可以在在槽函数中选择忽略信号传来的数据
  2. 拓展

    1. 一个信号可以连接多个槽函数

      此时这些槽会被调用, 但调用顺序不确定

    2. 多个信号可以连接一个槽函数

      任意信号发出, 槽函数都会被调用

    3. 信号连接信号

      一个信号发出会触发第二个信号

    4. 槽可以取消连接

      不常见, 因为一个对象被 Delete 后, 这个对象上的槽也会被自动取消

    5. 可以使用 C++11lambda 表达式


本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。