Qt图形化界面学习之信号和槽

本文详细介绍了Qt中的信号和槽机制,包括如何创建自定义信号和槽,以及如何通过函数指针实现信号和槽的重载。还讨论了如何使用Lambda表达式简化代码,并展示了信号连接信号的实现,以及在不同场景下的应用。同时,文章提到了信号和槽连接的规则,以及如何使用disconnect断开连接。
摘要由CSDN通过智能技术生成

信号和槽

连接函数:connect

参数1:信号的发送者
参数2:发送的信号
参数3:信号的接收者
参数4:处理的槽函数

以下实现点击按钮关闭窗口。
在这里插入图片描述

自定义的信号和槽

自定义信号默认写到 signals: 下面,并且返回值是void。
自定义信号只需要声明不需要实现。
自定义信号可以有参数,也可以重载。


槽函数:写到public下,返回值也是void。
槽函数需要声明并且实现。(需要对传递过来的信号进行处理,需要实现)
槽函数可以有参数,也可以重载。

接下来实现我们自定义的信号和槽
下课了 老师->饿了 学生->请老师吃饭
让我们梳理一下:
老师->饿了 是信号的发送者以及发送的信号。
学生->请客吃饭 是该信号的接收者以及处理的槽。
此外,我们还需要一个触发老师饿了的事件,也就是 下课了
实现过程如下:
首先,我们需要创建一个teacher类,在 signals: 下声明老师的信号:饿了
在这里插入图片描述
接着,我们创建一个student类,在里面声明和实现处理的槽函数。
注意:低版本qt需要在 public slots: 下实现,高版本在 public: 下或者 全局函数 实现。
我们先在student类的头文件中声明请客吃饭的函数。
在这里插入图片描述
接着,我们在.cpp文件中实现它。
在这里插入图片描述
注意,这里我们用了QDebug头文件。
其中,qDebug() << 用来打印 。
当然,我们也可以这样 qDebug(“如果只写在括号里,是不需要QDebug头文件的”);

我们已经实现了老师类和学生类,并且实现了老师的信号以及学生处理信号的槽函数。
接下来我们需要将信号和槽连接起来。
我们先在widget.h的头文件中,创建两个成员变量。
在这里插入图片描述
接着我们就要去写connect函数了。
我们始终要记住:参数1是信号的发送者,参数3是信号的接收者,参数2、4就是我们实现的信号以及槽函数了。
在这里插入图片描述
在这里,我们创建老师和学生对象,并且将他们加入到对象树中,然后进行连接。
但是,接下来运行代码我们会发现 程序没有输出。
因为我们只是连接了信号和槽,但是呢,如果没有下课,老师也就不会饿,那么请客吃饭也不会发生。
我们需要一个函数实现下课,并且触发老师饿了信号。
还是在头文件中声明,.cpp中实现。这里用了emit关键字。emit表示这是我们自定义的一个信号:tea->hungry()
在这里插入图片描述
在这里插入图片描述
最后运行输出。
在这里插入图片描述


重载信号和槽

在对信号和槽重载之前,我们先学习一个知识点: 函数指针
我们来看下面这个函数:

int foo()
{
    return 5;
}

在这个函数中,返回值是int类型,foo是函数名,无参,那么该函数类型为“无参数且返回类型为整数”。我们可以这样表示该种函数类型 int ( * somefunction ) ( )。同样的,如果是“有两个整型参数且返回值是布尔型”的函数类型,我们可以表示为: bool (* somefunction ) (int , int)
和变量一样,函数也是在内存中有固定地方存储的。如果我们直接打印函数名字,会出现函数的地址。std::cout<<foo;
接下来我们试着把一个函数赋值给函数指针。

int foo()
{
    return 5;
}
 
int goo()
{
    return 6;
}
 
int main()
{
    int (*funcPtr)() = foo; // funcPtr 现在指向了函数foo
    funcPtr = goo; // funcPtr 现在又指向了函数goo
    //但是千万不要写成funcPtr = goo();这是把goo的返回值赋值给了funcPtr
    return 0;
}

这里我们再举点例子巩固一下。

int foo();
double goo();
int hoo(int x);
 
// 给函数指针赋值
int (*funcPtr1)() = foo; // 可以
int (*funcPtr2)() = goo; // 错误!返回值不匹配!
double (*funcPtr4)() = goo; // 可以
funcPtr1 = hoo; // 错误,因为参数不匹配,funcPtr1只能指向不含参数的函数,而hoo含有int型参数
int (*funcPtr3)(int) = hoo; // 可以,所以应该这么写

接着我们可以通过函数指针来调用函数,例如:

int foo(){
    return 5;
}
int main()
{
    int (*funcPtr1)() = foo;
    int (*funcPtr2)() = &foo; // c++会隐式得把foo转换成&foo,所以你无需再加入&
    std::cout << funcPtr1() << std::endl;
    std::cout << funcPtr2() << std::endl;
}
结果:
5
5
int foo(int x)
{
    return x;
}
 
int main()
{
    int (*funcPtr)(int) = foo; 
    (*funcPtr)(5); // 通过funcPtr调用foo(5)
    funcPtr(5) // 也可以这么使用,在一些古老的编译器上可能不行
    return 0;
}

最后一个知识点,把函数指针作为参数传入另一个函数。

int add(int a, int b) {
	return a + b;
}
int sub(int a, int b) {
	return a - b;
}
void func(int c, int d, int(*f)(int a, int b)) { 
	// 这里传入了一个返回值为int型,双参数都为int型的函数
	cout << f(c, d) << endl;
}
int main()
{
	func(2, 3, add);
	func(2, 3, sub);
	system("pause");
	return 0;
}

接下来回到Qt中,我们需要实现的是对自定义信号以及槽进行重载。
首先呢,我们需要修改信号以及槽函数,加入参数:老师要吃的食物
注意加入头文件#include <QString>
在老师的头文件中,加入 void hungry(QString foodname); (注意信号不需要实现,只声明)
在学生的头文件中,加入并实现槽函数。这里直接给出实现的代码。

void student::GiveTeaFood(QString foodname)
{
    qDebug()<<"请老师吃饭,吃的是:"<<foodname<<endl;
}

最后呢,重写connect函数,在这里就用到了上述的函数指针知识。
因为编译器不知道我们传入的函数是无参还是重载后的有参版本。
connect(tea,&Teacher::hungry,stu,&student::GiveTeaFood);
因此我们需要创建两个函数指针,并且指向的是重载后的版本,之后把指针放入connect中。
void ( Teacher::* teacherSignal)(QString) = &Teacher::hungry;
void (student::* studentSlot)(QString)= &student::GiveTeaFood ;
在这里,需要注意一点,我们需要在函数指针前 写上对应的作用域
接着,在 下课并触发老师饿了 的函数中,加入 传入hungry函数 的参数。
emit tea->hungry("鱼香肉丝");
接着,连接。
connect(tea,teacherSignal,stu,studentSlot);
在这里插入图片描述
最后一点小问题,在 鱼香肉丝 的周围输出了" ",原因是QString类型的foodname输出会默认加引号。我们需要在输出语句中将其转换成char *类型。
这里先用toUtf8将其转换为QByteArray类型,再用data转换为char * 。
qDebug()<<"请老师吃饭,吃的是:"<<foodname.toUtf8().data()<<endl;
在这里插入图片描述


信号连接信号

这里,我们想要实现的功能是:点击一个名为 下课 的按钮,打印 请老师吃饭。
思考一下:我们已经实现了 下课—>老师饿了—>学生请客吃饭 的连接。
我们还需要创建一个 按钮(点击)—>下课 的连接。
在这里插入图片描述
但是呢,我们还可以用另一种方式实现上述功能。
我们 构建 按钮(点击)—> 老师饿了 —> 学生请客吃饭 的顺序传递信号。
暂时只学了 无参信号 可以这么传递。
在这里插入图片描述
最后,断开信号用disconnect函数,传入的是需要断开的两个信号。

连接时注意
1.信号可以连接信号。
2.一个信号可以连接多个槽函数。
3.多个信号可以连接同一个槽函数。
4.信号和槽函数的参数 类型 必须一 一对应。
5.信号和槽函数的参数 个数?信号的参数个数可以多于槽的参数个数。

Lambda表达式

λ表达式的构成如下:
[函数对象参数](操作符重载函数参数) mutable throwSpec -> 返回值 {函数体}
表达式从方括号[ ]开始,结束于花括号{ },参数放在小括号里面( ),如果有返回值,返回值类型要放在 -> 后面,当然也可以不写返回类型,编译器会自动推导。
我们如何来定义一个变量来保存λ表达式,或者说我们应该定义一个什么类型的变量来保存它?事实上,很少有人能够写出一个λ表达式的准确的类型,而λ表达式的类型往往又不重要,因此,我们可以使用C++11的关键字auto来让编译器帮助我们推断其类型。

// 定义简单的lambda表达式
auto basicLambda = [] { cout << "Hello, world!" << endl; };
// 调用
basicLambda();   // 输出:Hello, world!

在上述λ表达式中,没有参数,因此小括号省略了。

// 指明返回类型
auto add = [](int a, int b) -> int { return a + b; };
// 自动推断返回类型
auto multiply = [](int a, int b) { return a * b; };

int sum = add(2, 5);   // 输出:7
int product = multiply(2, 5);  // 输出:10

每当我们定义一个λ表达式,编译器会自动生成一个匿名类,称之为闭包类型。在运行时,这个λ表达式会返回一个匿名的闭包实例。闭包的强大之处在于可以通过传值或者引用的方式捕捉其封装作用域内的变量,前面的方括号就是用来定义捕捉模式以及变量,我们又将其称为lambda捕捉块。
函数对象参数有如下形式:

  1. 空 。

  2. = 。函数体内可以使用λ所在作用范围内所有可见的局部变量,包括(所在类的this),并且是值传递方式。(相当于编译器自动为我们按值传递了所有局部变量)
    在这里插入图片描述
    在这里插入图片描述

  3. & 。函数体内可以使用λ所在作用范围内所有可见的局部变量,包括(所在类的this),并且是引用传递方式。(相当于编译器自动为我们按引用传递了所有局部变量)

  4. a 。将a按值进行传递。按值进行传递的时候,函数体内不能修改传递进来的a的拷贝,因为默认情况下是const。要修改传递进来的a的拷贝,可以添加mutable进行修饰。

  5. &a 。将a按引用方式传递。
    在这里插入图片描述
    我们可以看到,只能修改传进去的参数。

  6. =,&a,&b 。除a、b按引用进行传递,其他参数按值进行传递。

  7. &,a,b。除a、b按值进行传递,其他参数按引用进行传递。

操作符重载函数参数
没有参数时可以省略,并且参数也可以通过值或者引用两种方式进行传递。

可修改标识符:mutable
按值传递函数对象参数时,加上mutable可以修改按值传递进来的拷贝(不是值本体)。
在这里插入图片描述
如果我们定义一个m变量,想在函数体内修改的话,不加mutable修饰是无法修改的。
正确代码如下:

 QPushButton *anNiu2 = new QPushButton(this);
    anNiu2->move(50,50);
    int m = 10;
    connect(anNiu,&QPushButton::clicked,this,[m]()mutable {m = m+100; qDebug()<<m<<endl;});
    connect(anNiu2,&QPushButton::clicked,this,[=](){qDebug()<<m<<endl;});
    qDebug()<<m<<endl;

在这里插入图片描述
分别点击第一个和第二个按钮,结果为:110 10
在这里插入图片描述
我们可以发现,值传递是不能修改本体的。

Lambda表达式的使用

1.我们可以用λ表达式实现关闭窗口功能。
在这里插入图片描述
我们先通过 = 实现调用所有成员,接着调用Widget下的close函数实现关闭窗口功能。
2.我们可以修改之前的 信号连接信号 ,用λ表达式实现信号重载有参数的实现。
我们有两种版本的信号以及槽函数:有参和无参。并且都对应连接起来了。
在这里插入图片描述
在这里插入图片描述
接下来我们需要实现一个按钮打印无参版,一个打印有参版。
当然无参版本我们只需要把按钮点击的信号与老师饿了的信号连起来就行。
在这里插入图片描述

结果如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值