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_ptr
或QSharedPointer
),但它的机制类似于引用计数。每个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.如果变量定义在里面:
由于时局部变量,无法长期维持, mysignal
和 myslot
的实例 s
和 r
是在 MainWindow
构造函数的局部作用域中创建的。这意味着它们只在构造函数执行期间存在,一旦构造函数执行完毕,这些局部变量就会被销毁。因此,当您点击按钮试图触发信号时,s
和 r
的实例可能已经不存在了,导致信号无法被成功触发或接收。
如果执意运行,会发现点击按钮无响应(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");});
效果: