Qt学习笔记
参考文章:(部分内容转载自以下文章)
【Qt】边学边写之Qt教程(零基础)-CSDN博客
QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)-CSDN博客
1.创建Qt项目
1.1 使用向导创建
Qt5基本模块
?向导会默认添加一个继承自QMainWindow的类,可以在此修改类的名字和基类。默认的基类有QMainWindow、QWidget以及QDialog三个,我们可以选择QWidget(类似于空窗口),QWidget 是所有能看到的窗口或者控件的父类,QMainWindow、QDialog 都继承自它
1.2一个简单的Qt应用程序
1.2.1 main函数中
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
main函数
QApplication 就是Qt里边每个应用程序有且仅有一个的应用程序对象
QApplication::exec() 程序的生命循环、消息循环 ,当作以下形式
QApplication应用程序类
- 管理图形用户界面应用程序的控制流和主要设置。
- 是Qt生命,一个程序要确保一直运行,就肯定至少得有一个循环,这就是Qt主消息循环,在其中完成来自窗口系统和其它资源的所有事件消息处理和调度。它也处理应用程序的初始化和结束,并且提供对话管理。
- 对于任何一个使用Qt的图形用户界面应用程序,都正好存在一个QApplication 对象,不论这个应用程序在同一时刻有多少个窗口。
a.exec()
- 程序进入消息循环,等待对用户输入进行响应。这里main()把控制权转交给Qt,Qt完成事件处理工作,当应用程序退出的时候exec()的值就会返回。在exec()中,Qt接受并处理用户和系统的事件并且把它们传递给适当的窗口部件。
?1.2.4 QtCreator常用快捷键
可以在工具>选项>环境>键盘中进行快捷键修改
运行 ctrl +R
编译 ctrl +B
帮助文档 F1 ,点击F1两次跳到帮助界面
跳到符号定义 F2 或者ctrl + 鼠标点击
注释 ctrl+/
字体缩放 ctrl + 鼠标滚轮
整行移动代码 ctrl + shift + ↑或↓
自动对齐 ctrl + i
同名之间的.h和.cpp文件跳转 F4
2、Qt 按键小程序
2.1 按钮的创建和父子关系
- 没有建立父子关系,显示的是顶层窗口
#include <QPushButton>
//添加按钮
QPushButton btu;
btu.setText("按钮1");
//将按钮显示出来
btu.show();
- ?建立父子关系,两种方式
- 利用指针的形式创建?
//第二种创建
QPushButton * btn2 = new QPushButton("按键1",this);
//重新指定窗口大小
this->resize(600,400);
//设置窗口标题
this->setWindowTitle("第一个项目");
//限制窗口大小
this->setFixedSize(600,400);
?2.2 Qt窗口坐标体系
对于嵌套窗口,其坐标是相对于父窗口来说的。顶层窗口的父窗口就是屏幕。
2.3 Qt常用API函数
move 移动窗口到父窗口某个坐标
resize 重新设置窗口的大小
setFixedSize 设置窗口的固定大小
setWindowTitle 设置窗口标题
setGeometry 同时设置窗口位置和大小,相当于move和resize的结合体
上面这几个函数都是QWidget类的成员函数,按钮其实是个窗口,而窗口类最终都是继承自QWidget类。
QWidget > QAbstractButton > QPushButton
?2.4 对象树模型
QObject是Qt里边绝大部分类的根类
1.QObject对象之间是以对象树的形式组织起来的。
? ? ? ? 当两个QObject(或子类)的对象建立了父子关系的时候。子对象就会加入到父对象的一个成员变量叫children(孩子)的list(列表)中。
? ? ? ? 当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里是说父对象和子对象,不要理解成父类和子类)
2.QWidget是能够在屏幕上显示的一切组件的父类
? ? ? ? QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。我们向某个窗口中添加了一个按钮或者其他控件(建立父子关系),当用户关闭这个窗口的时候,该窗口就会被析构,之前添加到他上边的按钮和其他控件也会被一同析构。这个结果也是我们开发人员所期望的。
? ? ? ? 当然,我们也可以手动删除子对象。当子对象析构的时候会发出一个信号destroyed,父对象收到这个信号之后就会从children列表中将它剔除。比如,当我们删除了一个按钮时,其所在的主窗口会自动将该按钮从其子对象列表(children)中删除,并且自动调整屏幕显示,按钮在屏幕上消失。当这个窗口析构的时候,children列表里边已经没有这个按钮子对象,所以我们手动删除也不会引起程序错误。
? ? ? ? Qt 引入对象树的概念,在一定程度上解决了内存问题。
3.对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
4.任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。
?Qwidget是能够再屏幕上显示的一切组件的父类。
- 程序崩溃代码示例:
{
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);
}
????????在上面的代码中,作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。
????????由此我们看到,Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。这些细节在今后的开发过程中很可能时不时跳出来烦扰一下,所以,我们最好从开始就养成良好习惯,在 Qt 中,尽量在构造的时候就指定 parent 对象,并且大胆在堆上创建。
3、信号和槽机制
信号:各种事件 (signal)
槽: 响应信号的动作 (slot)
类似于单片机中的中断信号和中断函数
?3.1 系统自带的信号和槽
下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?
其实两行代码就可以搞定了,我们看下面的代码
QPushButton * quitBtn = new QPushButton("关闭窗口",this);
connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
?# 第一行是创建一个关闭按钮,这个之前已经学过,第二行就是核心了,也就是信号槽的使用方式
connect函数是建立信号发送者、信号、信号接收者、槽四者关系的函数:
connect(sender, signal, receiver, slot);
参数解释:
- sender:信号发送者
- signal:信号
- receiver:信号接收者
- slot:接收对象在接收到信号之后所需要调用的函数(槽函数)
这里要注意的是connect的四个参数都是指针,信号和槽是函数指针,使用connect的时候保留&符号。
?
3.2 自定义信号和槽
Qt框架默认提供的标准信号和槽不足以完成我们日常应用开发的需求,比如说点击某个按钮让另一个按钮的文字改变,这时候标准信号和槽就没有提供这样的函数。但是Qt信号和槽机制提供了允许我们自己设计自己的信号和槽。
3.2.1 自定义信号使用条件
函数声明在类头文件的signals域下
没有返回值,void类型的函数
只有函数声明,没有实现定义
可以有参数,可以重载
通过emit关键字来触发信号,形式:emit object->sig(参数);
3.2.2 自定义槽函数使用条件
qt4 必须声明在类头文件的 private/public/protected slots域下面,qt5之后可以声明再类的任何位置,同时还可以是静态的成员函数,全局函数,lambda表达式
没有返回值,void类型的函数
不仅有声明,还得要有实现
可以有参数,也可以重载
?3.2.3 使用自定义信号和槽
【定义场景】:下课了,老师跟同学说肚子饿了(信号),学生请老师吃饭(槽)
- 首先定义一个学生类Student和老师类Teacher:
- ?老师类中声明信号 饿了 hungry?
- ?学生类中声明槽 请客treat?
- 自定义槽函数 实现?
#include<QDebug>
void Student::treat()
{
qDebug() << "Student treat teacher";
}
- 在窗口中声明一个公共方法下课,这个方法的调用会触发老师饿了这个信号,而响应槽函数学生请客?
- 在窗口中连接信号槽?
??3.2.4?解决使用自定义信号和槽的重载问题
- 问题:如果有两个重名的自定义信号或者槽,在直接使用connect连接时就会报错,所以需要解决重载问题。
?
- 解决方法:
//因为函数发生了重载,所以解决
? ? ?* 1 使用函数指针赋值,让编译器挑选符合类型的函数
? ? ?* 2 使用static_cast 强制转换,也是让编译器自动挑选符合类型的函数
????????1.重载有参函数,使用函数指针赋值:
void(Teacher::*teacher_qstring)(QString) = &Teacher::hungry;
void(Student::*student_qstring)(QString) = &Student::treat;
connect(pTeacher,teacher_qstring,pStudent,student_qstring);
????????2.使用强制类型转换解决重载问题:?
//也可以使用static_cast静态转换挑选我们要的函数
connect(
pTeacher,
static_cast<void(Teacher:: *)(QString)>(&Teacher:: hungry),
pStudent,
static_cast<void(Student:: *)(QString)>(& Student::treat)
);
3.3 信号和槽的拓展
- 一个信号可以和多个槽相连
????????如果是这种情况,这些槽会一个接一个的被调用,但是槽函数调用顺序是不确定的。
- 多个信号可以连接到一个槽
????????只要任意一个信号发出,这个槽就会被调用。如:一个窗口多个按钮都可以关闭这个窗口。
- 一个信号可以连接到另外的一个信号
????????当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。注意这里还是使用connect函数,只是信号的接收者和槽函数换成另一个信号的发送者和信号函数。如上面老师饿了的例子,可以新建一个按钮btn。
- 信号和槽可以断开连接
可以使用disconnect函数,当初建立连接时connect参数怎么填的,disconnect里边4个参数也就怎么填。这种情况并不经常出现,因为当一个对象delete之后,Qt自动取消所有连接到这个对象上面的槽。?
connect(pTeacher,teacher_qstring,pStudent,student_qstring);//建立连接
disconnect(pTeacher,teacher_qstring,pStudent,student_qstring);//断开连接
- 信号和槽的关系信号和槽函数参数类型和个数必须同时满足两个条件
????????1)?信号函数的参数个数必须大于等于槽函数的参数个数
????????2)?信号函数的参数类型和槽函数的参数类型必须一一对应
? ? ?hungry(QString) -> treat() ok
? ? ? hungry(QString) -> treat(int) 编译出错
?? ? ?hungry(QString,int) -> treat(int) 编译出错
?? ? ?hungry(int,String) -> treat(int) ok
3.5 Lambda表达式
C++11中的Lambda表达式用于定义匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:
分为四个部分:[局部变量捕获列表]、(函数参数)、函数额外属性设置opt、函数返回值->retype、{函数主体}
[capture](parameters) opt ->retType
{
……;
}
//使用lambda的方式,使用函数指针,
//void (*p)() 是一个函数指针变量的声明,指向返回类型为 void,没有参数的函数。
void(*p)()=
[]()
{
qDebug()<<"Hello Lambda";
};
p(); //运行函数指针指向的函数
//简写
[]()
{
qDebug()<<"Hello Lambda";
}();
3.5.1 Lambda表达式的局部变量引入方式
[ ],标识一个Lambda的开始。由于lambda表达式可以定义在某一个函数体(A)里边,所以lambda表达式有可能会去访问这个(A)函数中的局部变量。中括号里边内容是描述了在lambda表达式里边可以使用的外部局部变量的列表:
- ?[] 表示lambda表达式不能访问外部函数体的任何局部变量
- [a] 在函数体内部使用值传递的方式访问a变量
- [=] 函数外的所有局部变量都通过值传递的方式使用, 函数体内使用的是副本
- [&] 引用的方式使用lambda表达式外部的所有变量
- [=, &a] a使用引用方式, 其余是值传递的方式
- [&,a] a使用值传递方式, 其余是引用传递的方式
- [this] 在函数内部可以使用类的成员函数和成员变量,=和&形式也都会默认引入
error,a没有被捕获,我们需要传个参数过去,但是目的不是自己用的,是给别人用的,所以这里用捕获
[a,b]()
{
qDebug()<<"Hello Lambda";
qDebug()<<a <<b;
}();
[&a,&b]()
{
qDebug()<<"Hello Lambda";
qDebug()<<"修改前"<<a <<b;
a = 100;
b = 200;
}();
qDebug()<<"修改后:"<<a <<b;
?由于引用方式捕获对象会有局部变量释放了而lambda函数还没有被调用的情况。如果执行lambda函数那么引用传递方式捕获进来的局部变量的值不可预知。
所以在无特殊情况下建议使用[=](){}
的形式
3.5.2 Lambda表达式的函数参数
(params)表示lambda函数对象接收的参数,类似于函数定义中的小括号表示函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引用(如:(int &a,int &b))两种方式进行传递。函数参数部分可以省略,省略后相当于无参的函数。
3.5.3 Lambda表达式的选项Opt
Opt 部分是可选项,最常用的是mutable声明,这部分可以省略。外部函数局部变量通过值传递引进来时,其默认是const,所以不能修改这个局部变量的拷贝,加上mutable就可以
int a = 10 ;
[=]()
{
a=20;//编译报错,a引进来是const
}
[=]()mutable
{
a=20;//编译成功
};
3.5.4 槽函数使用Lambda表达式
以QPushButton点击事件为例:
QPushButton *btn = new QPushButton("按钮1",this);
int a = 10;
int b = 20;
connect(btn,&QPushButton::clicked,[=]()
{
qDebug()<<a <<b;
}
);
这里可以看出使用Lambda表达式作为槽的时候不需要填入信号的接收者。当点击按钮的时候,clicked信号被触发,lambda表达式也会直接运行。当然lambda表达式还可以指定函数参数,这样也就能够接收到信号函数传递过来的参数了。
//可以在函数体执行下面这些函数功能
this->close();//关闭窗口
this->resize(500,600);//设置窗口固定大小
由于lambda表达式比我们自己自定义槽函数要方便而且灵活得多,所以在实现槽函数的时候优先考虑使用Lambda表达式。一般我们的使用习惯也是lambda表达式外部函数的局部变量全部通过值传递捕获进来,也就是:?[=](){ }
的形式。
3.5.5 函数返回值 ->retType
->retType,标识lambda函数返回值的类型。这部分可以省略,但是省略了并不代表函数没有返回值,编译器会自动根据函数体内的return语句判断返回值类型,但是如果有多条return语句,而且返回的类型都不一样,编译会报错,如:
[=]()mutable
{
int b = 20;
float c = 30.0;
if(a>0)
return b;
else
return c;//编译报错,两条return语句返回类型不一致
};
3.6 信号和槽知识点总结
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!