参考文章:(部分内容转载自以下文章)
【Qt】边学边写之Qt教程(零基础)-CSDN博客
QT入门看这一篇就够了——超详细讲解(40000多字详细讲解,涵盖qt大量知识)-CSDN博客
Qt5基本模块
向导会默认添加一个继承自QMainWindow的类,可以在此修改类的名字和基类。默认的基类有QMainWindow、QWidget以及QDialog三个,我们可以选择QWidget(类似于空窗口),QWidget 是所有能看到的窗口或者控件的父类,QMainWindow、QDialog 都继承自它
#include "widget.h"
#include
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
main函数
QApplication 就是Qt里边每个应用程序有且仅有一个的应用程序对象
QApplication::exec() 程序的生命循环、消息循环 ,当作以下形式
QApplication应用程序类
a.exec()
可以在工具>选项>环境>键盘中进行快捷键修改
运行 ctrl +R
编译 ctrl +B
帮助文档 F1 ,点击F1两次跳到帮助界面
跳到符号定义 F2 或者ctrl + 鼠标点击
注释 ctrl+/
字体缩放 ctrl + 鼠标滚轮
整行移动代码 ctrl + shift + ↑或↓
自动对齐 ctrl + i
同名之间的.h和.cpp文件跳转 F4
#include
//添加按钮
QPushButton btu;
btu.setText("按钮1");
//将按钮显示出来
btu.show();
//第二种创建
QPushButton * btn2 = new QPushButton("按键1",this);
//重新指定窗口大小
this->resize(600,400);
//设置窗口标题
this->setWindowTitle("第一个项目");
//限制窗口大小
this->setFixedSize(600,400);
对于嵌套窗口,其坐标是相对于父窗口来说的。顶层窗口的父窗口就是屏幕。
move 移动窗口到父窗口某个坐标
resize 重新设置窗口的大小
setFixedSize 设置窗口的固定大小
setWindowTitle 设置窗口标题
setGeometry 同时设置窗口位置和大小,相当于move和resize的结合体
上面这几个函数都是QWidget类的成员函数,按钮其实是个窗口,而窗口类最终都是继承自QWidget类。
QWidget > QAbstractButton > QPushButton
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 对象,并且大胆在堆上创建。
信号:各种事件 (signal)
槽: 响应信号的动作 (slot)
类似于单片机中的中断信号和中断函数
下面我们完成一个小功能,上面我们已经学习了按钮的创建,但是还没有体现出按钮的功能,按钮最大的功能也就是点击后触发一些事情,比如我们点击按钮,就把当前的窗口给关闭掉,那么在Qt中,这样的功能如何实现呢?
其实两行代码就可以搞定了,我们看下面的代码
QPushButton * quitBtn = new QPushButton("关闭窗口",this);
connect(quitBtn,&QPushButton::clicked,this,&MyWidget::close);
# 第一行是创建一个关闭按钮,这个之前已经学过,第二行就是核心了,也就是信号槽的使用方式
connect函数是建立信号发送者、信号、信号接收者、槽四者关系的函数:
connect(sender, signal, receiver, slot);
参数解释:
这里要注意的是connect的四个参数都是指针,信号和槽是函数指针,使用connect的时候保留&符号。
Qt框架默认提供的标准信号和槽不足以完成我们日常应用开发的需求,比如说点击某个按钮让另一个按钮的文字改变,这时候标准信号和槽就没有提供这样的函数。但是Qt信号和槽机制提供了允许我们自己设计自己的信号和槽。
函数声明在类头文件的signals域下
没有返回值,void类型的函数
只有函数声明,没有实现定义
可以有参数,可以重载
通过emit关键字来触发信号,形式:emit object->sig(参数);
qt4 必须声明在类头文件的 private/public/protected slots域下面,qt5之后可以声明再类的任何位置,同时还可以是静态的成员函数,全局函数,lambda表达式
没有返回值,void类型的函数
不仅有声明,还得要有实现
可以有参数,也可以重载
【定义场景】:下课了,老师跟同学说肚子饿了(信号),学生请老师吃饭(槽)
#include
void Student::treat()
{
qDebug() << "Student treat teacher";
}
//因为函数发生了重载,所以解决
* 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(&Teacher:: hungry),
pStudent,
static_cast(& Student::treat)
);
如果是这种情况,这些槽会一个接一个的被调用,但是槽函数调用顺序是不确定的。
只要任意一个信号发出,这个槽就会被调用。如:一个窗口多个按钮都可以关闭这个窗口。
当第一个信号发出时,第二个信号被发出。除此之外,这种信号-信号的形式和信号-槽的形式没有什么区别。注意这里还是使用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
C++11中的Lambda表达式用于定义匿名的函数对象,以简化编程工作。首先看一下Lambda表达式的基本构成:
分为四个部分:[局部变量捕获列表]、(函数参数)、函数额外属性设置opt、函数返回值->retype、{函数主体}
[capture](parameters) opt ->retType
{
……;
}
//使用lambda的方式,使用函数指针,
//void (*p)() 是一个函数指针变量的声明,指向返回类型为 void,没有参数的函数。
void(*p)()=
[]()
{
qDebug()<<"Hello Lambda";
};
p(); //运行函数指针指向的函数
//简写
[]()
{
qDebug()<<"Hello 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]()
{
qDebug()<<"Hello Lambda";
qDebug()<<"修改前"<
由于引用方式捕获对象会有局部变量释放了而lambda函数还没有被调用的情况。如果执行lambda函数那么引用传递方式捕获进来的局部变量的值不可预知。
所以在无特殊情况下建议使用[=](){}
的形式
(params)表示lambda函数对象接收的参数,类似于函数定义中的小括号表示函数接收的参数类型和个数。参数可以通过按值(如:(int a,int b))和按引用(如:(int &a,int &b))两种方式进行传递。函数参数部分可以省略,省略后相当于无参的函数。
Opt 部分是可选项,最常用的是mutable声明,这部分可以省略。外部函数局部变量通过值传递引进来时,其默认是const,所以不能修改这个局部变量的拷贝,加上mutable就可以
int a = 10 ;
[=]()
{
a=20;//编译报错,a引进来是const
}
[=]()mutable
{
a=20;//编译成功
};
以QPushButton点击事件为例:
QPushButton *btn = new QPushButton("按钮1",this);
int a = 10;
int b = 20;
connect(btn,&QPushButton::clicked,[=]()
{
qDebug()<
这里可以看出使用Lambda表达式作为槽的时候不需要填入信号的接收者。当点击按钮的时候,clicked信号被触发,lambda表达式也会直接运行。当然lambda表达式还可以指定函数参数,这样也就能够接收到信号函数传递过来的参数了。
//可以在函数体执行下面这些函数功能
this->close();//关闭窗口
this->resize(500,600);//设置窗口固定大小
由于lambda表达式比我们自己自定义槽函数要方便而且灵活得多,所以在实现槽函数的时候优先考虑使用Lambda表达式。一般我们的使用习惯也是lambda表达式外部函数的局部变量全部通过值传递捕获进来,也就是: [=](){ }
的形式。
->retType,标识lambda函数返回值的类型。这部分可以省略,但是省略了并不代表函数没有返回值,编译器会自动根据函数体内的return语句判断返回值类型,但是如果有多条return语句,而且返回的类型都不一样,编译会报错,如:
[=]()mutable
{
int b = 20;
float c = 30.0;
if(a>0)
return b;
else
return c;//编译报错,两条return语句返回类型不一致
};