Qt界面编程(三)—— 父子关系、对象树、信号和槽(自定义信号和槽、Qt5与Qt4的写法)

一、Qt按钮小程序

1.  按钮的创建和父子关系

        在Qt程序中,最常用的控件之一就是按钮了,首先我们来看下如何创建一个按钮:

#include <QPushButton>

    QPushButton * btn = new QPushButton; 
    //设置父亲
    btn->setParent(this);
    //设置文字
    btn->setText("德玛西亚");
    //移动位置
    btn->move(100,100);

    //第二种创建
    QPushButton * btn2 = new QPushButton("孙悟空",this);
    //重新指定窗口大小
    this->resize(600,400);

    //设置窗口标题
    this->setWindowTitle("第一个项目");

    //限制窗口大小
    this->setFixedSize(600,400);

       上面代码中,一个按钮其实就是一个QPushButton类的对象,如果只是创建出对象,是无法显示到窗口中的,所以我们需要依赖一个父窗口,也就是指定一个父亲,利用setParent函数或者按钮创建的时候通过构造函数传参,此时我们称两个窗口建立了父子关系。在有父窗口的情况下,窗口调用show会显示在父窗口中如果没有父窗口,那么窗口调用show显示的会是一个顶层的窗口(顶层窗口是能够在任务栏中找到的,不依赖于任何一个窗口而独立存在)(按钮也是继承于QWidget,也属于窗口)。

        如果想设置按钮上显示的文字可以用setText,移动按钮位置用move。

        对于窗口而言,我们可以修改左上角窗口的标题setWindowTitle,重新指定窗口大小:resize,或者设置固定的窗口大小setFixedSize。 

2. Qt窗口坐标体系   

        通过以上代码可以看出Qt的坐标体系。以左上角为原点(0,0),以向右的方向为x轴的正方向,以向下方向为y轴的正方向。如下图所示:

         对于嵌套窗口,其坐标是相对于父窗口来说的。顶层窗口的父窗口就是屏幕。

3. 对象树模型 

        QObject是Qt里边绝大部分类的根类(基类)。

  1. QObject对象之间是以对象树的形式组织起来的。
    1. 当两个QObject(或子类)的对象建立了父子关系的时候。子对象就会加入到父对象的一个成员变量叫children(孩子)的list(列表)中。
    2. 父对象析构的时候这个列表中的所有对象也会被析构(注意,这里是说父对象和子对象,不要理解成父类和子类)
  2. QWidget是能够在屏幕上显示的一切组件的父类
    1. QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。我们向某个窗口中添加了一个按钮或者其他控件(建立父子关系),当用户关闭这个窗口的时候,该窗口就会被析构,之前添加到他上边的按钮和其他控件也会被一同析构。这个结果也是我们开发人员所期望的。
    2. 当然,我们也可以手动删除子对象。当子对象析构的时候会发出一个信号destroyed,父对象收到这个信号之后就会从children列表中将它剔除。比如,当我们删除了一个按钮时,其所在的主窗口会自动将该按钮从其子对象列表(children)中删除,并且自动调整屏幕显示,按钮在屏幕上消失。当这个窗口析构的时候,children列表里边已经没有这个按钮子对象,所以我们手动删除也不会引起程序错误。

Qt 引入对象树的概念,在一定程度上解决了内存问题。

  1. 对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
  2. 任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。

如果QObject在栈上创建,Qt 保持同样的行为。正常情况下,这也不会发生什么问题。来看下下面的代码片段:

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

作为父组件的 window 和作为子组件的 quit 都是QObject的子类(事实上,它们都是QWidget的子类,而QWidget是QObject的子类)。这段代码是正确的,quit 的析构函数不会被调用两次,因为标准 C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作用域时,会先调用 quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数。

但是,如果我们使用下面的代码:

{
    QPushButton quit("Quit");
    QWidget window;
    quit.setParent(&window);
}

情况又有所不同,析构顺序就有了问题。我们看到,在上面的代码中,作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。

由此我们看到,Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以,我们最好从开始就养成良好习惯,在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。

二、信号和槽机制

信号:各种事件

槽: 响应信号的动作

当某个事件发生后,如某个按钮被点击了一下,它就会发出一个被点击的信号(signal

某个对象接收到这个信号之后,就会做一些相关的处理动作(称为槽slot)。

但是Qt对象不会无故收到某个信号,要想让一个对象收到另一个对象发出的信号,这时候需要建立连接(connect

1. 系统自带的信号和槽

下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?

其实两行代码就可以搞定了,我们看下面的代码:

QPushButton * quitBtn = new QPushButton("关闭窗口",this);
connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);

第一行是创建一个关闭按钮,这个之前已经学过,第二行就是核心了,也就是信号槽的使用方式。

connect函数是建立信号发送者、信号、信号接收者、槽四者关系的函数:

connect(sender, signal, receiver, slot);

参数解释:

  1. sender:信号发送者
  2. signal:信号
  3. receiver:信号接收者
  4. slot:接收对象在接收到信号之后所需要调用的函数(槽函数)

这里要注意的是connect的四个参数都是指针,信号和槽是函数指针

系统自带的信号和槽如何查找呢,这个就需要利用帮助文档了,在帮助文档中比如我们上面的按钮的点击信号,在帮助文档中输入QPushButton,首先我们可以在Contents中寻找关键字 signals,信号的意思,但是我们发现并没有找到,这时候我们应该想到也许这个信号的被父类继承下来的,因此我们去他的父类QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号有如下几个 

 这里的clicked就是我们要找到,槽函数的寻找方式和信号一样,只不过他的关键字是slot。

2. 自定义信号和槽

        Qt框架默认提供的标准信号和槽不足以完成我们日常应用开发的需求,比如说点击某个按钮让另一个按钮的文字改变,这时候标准信号和槽就没有提供这样的函数。但是Qt信号和槽机制提供了允许我们自己设计自己的信号和槽。 

2.1 自定义信号使用条件

  1. 声明在类的signals域下
  2. 没有返回值,void类型的函数
  3. 只有函数声明,没有定义
  4. 可以有参数,可以重载
  5. 通过emit关键字来触发信号,形式:emit object->sig(参数);

2.2 自定义槽函数使用条件

  1. qt4 必须声明在 private/public/protected slots域下面,qt5之后可以声明public下,同时还可以是静态的成员函数,全局函数,lambda表达式
  2. 没有返回值,void类型的函数
  3. 不仅有声明,还得要有实现
  4. 可以有参数,可以重载

2.3 使用自定义信号和槽

        定义场景:下课了,老师跟同学说肚子饿了(信号),学生请老师吃饭(槽)

        首先定义一个学生类和老师类:

        老师类中声明信号 饿了 hungry

signals:
       void hungry();

        学生类中声明槽 请客treat

public slots:
       void treat();

        在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客

void MyWidget::ClassIsOver()
{
    //发送信号
    emit teacher->hungry();
}
学生响应了槽函数,并且打印信息
//自定义槽函数 实现
void Student::treat()
{
       qDebug() << "Student treat teacher";
}

在窗口中连接信号槽

teacher = new Teacher(this);
student = new Student(this);
connect(teacher,&Teacher::hungury,student,&Student::treat);

并且调用下课函数,测试打印出相应log

自定义的信号 hungry带参数,需要提供重载的自定义信号和 自定义槽

void hungry(QString name);  // 自定义信号
void treat(QString name );   // 自定义槽
// 但是由于有两个重名的自定义信号和自定义的槽,直接连接会报错,
// 所以需要利用函数指针来指向函数地址, 然后在做连接
void (Teacher:: * teacherSingal)(QString) = &Teacher:: hangry;
void (Student:: * studentSlot)(QString) = &Student::treat;
connect(teacher,teacherSingal,student,studentSlot);
// 也可以使用static_cast静态转换挑选我们要的函数
connect(
teacher,
static_cast<void(Teacher:: *)(QString)>(&Teacher:: hangry),
student,
static_cast<void(Student:: *)(QString)>(& Student::treat));

3. 信号和槽的拓展

  1. 一个信号可以和多个槽相连

如果是这种情况,这些槽会一个接一个的被调用,但是槽函数调用顺序是不确定的。像上面的例子,可以将一个按钮点击信号连接到关闭窗口的槽函数,同时也连接到学生请吃饭的槽函数,点击按钮的时候可以看到关闭窗口的同时也学生请吃饭的log也打印出来。

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

只要任意一个信号发出,这个槽就会被调用。如:一个窗口多个按钮都可以关闭这个窗口。

  1. 一个信号可以连接到另外的一个信号

当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。注意这里还是使用connect函数,只是信号的接收者和槽函数换成另一个信号的发送者和信号函数。如上面老师饿了的例子,可以新建一个按钮btn。

connect(btn,&QPushButton::clicked,teacher,&Teacher::hungry);
  1. 信号和槽可以断开连接

可以使用disconnect函数,当初建立连接时connect参数怎么填的,disconnect里边4个参数也就怎么填。这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽

  1. 信号和槽函数参数类型和个数必须同时满足两个条件
  1. 信号函数的参数个数必须大于等于槽函数的参数个数
  2. 信号函数的参数类型和槽函数的参数类型必须一一对应

4. Qt4版本的信号槽写法

connect(
    teacher,
    SIGNAL(hungry(QString)),
    student,
    SLOT(treat(QString))
);

这里使用了SIGNALSLOT这两个宏,宏的参数是信号函数和槽函数的函数原型。

因为直接填入了函数原型,所有这里边编译不会出现因为重载导致的函数指针二义性的问题。但问题是如果函数原型填错了,或者不符合信号槽传参个数类型约定,编译期间也不会报错,只有运行期间才会看到错误log输出。

原因就是这两个宏将后边参数(函数原型)转化成了字符串。目前编译器还没有那么智能去判断字符串里边的内容符不符合运行条件。

5. Lambda表达式 

C++11中的Lambda表达式用于定义匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:

分为四个部分:[局部变量捕获列表]、(函数参数)、函数额外属性设置opt、函数返回值->retype、{函数主体}

[capture](parameters) opt ->retType
{
    ……;
}

5.1 局部变量引入方式

[ ],标识一个Lambda的开始。由于lambda表达式可以定义在某一个函数体A里边,所以lambda表达式有可能会去访问A函数中的局部变量。中括号里边内容是描述了在lambda表达式里边可以使用的外部局部变量的列表:

    1. []

    表示lambda表达式不能访问外部函数体的任何局部变量

    1. [a]

    在函数体内部使用值传递的方式访问a变量

    1. [&b]

    在函数体内部使用引用传递的方式访问b变量

    1. [=]

    函数外的所有局部变量都通过值传递的方式使用, 函数体内使用的是副本

    1. [&]

    引用的方式使用lambda表达式外部的所有变量

    1. [=, &foo]

    foo使用引用方式, 其余是值传递的方式

    1. [&,foo]

    foo使用值传递方式, 其余是引用传递的方式

    1.  [this]

在函数内部可以使用类的成员函数和成员变量,=和&形式也都会默认引入

由于引用方式捕获对象会有局部变量释放了而lambda函数还没有被调用的情况。如果执行lambda函数那么引用传递方式捕获进来的局部变量的值不可预知。

所以在无特殊情况下建议使用[=](){}的形式

5.2 函数参数

        (params)表示lambda函数对象接收的参数,类似于函数定义中的小括号表示函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引用(如:(int &a,int &b))两种方式进行传递。函数参数部分可以省略,省略后相当于无参的函数。

5.3 选项Opt

        Opt 部分是可选项,最常用的是mutable声明,这部分可以省略。外部函数局部变量通过值传递引进来时,其默认是const,所以不能修改这个局部变量的拷贝,加上mutable就可以        

int a = 10 ;
    [=]()
    {
        a=20;//编译报错,a引进来是const
}
[=]()mutable
    {
        a=20;//编译成功
};

5.4 函数返回值 ->retType

        ->retType,标识lambda函数返回值的类型。这部分可以省略,但是省略了并不代表函数没有返回值编译器会自动根据函数体内的return语句判断返回值类型,但是如果有多条return语句,而且返回的类型都不一样,编译会报错,如:

[=]()mutable
{
        int b = 20;
        float c = 30.0;
        if(a>0)
            return b;
        else
            return c;//编译报错,两条return语句返回类型不一致
};

5.5 是函数主体{}

        {},标识函数的实现,这部分不能省略,但函数体可以为空。

5.6 槽函数使用Lambda表达式

        以QPushButton点击事件为例:

connect(btn,&QPushButton::clicked,[=](){
        qDebug()<<"Clicked";
});

这里可以看出使用Lambda表达式作为槽的时候不需要填入信号的接收者。当点击按钮的时候,clicked信号被触发,lambda表达式也会直接运行。当然lambda表达式还可以指定函数参数,这样也就能够接收到信号函数传递过来的参数了。

由于lambda表达式比我们自己自定义槽函数要方便而且灵活得多,所以在实现槽函数的时候优先考虑使用Lambda表达式。一般我们的使用习惯也是lambda表达式外部函数的局部变量全部通过值传递捕获进来,也就是:

        [=](){  }的形式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/7372.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

接口测试-postman使用总结

一、为何使用postman postman是一款简单高效的接口测试工具&#xff0c;能够很方便发送接口请求&#xff0c;易于保存接口请求脚本&#xff0c;postman提供接口响应数据比对功能&#xff0c;可以设置预期结果作断言&#xff0c;还能把测试用例放在一个集合中批量执行&#xff…

【JavaWeb】9—监听器

⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star~&#x1f60a; 如果文章对你有所帮助&#xff0c;可以点赞&#x1f44d;…

torchvision.transforms 常用方法解析(含图例代码以及参数解释)

本文代码和图片完全源于 官方文档: TRANSFORMING AND AUGMENTING IMAGES 中的 Illustration of transforms&#xff0c;参数介绍源自函数对应的官方文档。 代码中的变换仅仅使用了最简单的参数&#xff1a;pad&#xff0c;size 等&#xff0c;这里展现的只是简单的变换&#xf…

C/C++每日一练(20230408)

目录 1. 删除无效的括号 &#x1f31f;&#x1f31f;&#x1f31f; 2. 合并K个升序链表 &#x1f31f;&#x1f31f;&#x1f31f; 3. 四数之和 &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Python每日一练 专栏 …

SQL Server用户定义的函数(UDF)使用详解

SQL Server用户定义的函数一、背景知识1.1、用户定义函数的优点1.2、函数类型1.3、指引1.4、函数中的有效语句1.5、架构绑定函数1.6、指定参数二、创建用户定义函数2.1、限制和权限2.2、标量函数示例&#xff08;标量 UDF&#xff09;2.3、表值函数示例2.3.1、内联表值函数 &am…

项目管理软件调度的优势有哪些?

如果没有项目时间表&#xff0c;要跟踪在何时以及必须使用哪些资源之前需要完成什么是非常困难和耗时的。时间表是一个时间表&#xff0c;它概述了所有项目任务和需要完成的里程碑的开始和结束日期。 项目进度中的任务将具有依赖性&#xff0c;这意味着如果完成数据在一项活动上…

Redis7高级之Redlock算法和Redisson的使用(十)

10.1 Redlock 红锁算法 1.解决手写分布式锁的单点故障问题 Redis 提供了 Redlock 算法&#xff0c;用来实现基于多个实例的分布式锁锁变量由多个实例维护&#xff0c;即使有实例发生了故障&#xff0c;锁变量仍然是存在的&#xff0c;客户端还是可以完成锁操作Redlock算法是实…

计算机网络考试复习——第一章 1.5 1.6

1.5 计算机网络的类别 1.5.1计算机网络的定义&#xff1a; 系统集合&#xff0c;连接起来&#xff0c;协议工作&#xff0c;资源共享 计算机网络主要是由一些通用的、可编程的硬件互连而成的&#xff0c;而这些硬件并非专门用来实现某一特定目的&#xff08;例如&#xff0…

Java源码(一)ThreadLocal、SpringBoot Jar 启动原理

思维导图 一、ThreadLocal 1.场景 项目采用SSMShiro登录认证&#xff0c;改造需求如下&#xff1a; 后台管理员登录需要限制&#xff0c;同一个用户的不同IP需要通过过自定义验证后才能登录。 2.问题 在完成需求后发现有管理员用户&#xff08;这里就用A&#xff09;通过验…

Android build.gradle配置详解

Android Studio是采用gradle来构建项目的&#xff0c;gradle是基于groovy语言的&#xff0c;如果只是用它构建普通Android项目的话&#xff0c;是可以不去学groovy的。当我们创建一个Android项目时会包含两个Android build.gradle配置详解文件&#xff0c;如下图&#xff1a; 一…

区块链3链(TRC ERC BSC)授权持币生息源码

分享一款3链&#xff08;TRC ERC BSC&#xff09;授权持币生息源码、来自群友投稿的资源、据说是运营级的。简单的看了下没有问题什么大问题、有能力的可以拿来二开其他的模板。 搭建非常简单&#xff0c;教程就不写了、环境NGINX1.2PHP7.2MYSQL5.6TP默认伪静态 此类源码需要…

【Python】数学 - 用 Python 自动化求解函数 f(x) 的值

目录 1、缘起 2、求以下函数的值 3、代码清单 3.1、求解 f(0)、f(1)、 f(​编辑)、f(​编辑) 3.2、求解 g(0)、g(1)、g(​编辑)、g(​编辑) 3.3、求解 h(0)、h(1)、h(​编辑)、h(​编辑) 4、总结 1、缘起 Python 是一种强大的编程语言&#xff0c;它具有广泛的应用领域。…

Python模拟星空

文章目录前言Turtle基础1.1 Turtle画板1.2 Turtle画笔1.3 Turtle画图1.4 Turtle填色1.5 Turtle写字模拟星空模拟星球浪漫星空尾声前言 Python模拟星空&#xff0c;你值得拥有&#xff01;uu们一周不见啦&#xff0c;本周博主参考网上大佬们的星空&#xff0c;给大家带来了属于…

C语言操作符优先级

在平时写代码时&#xff0c;经常会用到操作符&#xff0c;但是如果不了解这些操作符的优先级&#xff0c;可能会让程序的执行效果和我们预期的不一样。 例如&#xff1a; int a 2;int b 3;int c 4;//int ret a b * c;//我们想要执行的顺序是ab的值再乘c//如果了解操作符优…

chat GPT人工智能写论文-怎么用chatGpt写论文

用chatGPT写文章会重复吗 使用 ChatGPT 写文章可能会出现重复的情况。因为 ChatGPT 是基于机器学习的自然语言处理技术&#xff0c;它并不具备人类的创造性思维&#xff0c;其生成的文本内容是基于已有语言数据的统计模型而产生的。 当输入信息重复、语言结构复杂或指定主题较…

【测试】《软件测试》阅读总结

第一章 软件测试的流程是什么&#xff1f; 需求分析--------测试计划----------测试开发--------测试执行-------测试报告 如何描述一个BUG 版本&#xff0c;测试环境、测试步骤和测试数据、实际结果、预期结果、附件&#xff08;截图、错误日志&#xff09; 软件测试过程包括…

HashMap,HashTable和ConcurrentHashMap之间有什么区别?

前言 在之前HashMap的学习中,我们可以知道HashMap是线程不安全的数据结构,它存储的一般是数据的键值对(Key-Value模型),其中Key允许为null,它底层是数组链表的实现,当单个链表的数据元素过多时,会转变为红黑树,在多线程环境下,对某个HashMap对象进行操作,是无法保证线程安全的,…

代理服务器与CDN的概念

代理服务器 特点&#xff1a;本身不产生内容&#xff0c;处于中间位置转发上下游的请求和响应 面向下游的客户端&#xff1a;它是服务器面向上游的服务器&#xff1a;它是客户端 正向代理&#xff1a;代理的对象是客户端 隐藏客户端身份绕过防火墙&#xff08;突破访问限制&am…

今天面了一个来京东要求月薪25K,明显感觉他背了很多面试题...

最近有朋友去京东面试&#xff0c;面试前后进行了20天左右&#xff0c;包含4轮电话面试、1轮笔试、1轮主管视频面试、1轮hr视频面试。 据他所说&#xff0c;80%的人都会栽在第一轮面试&#xff0c;要不是他面试前做足准备&#xff0c;估计都坚持不完后面几轮面试。 其实&…

LeetCode-146. LRU 缓存

目录LRU理论题目思路代码实现一代码实现二题目来源 146. LRU 缓存 LRU理论 LRU 是 Least Recently Used 的缩写&#xff0c;这种算法认为最近使用的数据是热门数据&#xff0c;下一次很大概率将会再次被使用。而最近很少被使用的数据&#xff0c;很大概率下一次不再用到。当缓…