目录
一、Qt的安装和下载
二、Qt介绍
Qt是一个由Qt Company于1991年开发的跨平台C++图形用户界面(GUI)应用程序开发框架。它不仅可用于开发GUI程序,还可以用于开发非GUI程序,如控制台工具和服务器。Qt是一个面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器,moc)和一些宏,使得Qt易于扩展并允许真正的组件编程。
Qt拥有广泛的开发功能,包括多线程、数据库、图像图形处理、音视频处理、网络通信、文件IO等。这使得Qt可以应用于纯软件开发,以及嵌入式行业,如消费类电子、工业控制、军工电子、电信/网络/通讯、航空航天、汽车电子、医疗设备、仪器仪表等相关行业。
Qt的优势在于其良好的跨平台特性,可以实现"一次编程,到处编译",使得开发者可以使用同一份代码构建在多个操作系统和设备上运行的应用程序。
Qt还广泛应用于桌面应用程序、移动应用程序、嵌入式系统、Web应用程序以及游戏开发等多个领域。例如,Linux桌面环境 KDE 、VLC多媒体播放器 、谷歌地图。
总的来说,Qt是一个功能强大、应用广泛的C++应用程序开发框架,适用于各种类型的应用程序和行业的开发需求。
三、初学Qt
1.1创建第一个Qt程序
1.1.1 点击创建项目后,选择项目路径以及 给项目起名称
1.1.2 名称 - 不能有中文 不能有空格
1.1.3 路径 - 不能有中文路径
1.1.4 默认创建有窗口类,myWidget,基类有三种选择: QWidget 、 QMainWindow、QDialog 1.1.5 main函数
1.1.6 QApplication a 应用程序对象,有且仅有一个
1.1.7 myWidget w;实例化窗口对象
1.1.8 w.show()调用show函数 显示窗口
1.1.9 return a.exec() 让应用程序对象进入消息循环机制中,代码阻塞到当前行
1.2按钮控件常用API
步骤
1.2.1 创建 QPushButton * btn = new QPushButton
1.2.2 设置父亲 setParent(this)
1.2.3 设置文本 setText("文字")
1.2.4 设置位置 move(宽,高)
1.2.5 重新指定窗口大小 resize
1.2.6 设置窗口标题 setWindowTitle
1.2.7 设置窗口固定大小 setFixedSize
实操
mydiget.cpp
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>//打印库
#include<QPushButton>//按钮控件的头文件
myWidget::myWidget(QWidget *parent)
: QMainWindow(parent)
{
this->setFixedSize(1024, 800);//设置固定大小,防止用户改变窗口大小
QPushButton *btn = new QPushButton();//新建一个按钮
btn->resize(100, 50);//按钮的大小
btn->setParent(this);//设置按钮的父部件为当前窗口部件
btn->show();//show以顶层方式弹出窗口控件
//显示文本
btn->setText("第一个按钮");
//创建第二个按钮 按照控件的大小创建窗口
QPushButton * btn2 = new QPushButton("第二个按钮",this);
btn2->setGeometry(QRect(100,0, 100, 50));
//移动btn2按钮
btn2->move(100,100);
//重置窗口
//resize(1024,800);
//设置窗口的标题
setWindowTitle("第一个窗口");
}
1.3对象树
1.3.1当创建的对象在堆区时候,如果指定的父亲是QObject派生下来的类或者QObject子类派生下来的类,可以不用管理释放的操作,将对象会放入到对象树中。
1.3.2一定程度上简化了内存回收机制
作用
主要用于正确管理控件对象的内存和事件传递。当一个QObject被创建并指定了另一个QObject作为其父对象时,这个QObject就会被添加到父对象的children()列表中。当父对象被销毁时,它会自动销毁其所有子对象,从而避免内存泄漏。此外,事件会从父对象传递到子对象,直到有一个对象处理了该事件为止。这种机制使得开发人员能够方便地处理事件,而不需要手动管理事件的分发。
1.4Qt中的坐标系
1.4.1左上角为 0 , 0 点
1.4.2x以右为正方向
1.4.3y以下为正方向
1.5信号和槽***
介绍
信号和槽是Qt框架中一种独特且强大的对象间通信机制。简单来说,你可以将信号看作是一个事件或者消息,而将槽看作是对这个事件或消息的处理函数。
想象一下,你有一个按钮对象。当这个按钮被点击时,它会发出一个信号,这个信号就像是一个广播,告诉所有"关注"了这个信号的对象:"嘿,我被点击了!"如果有其他对象对这个信号感兴趣,并且它们已经通过某种方式(使用connect函数)将这个信号和它们自己的一个函数(也就是槽)连接起来了,那么当信号发出时,这些槽函数就会被自动调用。
具体来说,槽就是一个普通的函数,它可以定义在类的任何部分(如public、private或protected),也可以有参数和返回值。与普通函数不同的是,槽函数可以被连接到一个或多个信号。这样,当信号发出时,与之连接的槽函数就会被自动执行。
这种信号和槽的机制使得对象之间的通信变得非常简单和直观。你不再需要关心具体是哪个对象发出了信号,只需要关注自己是否对这个信号感兴趣,并定义相应的槽函数来处理这个信号就可以了。
语法
1.5.1 连接函数 :connect
1.5.2 参数
参数1 信号的发送者
参数2 发送的信号(函数地址)
参数3 信号的接受者
参数4 处理的槽函数 (函数的地址)
1.5.3 松散耦合
在Qt中,松散耦合(Loose Coupling)主要体现在信号与槽(Signal & Slot)的通信机制上。这种机制允许发送者(通常是对象或组件)和接收者(同样是对象或组件)之间保持较低的依赖关系。发送者发出信号时,并不需要知道哪些接收者(槽)会响应这个信号,同样,接收者也不需要知道信号是从哪里发出的。这种通信方式是解耦的,因此是松散的。
具体来说,在Qt中,你可以使用connect()
函数将一个对象的信号连接到另一个对象的槽上。当信号被触发时,与其连接的槽函数会自动被调用。这种连接可以在运行时动态建立,也可以在对象创建时静态设置。由于信号和槽之间的连接是灵活的,因此可以轻松地修改或扩展应用程序的行为,而无需对代码进行大量修改。
这种松散耦合的设计有几个优点:
- 灵活性:由于发送者和接收者之间的依赖关系较低,你可以更容易地修改或替换系统中的组件。
- 可维护性:代码更加模块化,每个组件都有明确的职责和接口,使得系统更易于理解和维护。
- 可扩展性:新的组件可以轻松地集成到现有系统中,只需确保它们遵循相同的信号和槽接口即可。
总之,Qt中的松散耦合是一种设计原则,通过信号和槽机制实现对象之间的低依赖通信,从而提高了应用程序的灵活性、可维护性和可扩展性。
实现点击按钮关闭窗口的案例
(1)头文件中创建一个mypushbutton类,只需要添上析构函数即可,其余均有系统提供。
cpp
#ifndef MYPUSHBUTTON_H
#define MYPUSHBUTTON_H
#include <QPushButton>
class MyPushButton : public QPushButton
{
Q_OBJECT
public:
explicit MyPushButton(QWidget *parent = 0);
~MyPushButton();
signals:
};
#endif // MYPUSHBUTTON_H
(2) 源文件中添加mypushbutton.cpp,实现构造和析构。
cpp
#include "mypushbutton.h"
#include<QDebug>
MyPushButton::MyPushButton(QWidget *parent) : QPushButton(parent)
{
qDebug()<<"我的按钮类构造调用";
}
MyPushButton::~MyPushButton()
{
qDebug()<<"我的按钮析构调用";
}
(3)mywidget.cpp中实现按钮点击事件
cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include<QPushButton>//按钮控件的头文件
#include<mypushbutton.h>
#include<QDebug>
myWidget::myWidget(QWidget *parent)
: QMainWindow(parent)
{
//创建一个自己按钮的对象
MyPushButton* myBtn = new MyPushButton;
myBtn->setText("我自己的按钮");
myBtn->move(200,0);
myBtn->setParent(this);//设置到对象树中
myBtn->resize(150,50);
//需求 点击我的按钮 关闭窗口
//参数1 信号的发送者 参数2 发送信号(函数的地址) 参数3 信号的接收者 参数4 处理的槽函数
//clicked和close均为库函数
connect(myBtn,&MyPushButton::clicked,this,&myWidget::close);
connect(btn,&QPushButton::clicked,this,&myWidget::close);
}
myWidget::~myWidget()
{
qDebug()<<"myWidget的析构调用";
delete ui;
}
this
代表的是调用connect
函数的对象的指针。这行代码是在myWidget
类的一个成员函数内部执行的,那么this
就指向那个myWidget
对象的一个实例。这里,
connect
函数的作用是将myBtn
按钮的clicked
信号连接到当前myWidget
对象的close
槽上。当myBtn
被点击时,myWidget
对象的close
槽函数将会被调用,这通常会导致窗口关闭。
1.1~1.5源码位置:项目Gitee仓库链接https://gitee.com/liu-wei-hao123/green_C/tree/master/01FirstProject
1.6自定义信号和槽
emit介绍
emit是 Qt 框架中的一个关键字,用于触发(发射)一个信号。在 Qt 的信号和槽机制中,当某个事件发生时(比如用户点击了一个按钮),一个对象可以发射一个信号。这个信号随后可以被连接到一个或多个槽函数上,从而执行相应的操作。
自定义信号
创建新项目02SignalAndSlot,创建老师类和学生类,包括.h和.cpp文件。目标描述,下课函数触发后,老师会发出一个信号------饿了,学生响应信号,请客吃饭。
teacher.h
cpp
#ifndef TEACHER_H
#define TEACHER_H
#include <QObject>
class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = nullptr);
signals:
//自定义信号,写道signals下
//返回值void,只需要声明,不需要实现
//可以有参数,可以重载
void hungry();//无参信号
void hungry(QString foodName);//有参信号
};
#endif // TEACHER_H
teacher.cpp
cpp
#include "teacher.h"
#include"QDebug"
Teacher::Teacher(QObject *parent) : QObject(parent)
{
}
自定义槽函数
student.h
cpp
#ifndef STUDENT_H
#define STUDENT_H
#include <QObject>
class Student : public QObject
{
Q_OBJECT
public:
explicit Student(QObject *parent = nullptr);
//早期qt版本必须写到public slots,高级版本写到public或全局下
//返回值void ,需要声明,也需要实现
//可以有参数,可以发生重载
void treat();//无参槽函数
void treat(QString foodName);//有参槽函数
signals:
};
#endif // STUDENT_H
student.cpp
cpp
#include "student.h"
#include <QDebug>
//#define cout qDebug()
Student::Student(QObject *parent) : QObject(parent)
{
}
void Student::treat()
{
qDebug()<<"请老师吃饭";
}
void Student:: treat(QString foodName)
{
qDebug()<<"请老师吃饭,老师要吃"<<(foodName);
}
运行
widget.cpp(有参)
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建一个老师
this->zt=new Teacher(this);
//创建一个学生
this->st=new Student(this);
//此句代码会出错,因为系统分不清是哪一个hungry和treat函数
//所以需要更加明确的指向
connect(zt,&Teacher::hungry,st,&Student::treat);
// 连接带参数的 信号和槽
// 指针->地址
// 函数指针->函数地址
void (Teacher::*teacherSignal)(QString) = &Teacher::hungry;
void (Student::*studentSlot)(QString) = &Student::treat;
connect(zt,teacherSignal,st,studentSlot);
classIsOver();
}
void Widget:: classIsOver()
{
//下课函数,调用后 发出老师饿了的信号
emit zt->hungry("宫保鸡丁");
}
Widget::~Widget()
{
delete ui;
}
在 Qt 的信号和槽机制中,使用函数的地址(即指针)来连接信号和槽是一种常见且必要的做法。这样做的目的是在运行时动态地确定当某个信号被触发时应该调用哪个槽函数。
好处:
多态性 :通过成员函数指针,你可以连接到任何继承自
Teacher
或Student
的类的成员函数,只要这些类有相应的信号或槽。这是面向对象编程中多态性的一个体现。灵活性:在运行时,你可以动态地改变信号和槽的连接。例如,你可以断开现有的连接,并连接到其他槽函数。
解耦:信号和槽机制允许对象之间的通信解耦。发送信号的对象不需要知道哪些对象正在监听该信号,也不需要知道当信号发出时会发生什么。同样,接收信号的对象也不需要知道信号是从哪里发出的。
安全性:Qt 的信号和槽机制是类型安全的。在连接信号和槽时,Qt 会检查它们的签名是否匹配。这有助于避免运行时错误。
信号连接信号
widget.cpp
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建一个老师
this->zt=new Teacher(this);
//创建一个学生
this->st=new Student(this);
//连接带参数的 信号和槽
//指针->地址
//函数指针->函数地址
//无参信号和槽连接
void (Teacher::*teacherSignal2)(void) = &Teacher::hungry;
void (Student::*studentSlot2)(void) = &Student::treat;
connect(zt,teacherSignal2,st,studentSlot2);
//点击一个按钮 下课按钮,再触发下课
QPushButton * btn = new QPushButton("下课",this);
//重置窗口大小
this->resize(600,400);
btn->move(250,150);
//信号连接信号
//这里只能使用无参的函数,因为&QPushButton::clicked的信号是无参数的
//信号和槽的参数类型声明一致
connect(btn,&QPushButton::clicked,zt,teacherSignal);
}
void Widget:: classIsOver()
{
//下课函数,调用后 发出老师饿了的信号
emit zt->hungry("宫保鸡丁");
}
Widget::~Widget()
{
delete ui;
}
运行结果
断开信号
断开信号可以使用disconnect函数
cpp
//断开信号
disconnect(zt,teacherSignal2,st,studentSlot2);
1.7拓展
1、信号是可以连接信号 2、一个信号可以连接多个槽函数 3、多个信号 可以连接 同一个槽函数 4、信号和槽函数的参数 必须类型一一对应 5、信号和槽函数的参数个数 是不是要一致? 信号的参数个数 可以多于槽函数的参数个数
1.8Qt4版本写法
cpp
//利用Qt4信号槽 连接无参版本
Qt4底层SIGNAL("hungry") SLOT("treat")
connect(zt,SIGNAL(hungry()),st,SLOT(treat()));
//Qt5以上 支持Qt4的版本写法,反之不支持
- 优点 参数直观
- 缺点 编译器不会检测参数类型
1.9Lambda表达式
介绍
在 C++11 及以后的版本中,Lambda 表达式(也被称为匿名函数)是一种可以定义小型匿名函数对象的方式。这些对象可以捕获其所在作用域的变量,并且可以在需要的地方像普通函数对象一样被调用。Lambda 表达式在 Qt 的信号和槽机制中特别有用,因为它们允许你定义简短的内联函数来连接信号和槽,而不必创建单独的槽函数。
- capture:捕获子句,用于指定哪些外部变量可以在 lambda 体内使用。可以是按值捕获([]、[=])或按引用捕获([&]、[&variable])。
- parameters:Lambda 函数的参数列表,与普通函数相同。
- return_type:Lambda 函数的返回类型。如果省略,则根据 lambda 体的返回语句推断返回类型。
- body_of_lambda:Lambda 函数的主体,即要执行的代码
举例
1.值传递or址传递
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//Lambda
QPushButton * myBtn = new QPushButton(this);
myBtn->setText("按钮1");
QPushButton * myBtn2 = new QPushButton(this);
myBtn2->setText("按钮2");
myBtn2->move(100,100);
int m = 10;
connect(myBtn,&QPushButton::clicked,this,[&]() {m = 100;qDebug()<<m;});
connect(myBtn2,&QPushButton::clicked,this,[=] () {qDebug()<<m;});
}
在 Qt 的信号和槽机制中,使用 Lambda 表达式时,Lambda 的捕获列表决定了哪些变量是通过引用捕获的,哪些是通过值捕获的。在这段代码中,有两个 Lambda 表达式分别连接到两个按钮的
clicked
信号。对于第一个 Lambda 表达式,使用了
[&]
捕获列表,这意味着所有的自动存储期变量都是通过引用捕获的。因此,当myBtn
被点击时,Lambda 表达式内部对m
的修改(m = 100;
)会反映到main
函数或包含此代码的作用域中的m
变量上。对于第二个 Lambda 表达式,使用了
[=]
捕获列表,这意味着所有的自动存储期变量都是通过值捕获的。因此,m
的一个副本被创建并传递给 Lambda 表达式。在 Lambda 表达式内部对m
的任何修改都只影响这个副本,而不会影响原始m
变量的值。当点击
myBtn
时,m
的值在第一个 Lambda 表达式中被修改为 100,并且这个修改是全局的,因为m
是通过引用捕获的。然而,当随后点击myBtn2
时,第二个 Lambda 表达式中的m
是原始m
的一个值拷贝(在 Lambda 创建时拷贝的,值为 10),因此它打印的是 10 而不是 100。简而言之,第二个 Lambda 表达式打印的
m
的值没有变成 100,是因为它使用的是m
的一个值拷贝,而这个拷贝是在 Lambda 表达式创建时(即在connect
调用时)获取的,此时m
的值仍然是 10。点击myBtn
修改了原始m
的值,但这不影响已经通过值捕获到 Lambda 表达式中的m
副本。如果想让第二个 Lambda 表达式也反映
m
的修改,应该使用[&]
捕获列表而不是[=]
。但是,请注意这样做可能会引入生命周期问题,特别是如果m
是一个局部变量而不是类的成员变量,并且connect
调用发生在不同的作用域(例如在一个函数中)。在这种情况下,局部变量可能在 Lambda 表达式被调用时已经不再存在。在类成员函数的上下文中使用this
指针通常是安全的,因为类的成员变量(如m
)将与类的实例共存亡。
2.lambda作槽函数
Ⅰ、
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建一个老师
this->zt=new Teacher(this);
//创建一个学生
this->st=new Student(this);
void (Teacher::*teacherSignal)(QString) = &Teacher::hungry;
void (Student::*studentSlot)(QString) = &Student::treat;
connect(zt,teacherSignal,st,studentSlot);
QPushButton * btn2 = new QPushButton();
btn2->setText("饿了");
btn2->move(100,0);
btn2->setParent(this);//设置按钮的父部件为当前窗口部件
btn2->show();//show以顶层方式弹出窗口控件
//利用lambda实现下课函数
connect(btn2,&QPushButton::clicked,this,[=] (){
emit zt->hungry("宫保鸡丁");
btn2->setText("吃饭");
});
}
Ⅱ、
cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//利用lambda表达式 实现点击按钮 关闭窗口
QPushButton * btn2 = new QPushButton();
btn2->setText("关闭");
btn2->move(100,0);
btn2->setParent(this);//设置按钮的父部件为当前窗口部件
btn2->show();//show以顶层方式弹出窗口控件
connect(btn2,&QPushButton::clicked,this,[=] (){
this->close();
btn2->setText("aaa");
});
}
1.6~1.9源码位置:
项目Gitee仓库链接https://gitee.com/liu-wei-hao123/green_C/tree/master/02_SignalAndSlot
第一天的学习记录分享完毕,关注我,带你了解更多的编程知识。
看到这里,不妨点个攒,关注一下吧!
最后,谢谢你的观看!