Qt编程入门(1) : 信号和槽

信号槽是Qt的核心通信机制,类似于一个发送天线,向四面八方发送信号,任何人都可能接收到;槽函数类似于一个收音机,只有该收音机将广播调制到特定的频率上才能接收到对应的广播,频率就是信号槽的连接。当某个特定的对象发送信号时,与之关联的某个槽函数就会被执行。

信号槽原理

Qt的信号signals是一个特殊的宏,而槽函数则和普通的函数没有什么区别。qmake会在编译器之前先行执行,将signals宏展开为C++函数,然后将信号和槽的连接生成为一个带有moc_前缀的cpp文件,里面产生C++代码被包含进行项目中提供给编译器编译。

textChanged信号在moc文件中被变形为:

// SIGNAL 0
int Widget::textChanged(const QString & _t1)
{
    int _t0 = int();
    void *_a[] = { const_cast<void*>(reinterpret_cast<const void*>(&_t0)), const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
    return _t0;
}

其实signals就是一函数,不过在调用QMetaObject::activate(this, &staticMetaObject, 0, _a);之后转到对应的槽函数中去执行罢了。

connect函数在moc文件中被变形为:

void Widget::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        Widget *_t = static_cast<Widget *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: { int _r = _t->textChanged((*reinterpret_cast< const QString(*)>(_a[1])));
            if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; }  break;
        case 1: { int _r = _t->textChanged((*reinterpret_cast< const myclass(*)>(_a[1])));
            if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; }  break;
        case 2: _t->on_pushButton_clicked(); break;
        case 3: _t->onBtnClicked(); break;
        case 4: _t->onTextChanged((*reinterpret_cast< const QString(*)>(_a[1]))); break;
        case 5: _t->onTextChanged((*reinterpret_cast< const myclass(*)>(_a[1]))); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        void **func = reinterpret_cast<void **>(_a[1]);
        {
            typedef int (Widget::*_t)(const QString & );
            if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Widget::textChanged)) {
                *result = 0;
                return;
            }
        }
        {
            typedef int (Widget::*_t)(const myclass & );
            if (*reinterpret_cast<_t *>(func) == static_cast<_t>(&Widget::textChanged)) {
                *result = 1;
                return;
            }
        }
    }
}

这里已经明确的告诉编译器那个信号要调用什么样的槽函数了。

信号槽的连接形式

信号和槽的连接有多种方式,这里只列举最常见的4种形式:

  1. 信号->槽函数,原型:connect(sender, signal, receiver, slot)
    connect(btn, SIGNAL(clicked(bool)), this, SLOT(onBtnClicked()));
    当对象btn发送clicked信号后,this对象的onBtnClicked()函数被执行。

  2. 信号->信号,原型:connect(sender, signal, receiver, signal)
    connect(edit, SIGNAL(textChanged(QString)), this, SIGNAL(textChanged(QString)));
    当edit对象发送textChanged信号后,this对象的textChanged()函数被执行。

  3. 多个信号->同一个槽函数
    connect(btn, SIGNAL(clicked(bool)), this, SLOT(onBtnClicked()));
    connect(edit, SIGNAL(editingFinished()), this, SLOT(onBtnClicked()));
    connect(this, SIGNAL(windowTitleChanged(QString)), this, SLOT(onBtnClicked()));
    无论是btn, edit还是this的信号发送,都会调用this的onBtnClicked()函数。

  4. 一个信号->多个槽函数
    connect(edit, SIGNAL(textChanged(QString)), this, SLOT(onTextChanged(QString)));
    connect(edit, SIGNAL(textChanged(QString)), this, SLOT(raise()));
    一个信号还可以出发多个槽函数被调用,不过他们的执行先后顺序就不确定了。

信号槽的调用方式

查阅connect()函数的帮助文档,发现在最后还有一个参数Qt::ConnectionType,那么这个参数起到什么作用呢?它有很重要的作用,它决定了信号和槽函数在调用时将采取什么样的方式。
它定义了4中连接形式:
1. AutoConnection
自动模式,也是默认的选项。根据Qt的描述,如果信号发送对象和接收对象在同一个线程,那么实际使用的是DirectConnection(直连)模式,如果它们在不同的线程,使用的就是QueuedConnection(队列)模式。这两个模式的意义请继续看下面的讲解。
2. DirectConnection
直连模式,言下之意就是直接调用。当信号发出后,将立即执行槽函数,并且等待槽函数执行完毕后才返回。当发送和接收对象都处于同一个线程内部时使用这种方式。
3. QueuedConnection
队列模式主要用在线程间信号槽传递。当发送和接收处于同一线程时,他们处于一个事件循环中,发送和接收函数的执行都是按照事件循环的顺序执行;而当他们处于不同线程时,发送对象并不知晓接收方所在的线程处于事件循环的什么状态,因此信号被传递进对方线程的信号队列,等待对方线程处理,同时发送信号立即结束返回。他们的执行是异步进行的。
4. BlockingQueuedConnection
有时候虽然他们不在同一个线程,但是发送方也希望等槽函数执行完毕后才返回,因此这个参数就是为了这个目的。值得注意的时使用这个参数必须是发送和接收对象不在一个线程,否则可能会引起死锁。
5. UniqueConnection
这个是一个组合规则,一般很少使用。

当我们使用QueuedConnection模式进行信号槽连接时,必须要确保信号槽的传递参数是 Meta-Object 系统所能识别的类型,如果要传递我们自己定义的类型呢?

信号槽的参数传递

我们知道槽其实就是一个函数,它可以像普通函数一样有返回值,有各种各样的参数。在Qt中,使用 Meta-Object 来表示connect传递的参数。Meta-Object 是什么东西?它是一种能被Qt所识别的类型,比如说int,float,bool等,还有QString,QSize,QRect,QFont等Qt所定义的类型。我们自己创造的class类型是不被Meta-Object系统识别的,那如果要传递我们自己定义的类型时该如何办呢?不用担心,Qt为我们提供了一种注册机制,可以将我们自己的类型注册为Meta-Object类型。

下面是我的自定义类:

class myclass
{
public:
    ~myclass(){}
    myclass(){}
    myclass(const myclass& other){
        if (&other != this) {
            x = other.x;
            y = other.y;
        }
    }
    int x;
    int y;
};

要让Qt识别自己的类型,class必须要提供拷贝构造函数,因为在线程间拷贝的时候会用到拷贝构造函数,如果你不能保证使用系统提供的默认拷贝构造函数能得到正确结果,就必须实现自己的拷贝构造函数。

现在有这样一个信号定义:textChanged(const myclass& data);
还有这样一个槽函数定义:void onTextChanged(const myclass& data);
他们都使用自定义的class类型作为信号槽的传递参数,在使用connect函数之前,我们使用Qt提供的qRegisterMetaType(“myclass”);函数注册该class为Meta-Object类型。然后使用connect连接信号槽,这样我们就可以得到正确的调用。

或许你会发现一个问题,即使不使用qRegisterMetaType函数注册,一样能得到正确结果。那是因为在前面已经说过的问题,在同一个线程中,使用信号槽的默认调用方式是DirectConnection模式,在这种情况下无须注册都可以使用,但当你使用QueuedConnection模式连接时,你就会得到一个错误提示:

QObject::connect: Cannot queue arguments of type 'myclass'
(Make sure 'myclass' is registered using qRegisterMetaType().)

编译器告诉你在使用队列模式时必须要注册Meta-System类型。

参数传递的效率

我们知道一般参数传递分值传递和引用传递,当然也可以说还有指针传递。那么在信号槽中使用不同的参数传递效率会如何呢?
我现在将myclass类修改一下,使其可以在构造和析构的时候打印些字符,这样我就能知道该对象呗创建和拷贝了多少次。

修改后的myclass大致是这样:

class myclass
{
public:
    ~myclass(){ qDebug() << "~myclass()"; }
    myclass(){ qDebug() << "myclass()"; }
    myclass(const myclass& other){
        if (&other != this) {
            x = other.x;
            y = other.y;
        }
        qDebug() << "myclass(const myclass& other)";
    }
    int x;
    int y;
};

分别调用不同的参数传递方式打印出来的结果如下:

// 值传递
myclass()
myclass(const myclass& other)
myclass(const myclass& other)
~myclass()
~myclass()
myclass(const myclass& other)
x= 10 , y= 5
~myclass()
~myclass()
// 引用传递
myclass()
myclass(const myclass& other)
~myclass()
x= 10 , y= 5
~myclass()
// 指针传递
myclass()
x= 10 , y= 5
~myclass()
参数传递方式默认构造函数调用次数拷贝构造函数调用次数
值传递1次3次
引用传递1次1次
指针传递1次

!!
其结果不言而喻,指针的效率是最高的,值传递时拷贝构造函数竟然被调用了3次,分别在emit发送信号时,发送到线程循环等待时,进入槽函数作形式参数时。而引用只在进入线程循环等待时拷贝了一次,这也就说明了为什么使用引用时对象在emit之后被释放了,但槽函数却还是能得到正确的结果,因为线程循环等待时即使是引用传递也会进行对象拷贝的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux下Qt编程入门需要掌握以下几个方面: 1. Qt的基本概念和框架:了解Qt的基本概念和框架,包括Qt的核心模块、GUI模块、网络模块等。 2. Qt的开发环境:熟悉Qt的开发环境,包括Qt Creator、Qt Designer等工具的使用。 3. Qt的编程语言:掌握Qt的编程语言,包括C++和QML。 4. Qt的常用控件:熟悉Qt的常用控件,包括按钮、文本框、标签、列表框等。 5. Qt信号:了解Qt信号机制,掌握如何使用信号进行事件处理。 6. Qt的布局管理器:熟悉Qt的布局管理器,包括水平布局、垂直布局、网格布局等。 7. Qt的文件操作:掌握Qt的文件操作,包括文件读写、文件夹操作等。 8. Qt的网络编程:了解Qt的网络编程,包括TCP/IP协议、HTTP协议等。 以上是Linux下Qt编程入门的基本内容,需要通过实践不断深入学习和掌握。 ### 回答2: Linux是一个开源的操作系统,因为其安全性、稳定性好,越来越多的程序员使用这个操作系统进行开发。在Linux系统下,Qt是一款非常优秀的GUI开发工具,它能够帮助开发者快速、效地进行GUI界面开发。 Qt是一款基于C++的开发工具,可以在Open Source和Commercial版本使用。在Qt的开发,最基本的工具就是Qt Creator,它是一个强大的IDE工具,支持多种开发语言,例如C++、QML、JavaScript等。在Qt Creator,可以方便地进行图形化开发,从而快速开发出GUI界面。 在开始使用Qt编程之前,首先要了解一些基本的概念: 1. QObject:这是所有Qt类的基类,它提供了一些核心功能,例如信号机制、对象树机制等。所有的QObject派生出的类都可以使用这些功能。 2. QWidget:是Qt所有用户界面的基本组件。所有的UI元素都必须派生自QWidget,包括顶层窗口、对话框、所有底层小部件等。 3. 信号机制:是Qt的一种独特的通信机制,用于在不同组件之间传递消息。信号是组件发出的消息,而是对该消息进行响应的组件的函数。 接下来,我们介绍一下Qt在Linux下的编译和运行。 1. 编译程序:使用Qt Creator编译程序非常简单,只需要打开Qt Creator,创建新项目,选择适当的模板,然后进行代码编写和调试。编译完成后,在Linux运行程序也非常简单。只需要打开终端,进入项目文件夹,运行生成的可执行文件即可。 2. 调试程序:在Qt Creator,可以通过断点、观察变量等方式来调试程序。打开Debugger窗口,设置断点,然后运行程序。在程序运行到断点时,可以查看变量的值和调用栈等信息,帮助找到程序的问题。 3. 部署程序:编写好的Qt程序可以在Linux部署,只需要将编译后的可执行文件和依赖的库文件打包即可。打包后,将其拷贝到Linux系统的任意目录下即可。 总之,Qt Creator是一款非常优秀的GUI开发工具,在Linux系统具有很好的适用性,可以让开发者更加方便、效地进行GUI界面开发。 ### 回答3: Qt是一种跨平台的应用程序框架,可以在Windows、Linux、macOS等操作系统上运行。Qt具有强大的GUI(图形用户界面)程序开发功能,也可以用于非GUI程序的开发,比如网络程序、数据库程序等。由于Qt的优秀特性,已成为许多业界公司和个人开发者的选择。本文将简要介绍如何在Linux下开始使用Qt编程。 首先,安装Qt Creator Qt Creator是Qt官方提供的一个集成开发环境,可以帮助我们创建、编写和调试Qt应用程序。在Ubuntu/Linux Mint这类基于Debian的系统,可以通过执行以下命令安装Qt Creator: sudo apt-get install qtcreator 如果你使用的是其他发行版,可以尝试使用发行版自带的包管理器安装,或者在Qt官网下载安装包进行安装。 创建Qt项目 Qt Creator可以帮助我们创建各种类型的Qt项目,比如控制台应用程序、GUI应用程序、库项目等。下面以创建一个控制台应用程序为例介绍: 1. 打开Qt Creator,点击File->New File or Project,选择Console Application,然后点击Choose。 2. 在下一步,填写项目的名称和项目路径,然后点击Next。 3. 在下一步,选择使用哪些Qt库,然后点击Next。 4. 最后,点击Finish创建项目。 编写Qt程序 创建完项目后,我们可以开始编写Qt程序。在Qt Creator,我们可以通过多种方式编写程序代码,比如使用内置的文本编辑器、使用外部编辑器、从文件导入代码等。这里我们使用内置的文本编辑器为例: 1. 点击项目栏的main.cpp,打开代码编辑器。 2. 在代码编辑器输入以下代码: #include <QCoreApplication> #include <QDebug> int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); qDebug() << "Hello, Qt!"; return a.exec(); } 3. 点击Build->Build Project或者直接按Ctrl + B编译代码。 4. 在编译成功后,点击Run->Run or Debug或者直接按Ctrl + R运行程序。 总结 通过上述步骤,我们成功创建并运行了一个简单的Qt控制台应用程序。在之后的学习,我们可以继续深入学习Qt的各种功能,比如图形界面开发、多线程编程、网络编程等等。在学习,我们也可以参考Qt官网提供的文档和示例程序,以便更好地掌握Qt编程。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值