QT入门-信号与槽

1.QT基本框架

#include "myWindow.h"
 
#include <QApplication>
 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    myWindow w;
    w.show();
    return a.exec();
}

QApplicata:应用程序对象,必须有且只能有一个

Qwidget:qt自带空窗口类,即运行时的窗口。

myWindow:自定义窗口类,继承自QWidget。

a.exec():进入消息循环,除非人为结束进程,否则阻塞在这里等待用户输入,死循环。

qmake:qt的编译器。

.pro文件:qt的项目文件。

 拖动按钮到窗口中,然后右键转到槽,自定义实现功能

#include "mainwindow.h"
#include "ui_mainwindow.h"


#include<QMessageBox>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::on_pushButton_clicked()
{
//this表示设置父对象为MainWindow
    QMessageBox::information(this,"hello","my first qt application!");
}

运行:
 

2.对象树

对象树是qt中非常重要的内容,主要用于自动析构对象。

在qt中,我们自定义的类可以继承自qt中已有的类。比如上述myWindow就是继承自QWidget。

QWidget又是继承自QObject。

在qt中QObject是所有类的祖先,向下生成了许多子类,这样一个关系就叫做对象树。

 (注意这里的父子关系并不是语法里面的父子继承关系,而是qt里面的一种机制)

使用时,需要通过setParent函数将对象间确定父子关系,之后子对象会进入父对象的children列表。 

当析构时,会先从父对象开始,先走父对象析构函数(注意,只是析构没有真正释放),然后依旧children列表析构子对象,直到走到叶子对象后,释放叶子对象,再返回父对象释放,直到回到一开始的父对象。

当释放一个对象时,会把它所有的子对象都释放掉,因此,之后不能再去释放子对象,否则会发生二次释放的错误。

错误代码1:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    myWidget * c = new myWidget;
    QWidget * s = new QWidget;
    c->setParent(s);//s是c的父对象
    delete s;//删释放s同时也会将c释放
    delete c;//错误,二次释放
    return 0;
}

更改:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    myWidget * c = new myWidget;
    QWidget * s = new QWidget;
    c->setParent(s);//s是c的父对象
    delete s;//删释放s同时也会将c释放
    return 0;
}

错误代码2:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    myWidget c;
    QWidget s;// s后定义,结束时先析构
    c.setParent(&s);//s为父,c为子
    return 0;
    //发生错误,s先析构会把c也析构,
    //之后c调用析构函数时会二次析构
}

更改:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget s;//s析构子对象时发现c已经被析构了,就不会在析构了,这是qt中实现的
    myWidget c;
    c.setParent(&s);
    return 0;
}

防止二次析构的实现:

Qt 并不直接使用智能指针来管理对象树中的对象生命周期(如 std::shared_ptrQSharedPointer),但它的机制类似于引用计数。每个 QObject 都知道有多少子对象以及它们是谁,而子对象知道它们的父对象是谁。当对象被删除时,它会通知其父对象和子对象调整它们的指针和列表

3.信号与槽

1.1 信号和槽

1,信号和槽(Signal&Slot)

  • 信号与槽(Signal&Slot)是Qt的基础,也是Qt的一大创新,因为有了信号与槽的编程机制,在Qt中处理 界面各组件的交互操作时,变得更加直观和简单,它可以让app开发人员把互不了解的对象绑定在一起

2,信号(Signal)

  • 信号就是在特定情况下被发送的事件,eg:PushButton最常见的信号就是鼠标单击时发送的clicked()信号,一个ComboBox最常见的信号是选择的列表项变化时发送的CurrentIndexChanged()信号。
  • GUI程序设计主要就是对界面各组件的信号相应,只需要知道什么情况下发送哪些信号,合理去响应和处理这些信号即可。

3,槽(Slot)

  • 槽就是对信号的响应函数,与一般的C++函数一样,可以定义在类的public,private,protect,可以具有任何参数,也可以直接被调用
  • 槽函数与一般函数不同的是,槽函数可以与一个信号关联,当信号被发送,关联的槽函数被自动执行
  • 信号和槽关联是用,QObject.connect()函数实现的,基本格式为:
QObject::connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
参数1:信号的发送者
参数2:发送的信号
参数3:信号的接收者
参数4:处理函数(槽函数)

实例:信号与槽示例QPushButton

信号

1.我们打开Qt助手,搜索QPushButton,发现没有signal,只有个public slot

2.找QPushButton的父类,QAbstractButton,找到signals 

3.可以看到里面有4个信号

槽函数:

1.自动生成的槽函数一般都是定义在窗口类中的

2.同时,我们使用的很多槽函数都是定义在QWidge(MainWindow的父类)中的

代码实现:

#include "mainwindow.h"
#include "ui_mainwindow.h"


#include<QMessageBox>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    //由于connect时QOBJECT下的成员函数,而QWidget继承于QOBJECT,因此阔以直接使用
    connect(ui->mybutton,&QPushButton::clicked,this,&QWidget::close);
}

MainWindow::~MainWindow()
{
    delete ui;
}

//使用
void MainWindow::on_pushButton_clicked()
{
    QMessageBox::information(this,"hello","my first qt application!");
//    QWidget::close();
}

注意

1:

通过ui界面拖动的组件,定义在ui对象中,要想使用就通过ui->来调用

private:
    Ui::MainWindow *ui;

 2:
信号和槽传入的指针而不是函数,下面的写法是错的:

    connect(ui->mybutton,&QPushButton::clicked(),this,&QWidget::close());

 3:一个信号可以触发多个槽,这里就将ui定义的组件重新connect到close上了

结果:点击按键后窗口关闭

 1.2 自定义信号与槽

 在上面的工程添加类

定义两个类 

 

 1.signal

头文件如下: 

#ifndef MYSIGNAL_H
#define MYSIGNAL_H

#include<QObject>
//为signals添加头文件
class mysignal : public QObject
{
    Q_OBJECT
public:
    explicit mysignal(QObject *parent = nullptr);
signals:
    void send_signal();
};

#endif // MYSIGNAL_H

signal是一个宏,不是c++语法内容,我们定义一个信号后,不需要实现,直接通过

//emit是发送信号的标识,不写会报警告,版本问题,(非必须)
 emit obj.send_signal();

 就能实现信号的发送,因此,cpp文件不用写

2.slot

头文件如下: 

#ifndef MYSLOT_H
#define MYSLOT_H
#include<QObject>


class myslot : public QObject//要写上public
{
    Q_OBJECT
public:
    explicit myslot(QObject *parent = nullptr);//防止隐式转换的写法
signals:
public slots:
    void ack();//需要实现
};

#endif // MYSLOT_H

注意slots的格式

cpp文件:

#include "myslot.h"
#include<QDebug>
myslot::myslot(QObject * parent) : QObject(parent)
{

}

void myslot::ack()
{
    qDebug()<<"recv!!!";
}

3.main

#include "mainwindow.h"

#include <QApplication>
#include <QLocale>
#include <QTranslator>
#include <mysignal.h>
#include <myslot.h>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTranslator translator;
    const QStringList uiLanguages = QLocale::system().uiLanguages();
    for (const QString &locale : uiLanguages) {
        const QString baseName = "myfirst_qt_" + QLocale(locale).name();
        if (translator.load(":/i18n/" + baseName)) {
            a.installTranslator(&translator);
            break;
        }
    }
    MainWindow w;

    mysignal s(&w);
    myslot r(&w);

    w.connect(&s,&mysignal::send_signal,&r,&myslot::ack);
    emit s.send_signal();

    w.show();
    return a.exec();
}

效果:

1.3 信号与槽函数的重载

1.直接重载

报错如下:

2.两种通过函数指针实现重载的方式

 1.普通函数指针
返回类型 (*指针名称)(参数类型列表);

 案例:

int add(int x, int y) {
    return x + y;
}

int main() {
    // 声明一个指向函数的指针
    int (*funcPtr)(int, int) = &add;

    // 使用函数指针调用函数
    int result = funcPtr(3, 4); // 等同于 add(3, 4)

    return 0;
}
2.类成员函数指针 

 类如下:

class Calculator {
public:
    int add(int x, int y) {
        return x + y;
    }
};

实现:

int main() {
    Calculator calc;

    // 声明一个指向成员函数的指针
    int (Calculator::*calcPtr)(int, int) = &Calculator::add;

    // 使用成员函数指针调用函数
    int result = (calc.*calcPtr)(5, 6); // 等同于 calc.add(5, 6)

    return 0;
}
 方法一:
    void (mysignal::*send_str)(QString&) = &mysignal::send_signal;
    void (myslot::*ack_str)(QString&) = &myslot::ack;

    w.connect(&s,send_str,&r,ack_str);

先定义一个带指定参数的函数指针,然后指向对应的信号或者槽,由于参数类型确定,那么会自动给函数指针匹配合适的重载函数

方法二:
w.connect(&s,(void(mysignal::*)(QString&))&mysignal::send_signal,&r,(void(myslot::*)(QString&))&myslot::ack);

节约行数,但是可读性差,不推荐 

核心:注意信号重载函数指针指向了哪一个函数,对重载函数的信号连接时,要指明到底连接的是哪一个槽函数

效果:

 mian代码:

#include "mainwindow.h"

#include <QApplication>
#include <QLocale>
#include <QTranslator>
#include <mysignal.h>
#include <myslot.h>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QTranslator translator;
    const QStringList uiLanguages = QLocale::system().uiLanguages();
    for (const QString &locale : uiLanguages) {
        const QString baseName = "myfirst_qt_" + QLocale(locale).name();
        if (translator.load(":/i18n/" + baseName)) {
            a.installTranslator(&translator);
            break;
        }
    }
    MainWindow w;

    mysignal s(&w);
    myslot r(&w);

//    w.connect(&s,&mysignal::send_signal,&r,&myslot::ack);
#if 0
    void (mysignal::*send_str)(QString&) = &mysignal::send_signal;
    void (myslot::*ack_str)(QString&) = &myslot::ack;

    w.connect(&s,send_str,&r,ack_str);
#else
    
    w.connect(&s,(void(mysignal::*)(QString&))&mysignal::send_signal,&r,(void(myslot::*)(QString&))&myslot::ack);
#endif


    emit s.send_signal();
    QString str = "jjj";
    emit s.send_signal(str);
    w.show();
    return a.exec();
}

 1.4 信号和槽的扩展

1.信号链接信号

 定义一个按键,先按键触发信号,然后信号触发槽

    mysignal s(&w);
    myslot r(&w);
    QPushButton bnt;
    bnt.setParent(&w);
    bnt.setText("触发信号");
//    w.connect(&s,&mysignal::send_signal,&r,&myslot::ack);
#if 1
    void (mysignal::*send_str)() = &mysignal::send_signal;
    void (myslot::*ack_str)() = &myslot::ack;
    void (mysignal::*send_str1)() = &mysignal::send_signal;
    w.connect(&bnt,&QPushButton::clicked,&s,send_str1);
    w.connect(&s,send_str,&r,ack_str);
//    emit s.send_signal();
//    QString str = "jjj";
//    emit s.send_signal(str);
    w.show();
    return a.exec();

效果:

点击后触发槽函数:

即:

信号连接信号,一个消息信号可以绑定多个槽,同样的,一个槽函数也可以绑定多个消息信号。(多对多关系

2.在ui函数中使用自定义变量

 1.如果变量定义在外面,无法进行connect:

2.如果变量定义在里面:
 

由于时局部变量,无法长期维持, mysignalmyslot 的实例 sr 是在 MainWindow 构造函数的局部作用域中创建的。这意味着它们只在构造函数执行期间存在,一旦构造函数执行完毕,这些局部变量就会被销毁。因此,当您点击按钮试图触发信号时,sr 的实例可能已经不存在了,导致信号无法被成功触发或接收。

如果执意运行,会发现点击按钮无响应(s,r早被销毁了) 

3.使用成员函数:

效果:

3.信号的断开-disconnect()

    connect(ui->mybutton,&QPushButton::clicked,&s,send_str1);
    connect(&s,send_str,&r,ack_str);
    disconnect(&s,send_str,&r,ack_str);

 效果:

点击无反应(连接已经断开) 

 1.5 lambda辅助槽函数

1.为何要用lambda 

有时候槽函数的内容不简单,但是需要在各处定义槽函数和其实现,那么全部重写很麻烦,由此引入lambda的使用

直接在要用槽函数的地方(connect处),把函数整个传入当作参数,这样就不用先定义再实现再调用了,简便很多

比如下例:

    connect(ui->bnt1,&QPushButton::clicked,this,[this]()mutable{a+=10;qDebug()<<a;});

简单的功能直接使用lambda实现,避免了重复的简单操作 

2.lambda解析

  • 捕获列表。在C++规范中也称为Lambda导入器, 捕获列表总是出现在Lambda函数的开始处。实际上,[]是Lambda引出符。编译器根据该引出符判断接下来的代码是否是Lambda函数,捕捉上下文中的变量以供Lambda函数使用。(能够使用的变量,不是传入参数)
    • []中括号函数对象里面对应参数有以下形式:
      空,没有使用任何函数对象参数
      =,函数体内使用Lambda所在范围内的可见局部变量,包括所在类的this的传值方式,相当于编译器给Lambda所在范围的所有局部变量**赋值**一份给Lambda函数
      &,函数体内使用Lambda所在范围内的可见局部变量,包括所在类的this的引用方式,相当于编译器给Lambda所在范围的所有局部变量**引用**一份给Lambda函数
      this,函数体内可以使用Lambda所在内的成员变量
      a,不是字母,而是指具体一个变量a,Lambda内拷贝一个变量a使用
      &a,Lambda内引用变量a
      a, &b,拷贝a,引用b
      =,&a,&b,除a,b引用外,其他变量做拷贝操作
      &,a,b,除a,b拷贝外,其他变量做引用操作
  • 参数列表与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号“()”一起省略。
  • 可变规格。mutable修饰符, 默认情况下Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空)使用后我们就可以改变捕获列表中变量的值
  • 异常说明。用于Lamdba表达式内部函数抛出异常。
  • 返回类型。 追踪返回类型形式声明函数的返回类型。我们可以在不需要返回值的时候也可以连同符号”->”一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导。
  • lambda函数体内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。

 3.lambda在QT中的使用-无参数的信号调用有参数的槽函数

在上文代码的基础上实现以下连接 

    connect(ui->mybutton,&QPushButton::clicked,&s,send_str1);
//    connect(&s,send_str,&r,ack_str);
    QString str = "hello";
    //注意QString& 不能接受const char字符串,因此这里将前面的ack参数改为了QString
    connect(&s,send_str,&r,[=](){r.ack("aaaaaaa");});

 效果:

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

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

相关文章

过河卒(洛谷)

题目 原题 题目描述 棋盘上 A A A 点有一个过河卒&#xff0c;需要走到目标 B B B 点。卒行走的规则&#xff1a;可以向下、或者向右。同时在棋盘上 C C C 点有一个对方的马&#xff0c;该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。…

3秒实现无痛基于Stable Diffusion WebUI安装ComfyUI!无需重复安装环境!无需重复下载模型!安装教程

标题略有夸张的表达了接下来这一套确实很简单&#xff0c;相较于直接下载或者通过秋叶包更新而言。大大节省磁盘空间&#xff0c;和下载时间。 这篇教程不需要你有&#xff1a; 代码基础。都是复制粘贴就完事。魔法。 这篇教程默认你已经有&#xff1a; 1. 本地能够正常使用…

汽车出租管理系统

文章目录 汽车出租管理系统一、系统演示二、项目介绍三、系统部分功能截图四、部分代码展示五、底部获取项目源码&#xff08;9.9&#xffe5;带走&#xff09; 汽车出租管理系统 一、系统演示 汽车租赁系统 二、项目介绍 语言&#xff1a;java 框架&#xff1a;SpringBoot、…

【2024年数据】67个“绿色金融”主题DID政策汇总(已去重)

DID”发文趋势和主题分布 数据来源&#xff1a;中国知网、各期刊官网 时间跨度&#xff1a;2017-2024年 数据范围&#xff1a;中国各省 数据指标&#xff1a; 序号 用于构建DID的政策 文献标题 1 “宽带中国” 数字技术创新与中国企业高质量发展——来自企业数字专利的证据…

陶陶摘苹果C++

题目&#xff1a; 代码&#xff1a; #include<iostream> using namespace std; int main(){//一、分析问题//已知&#xff1a;10 个苹果到地面的高度a[10],陶陶把手伸直的时候能够达到的最大高度height//未知&#xff1a;陶陶能够摘到的苹果的数目sum。//关系&#xff…

ncc匹配提速总结

我们ncc最原始的匹配方法是&#xff1a;学习模板w*h个像素都要带入ncc公式计算 第一种提速&#xff0c;学习模板是w*h&#xff0c;而我们支取其中的w/2*h/2,匹配窗口同理&#xff0c;计算量只有1/4。 另外一种因为ncc是线性匹配&#xff0c;我们在这上面也做了文章&#xff0…

ROS URDF、rviz、gazebo(1)

文章目录 1.URDF优化_xacro:1.1.xacro语法&#xff1a;1.2.xacro之launch集成&#xff1a; 2.arbotix控制机器人运动:2.1.编写arbotix配置文件&#xff1a;2.2.配置launch文件&#xff1a;2.3.向话题发布信息&#xff1a; 3.URDF集成gazebo&#xff1a;3.1.创建功能包并导入相关…

专业140+总分410+华南理工大学811信号与系统考研经验华工电子信息与通信,真题,大纲,参考书。

23考研已经落幕&#xff0c;我也成功的上岸华工&#xff0c;回首这一年多的历程&#xff0c;也是有一些经验想和大家分享一下。 首先说一下个人情况&#xff0c;本科211&#xff0c;初试成绩400分。专业课140。 整体时间安排 对于考研&#xff0c;很重要的一环就是时间安排&…

C语言--------数据在内存中的存储

1.整数在内存中的存储 整数在内存是以补码的形式存在的&#xff1b; 整型家族包括char,int ,long long,short类型&#xff1b; 因为char类型是以ASCII值形式存在&#xff0c;所以也是整形家族&#xff1b; 这四种都包括signed,unsigned两种&#xff0c;即有符号和无符号&am…

中文GPTS使用秘籍,字节扣子Coze工作流使用全教程

大家好&#xff0c;我是斜杠君。今天和大家分享字节扣子Coze工作流创建和使用全教程&#xff0c;手把手教会你。 首先我们先来看一下如何创建一个工作流。 我们以创建这样一个工作流为例。这个工作流程的作用是&#xff1a;把用户输入的内容通过头条接口查询信息&#xff0c;把…

算法沉淀——位运算(leetcode真题剖析)

算法沉淀——位运算 常用位运算总结1.基础位运算2.确定一个数中第x位是0还是13.将一个数的第x位改成14.将一个数的第x位改成05.位图6.提取一个数最右边的17.删掉一个数最右边的18.异或运算9.基础例题 力扣题目讲解01.面试题 01.01. 判定字符是否唯一02.丢失的数字03.两整数之和…

深入理解 Nginx 插件及功能优化指南

深入理解 Nginx 插件及功能优化指南 深入理解 Nginx 插件及功能优化指南1. Nginx 插件介绍1.1 HTTP 模块插件ngx_http_rewrite_modulengx_http_access_module 1.2 过滤器插件ngx_http_gzip_modulengx_http_ssl_module 1.3 负载均衡插件ngx_http_upstream_modulengx_http_upstre…

模拟发送 Ctrl+Alt+Del 快捷键

目录 前言 一、在 XP 系统上模拟 SAS 二、在不低于 Vista 的系统上模拟 SAS 2.1 一些细节 2.2 实现原理和应用 三、完整实现代码和测试 3.1 客户端控制台程序 3.2 服务程序 3.3 编译&测试程序 四、总结&更新 参考文献 前言 对于开启了安全登陆的窗口工作站…

Java 基于微信小程序的电子商城购物系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12W、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

《统计学简易速速上手小册》第9章:统计学在现代科技中的应用(2024 最新版)

文章目录 9.1 统计学与大数据9.1.1 基础知识9.1.2 主要案例&#xff1a;社交媒体情感分析9.1.3 拓展案例 1&#xff1a;电商销售预测9.1.4 拓展案例 2&#xff1a;实时交通流量分析 9.2 统计学在机器学习和人工智能中的应用9.2.1 基础知识9.2.2 主要案例&#xff1a;预测客户流…

C语言 服务器编程-日志系统

日志系统的实现 引言最简单的日志类 demo按天日志分类和超行日志分类日志信息分级同步和异步两种写入方式 引言 日志系统是通过文件来记录项目的 调试信息&#xff0c;运行状态&#xff0c;访问记录&#xff0c;产生的警告和错误的一个系统&#xff0c;是项目中非常重要的一部…

Flutter 网络请求之Dio库

Flutter 网络请求之Dio库 前言正文一、配置项目二、网络请求三、封装① 单例模式② 网络拦截器③ 返回值封装④ 封装请求 四、结合GetX使用五、源码 前言 最近再写Flutter系列文章&#xff0c;在了解过状态管理之后&#xff0c;我们再来学习一下网络请求。 正文 网络请求对于一…

Linux基础-配置网络

Linux配置网络的方式 1.图形界面 右上角-wired-配置 点加号-新建网络配置文件2.NetworkManager工具 2.1用图形终端nmtui 1.新建网络配置文件add 1.指定网络设备的类型Ethernet 2.配置网络配置文件的名称&#xff0c;名称可以有空格 3.配置网络配置文件对应的物理网络设备的…

【大厂AI课学习笔记】【1.6 人工智能基础知识】(2)机器学习

目录 必须理解的知识点&#xff1a; 举一个草莓的例子&#xff1a; 机器学习的三个类别&#xff1a; 监督学习&#xff1a; 无监督学习&#xff1a; 强化学习&#xff1a; 更多知识背景&#xff1a; 机器学习的诞生需求 监督学习的关键技术与实现步骤 无监督学习的关…

【教学类-48-03】202402011“闰年”(每4年一次 2月有29日)世纪年必须整除400才是闰年)

2000-2099年之间的闰年有25次&#xff0c; 背景需求&#xff1a; 已经制作了对称年月的数字提取&#xff0c;和年月日相等的年份提取 【教学类-48-01】20240205对称的“年”和“月日”&#xff08;如2030 0302&#xff09;-CSDN博客文章浏览阅读84次。【教学类-48-01】202402…