目录
简单的信号与槽connect的方法
一般直接如以下方法关联
connect(ui->startTaskButton,&QPushButton::clicked,this,&MainWindow::close);`
关联后,点击startTaskButton这个继承于QPushButton类的按钮时(clicked),触发当前(this)MainWindow的类的close方法
使用自定义信号与槽函数connect
有时qt自带的函数不能满足我们的需求,我们需要自定义信号与槽函数
自定义函数需要满足下面要求
信号函数
- 信号需要在signals下声明 不在signals内声明会触发意料之外的bug
- 返回值为void
- 只需要声明 不需要实现
- 参数任意,可以重载
槽函数
- 返回值为void
- 新版本不需要写入public slots:下 可以写在任意位置
- 需要声明与实现
自定义信号与槽时connect函数的使用
以下举例说明两个常用的使用方法
1 QT5之后的connect语法
connect( 实例指针 , &类::信号函数不带括号 , 实例指针 , &类::槽函数不带括号 )
如:
connect(ui->startTaskButton,&QPushButton::clicked,this,&MainWindow::testSlot);
一个要注意的点是你在creator内添加的控件调用方法一般是ui->button等,这时其已经是指针类型了,在connect的时候不要再加&号了。如
connect(&ui->CreateManuelTaskButton,&QPushButton::clicked,......);
这会导致一堆报错,如
error: no matching member function for call to ‘connect’
error: no matching function for call to ‘MainWindow::connect(QPushButton**, void (QAbstractButton::)(bool), MainWindow::MainWindow(QWidget)::<lambda()>)’
});
^
error: no type named ‘type’ in ‘struct std::enable_if<false, QMetaObject::Connection>’
还有各种warning
这种牵一发而动全身的错误会导致大量标红与报错,使你不知道问题的本源到底在哪。
2 带SIGNAL和SLOT标识的(qt5之前的方法)
这种方法是没有编译期检查的,所以可能导致一些意料之外的bug等,新版之后不建议使用。
connect(实例指针,SIGNAL(实例信号函数带括号),this,SLOT(实例槽函数带括号));
connect(this,SIGNAL(testSignal()),this,SLOT(testSlot()));
如图所示
3 使用lambda表达式
lambda表达式为C++11之后的特性,一个常用的形式为:
connect(ui->CreateManuelTaskButton,&QPushButton::clicked,[](){
//一系列操作
});
lambda的完整形式为
[ capture ] ( params ) opt -> ret { body; };
其中 capture 是捕获列表,params 是参数表,opt 是函数选项,ret 是返回值类型,body是函数体。
当参数void的时候,可以省略() ,即以下两种写法均正确
auto f1 = [](){ return 1; };
auto f2 = []{ return 1; }; // 省略空参数表
当你需要在lambda函数内用到外部变量时,你需要注意[]内的表达。
[] 不捕获任何变量。
[&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
[=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
[=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获 foo 变量。
[bar] 按值捕获 bar 变量,同时不捕获其他变量。
[this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。
同时你可以发现,这时的connect只有三个参数
自定义信号与槽的重载
当发生函数重载时,函数参数可以有多个,这时connect内的函数参数需要创建一个函数指针来代替。
即
connect(实例指针,函数指针,实例指针,函数指针);
为了使用函数指针来代替重载函数以避免二义性,我们要定义一个函数指针来特定指向重载函数的其中一种形式。
先来复习一下函数指针
函数指针
一个栗子:
int(*p)(int, int);
函数指针的定义方式为:
函数返回值类型 (* 指针变量名) (函数参数列表);
“函数返回值类型”表示该指针变量可以指向具有什么返回值类型的函数;“函数参数列表”表示该指针变量可以指向具有什么参数列表的函数。这个参数列表中只需要写函数的参数类型即可。
我们看到,函数指针的定义就是将“函数声明”中的“函数名”改成“(*指针变量名)”。但是这里需要注意的是:“(*指针变量名)”两端的括号不能省略,括号改变了运算符的优先级。如果省略了括号,就不是定义函数指针而是一个函数声明了,即声明了一个返回值类型为指针型的函数。
那么怎么判断一个指针变量是指向变量的指针变量还是指向函数的指针变量呢?首先看变量名前面有没有“”,如果有“”说明是指针变量;其次看变量名的后面有没有带有形参类型的圆括号,如果有就是指向函数的指针变量,即函数指针,如果没有就是指向变量的指针变量。
分析一个栗子:
int(*p)(int, int);
这个语句就定义了一个指向函数的指针变量 p。首先它是一个指针变量,所以要有一个“*”,即(*p);其次前面的 int 表示这个指针变量可以指向返回值类型为 int 型的函数;后面括号中的两个 int 表示这个指针变量可以指向有两个参数且都是 int 型的函数。所以合起来这个语句的意思就是:定义了一个指针变量 p,该指针变量可以指向返回值类型为 int 型,且有两个整型参数的函数。p 的类型为 int(*)(int,int)。
如何调用函数指针指向的函数呢?
int function(int, int);
int main()
{
int(*p)(int, int);
int a, b, c;
p = function;
c = (*p)(a, b); //通过函数指针调用
}
//调用方法即
(*p)(a, b); //通过函数指针调用
函数指针的绑定
创建函数指针时,我们还需要将类的作用域加上。
void(MainWindow:: *slot1)(void) = &MainWindow::testSlot;//定义函数指针
connect(ui->startTaskButton,&QPushButton::clicked,this,slot1);
注意此处为何函数指针没有参数? 如为何不是(*slot1)(“string…”);
这里的函数绑定只需要一个地址,不是调用,所以不需要参数。
记得信号函数一定要放在signals之下 此处未放signals处会使编译器认为你信号函数缺少实现,从而编译错误。
一个实现结果如下图:
emit
有时候我们需要在其他函数中触发一个信号或槽函数。
比如emit function();
那么这emit到底是什么个意思?
我们在qt内直接ctrl+鼠标左键点击emit
查看定义 ,跳转如图
可以发现emit就是个宏定义,在编译的时候其直接会变成空白。这意味着你加不加其实都一样。
我们反过来看一下emit的用法,emit function();
表示发射信号调用function函数,但如果我们去掉emit,这行就成为了function();
思考一下,这不直接是c++的调用函数的用法吗,实际上emit只是起到了提示作用,加不加上效果是一样的。
注意和错误经验
connnet的时候,信号和槽函数的参数类型和数量必须一致,否则编译会出现错误(提示QObject问题)