QT 学习笔记摘要(一)
第一节 QT介绍
1. QT概述
简单来说,QT就是一个跨平台的客户端技术,HTML画网页一样,而QT就是画客户端的,它不仅可以绘制界面而且可以做单机应用开发,还可以做网络程序的客户端界面开发
更专业的说法是:Qt 是⼀个 跨平台的 C++ 图形⽤⼾界⾯应⽤程序框架 ,它拥有完备的C++ 图形库和集成了一系列代码模块简化难度,开发者可以通过简单的拖拽和组合来实现复杂的应⽤程序,而且Qt 支持C++,Python,QML,Javascript 等多种语言,适合多种技术、开发方式。同时Qt 也拥有一套完整的设计、开发工具以及丰富的文档和例程,其开源社区非常活跃,这些能明显降低开发难度和缩短开发时间
其实除了QT是客户端技术,还有VB,C++、win32API 的 MFC ,Winform ,C# .net framework,Java swing/javaFx ,C++ duilib,Objective-c/swift cocoa等等
但他们不是做出来的客户端界面丑,无法跨平台,没有活跃的社区,就是没有维护了,所以一般公司做客户端界面都是用的QT;
而Qt是很多客户端跨平台的首选,因为开源、UI 库和各种功能的类库非常丰富,但这里只推荐做pc的客户端,移动端的客户端界面还是不推荐使用Qt
2. QT框架
一句话:One framework. One codebase. Any platform,这是Qt 官网的一句话,很好的概括了什么是Qt,
口号说明:
- One framework(一个框架):只需要使用一个开发框架
- One codebase(一套代码):只需要维护一份代码,而不是针对不同平台写不同的代码
- Any platform(任何平台):这套代码可以运行在多个平台上,比如 Windows、macOS、Linux,甚至移动设备(iOS/Android)或 Web 端
3. QT发展(了解)
-
Qt最早是1991年由挪威的Eirik Chambe-Eng和Haavard Nord开发的,1994年3月4日成立奇趣科技公司(Trolltech);
-
2000年奇趣科技公司为开源社区发布了遵循 GPL(GNU General Public License)许可证的开源版本;
-
2008年诺基亚公司收购了奇趣科技公司,并增加了 LGPL(GNU Lesser General Public License)的授权模式;
-
2011年3月Qt 商业授权业务出售给了芬兰IT服务公司Digia
- 2013年7月3日发布Qt5,2020年6月12日**Qt5.15** LTS 正式发布,目前已经推出Qt6。
经过多年的发展,市面上也有很多基于Qt开发的应用程序:WPS、YY语音、豆瓣电台、虾米音乐、淘宝助理、千牛、暴雪的战网客户端、VirtualBox、Google地图、Photoshop等等
4. QT优点
-
跨平台,⼏乎⽀持所有的平台;
-
接⼝简单,容易上⼿,学习 QT 框架对学习其他框架有参考意义。
-
⼀定程度上简化了内存回收机制;
-
开发效率⾼,能够快速的构建应⽤程序。
-
有很好的社区氛围,市场份额在缓慢上升。
-
可以进⾏嵌⼊式开发。
5. Qt 的应⽤场景
-
主要: 桌⾯应⽤程序, 嵌⼊式系统
-
次要: 移动应⽤程序
额外多说一下:嵌入式系统指的是日常使用的: 冰箱,洗衣机,路由器,投影仪...之类的,这些设备里面就使用了嵌入式系统
第二节 QT Creator
1. 下载
https://download.qt.io/archive/qt/5.12/5.12.11/
虽然目前最新的QT版本是QT6,但是我这里使用的是QT5
- 我使用的是5.12.11版的qt,大家可以根据自己的实际情况进行下载,
- 值得多说一句的是:类似于这些软件/工具的下载,不要最新的,也不要太老的
- 最新的版本出了问题不好解决,太老的版本可能又会有兼容性问题
2. 安装
说明一下:
- 可能第一个界面是需要注册账号的,直接使用邮箱+密码注册,然后会给你的邮箱中发个邮件,最后你在你的邮箱中确认那个邮件就行了(如果在安装之间断网,那么就不需要注册账号)
- 下一步,下一步,然后选择那四个组件就行了,不要全选
3. 配置环境变量
为了让操作系统/QT Creator工具,能够找到Qt SDK中提供的exe,也就是运行Qt 程序的时候,能够找到对应.dll 动态库,所以我们这里可以先把环境变量配置好
4. 基本介绍
刚才其实我们就已经把QT开发环境下好了,可能桌面没有显示,按一下win键,如下图
5. 新建项目
- 其实一路next下去也行
6. 项目结构
6.1 qt_tmp.pro
qt_tmp.pro是项目的配置文件
说明一下:
- 这里该怎么理解QT += 模块呢,其实就是我们在做大型项目是需要引入的第三方库,
- 后面我们会需要引入multimedia 模块,而这个模块是跟音视频有关的模块,简单理解为第三方库也行
6.2 mainwindow.h
说明一下:
- Q_OBJECT其实是个宏,其中有QT中的信号与槽的宏定义,更详细的后面再说
关于QT中对头文件的设定:
- 在使用Qt中内置的类时,一般来说包含头文件的名字就是和类名一致的,但是也不是所有的Qt类都需要显示包含头文件
- 毕竟在c++中,一些头文件都是间接被包含的
关于构造函数中需要穿父类的指针说明:
- QT中为了统一析构父类和子类,引入了对象树的概念(后面详细介绍)
- 而创建对象时就需要把这个对象往树·上挂,而往树上挂就必须要指明父节点,
- 则构造的时候就需要传一个父类的指针
6.3 mainwindow.cpp
6.4 main.cpp
说明一下:
- 是先有应用,再有窗口,窗口是放在应用里的,可能有多个窗口,所以他们2个对象实例化的顺序不能颠倒
这里简单说一下a.exec(),其实可以简单的理解为一个死循环,但还做了一些其他的东西,
- 如果没有最后一行代码,而是写成了return 0; 那么这个窗口会闪一下,就退出了,
- 简单解释:能出现窗口是因为有w.show();// 显示窗口,而w对象又是栈上开辟的,出了作用域就没了
顺便再说一下:w.show()
- .show()方法表示让控件显示处理
- .hide()方法表示让控件隐藏
- widget的父类时QWidget,则上面的那两个方法都是QWidget提供的
6.5 mainwindow.ui
上面那种格式是xml格式,而这里的xml格式就是去描述这个界面是怎么样的,进一步的qmake会调用相关的工具,依据这个xml文件生成一些c++代码,从而把完整的界面构建出来
6.6 临时文件
说明一下:
- 在运行一次程序之后,就会在 项目并列的地方,多出一个"build-xxxx"目录
- 这个目录里面就是该项目运行过程中,生成的一些临时文件
7. 第一个程序hello world
7.1 纯图形化方式
说明一下:
- 以图形化方式实现界面,主要是在qt designer 中拖动控件,调调属性等
- 而我刚才往界面上拖拽了一个QLabel控件,此时,ui文件的xml中就会多出来这一段代码
- 进一步的qmake就会在编译项目的时候,基于这个内容,生成一段C++代码,通过这个C++代码构建出界面内容
7.2 纯代码方式
方式一:重建一个cpp文件
#include <iostream>
#include <QMainWindow>
#include <QLabel>
#include <QApplication>
using namespace std;int main(int argc, char *argv[])
{// cout << "Hello World!" << endl;QApplication a(argc, argv);// 因为show这个方法本来就是QMainWindow类的,所以直接实例化这个类的对象就行QMainWindow w;w.setGeometry(0,0,800,600);w.setWindowTitle("第一个程序");QLabel* label = new QLabel(&w);// 对这个控件做相关属性设置label->setText("hello world");label->setGeometry(160,110,400,71);label->setStyleSheet("font: 75 20pt Consolas;");w.show();return a.exec();
}
方式二:重建一个qt设计类文件
说明一下:
- 一般通过代码来构造界面的时候,通常会把构造界面放到Widget/MainWindow的构造函数中
- 这里的this指针,是给当前这个label对象,指定一个父对象(以后会说的对象树)
- 在QT中的字符串和C++/C中的字符串是不一样的,当时C++/C的字符串不好用,所以QT为了字节的开发能变的顺畅,就自己发明了一套轮子,搞了一系列基础类,来支持QT的开发,包括但不限于
字符串->QString 动态数组->QVector 链表->QList 字典->QMap - 在QString中也提供了 C风格字符串作为参数的构造函数,不显示构造QString,上述代码中,C风格字符串也会隐式构造成QString对象
- 每个标签都有一个同名的头文件,但有时候也会被其他头文件间接包含,
比如: QString 对应的头文件,已经被很多Qt内置的其他类给间接包含了,因此一般不需要显示包含QString头文件
这里还有一个值得思考的点: 一个对象new出来了,但是却没有delete,这难道不会造成内存泄漏吗
- 在上述代码中,Qt不会产生内存泄漏,label对象会在合适的时候被析构函数释放~~
- 之所以能够把对象释放掉,主要是因为把这个对象挂到了对象树上了,交给Qt的对象树统一管理
- 但如果这个对象是在栈上创建的话,就有可能会存在一些"提前释放"的问题,此时就会导致对应的控件就在界面上不存在了
- 推荐: 在堆上创建对象,并挂在对象树上
7.3 验证对象树会统一析构
- 注: 上面少写了个delete
- 自己实现label类,并继承QLabel,然后再自主实现析构函数
- 则可以在输出日志中观察到 自己实现的析构函数被调用了,则对象树的确会统一析构
- 这里使用qDebug进行输出日志(不建议使用cout),还有一个好处->可以进行统一关闭
输出日志一般是在开发阶段,调试程序的时候使用
8. 快捷键
- 注释:ctrl + /
- 运⾏:ctrl + R
- 编译:ctrl + B
- 字体缩放:ctrl + ⿏标滑轮
- 查找:ctrl + F
- 整⾏移动:ctrl + shift + ⬆/⬇
- 帮助⽂档:F1
- ⾃动对⻬:ctrl + i;
- 同名之间的 .h 和 .cpp 的切换:F4
- ⽣成函数声明的对应定义: alt + enter
9. 使⽤帮助⽂档
-
方法一:光标放到要查询的类名/⽅法名上, 直接按 F1(推荐)
-
方法二:Qt Creator 左侧边栏中直接⽤⿏标单击 "帮助" 按钮
-
方法三:找到 Qt Creator 的安装路径,在 "bin" ⽂件夹下找到 assistant.exe,双击打开;
10. Qt 中的命名规范
-
类名:⾸字⺟⼤写,单词和单词之间⾸字⺟⼤写;MyClass MyAdd
-
函数名及变量名:⾸字⺟⼩写,单词和单词之间⾸字⺟⼤写;studentCount
11. Qt 窗⼝坐标体系
计算机中的坐标系和数学中的坐标系是不一样的,y轴是向下增长的
说明一下:
- 对于嵌套窗口,其坐标是相对于父窗口来说的
第三节 信号与槽
1. 基本概念
信号:信号就是一个通知机制,比如在某个状态做了一个什么事情,或者状态发生了改变,需要有别人来处理的时候,就要发出一个信号,就相当于发出一个通知,信号是没有实现的,信号的成员函数是不需要被定义的
槽函数:信号发出去以后,一定要做一件事情,而这个事情就是由槽函数来做的,简单来说就是对信号做出的一种响应
在QT中信号和槽函数一定要先进行绑定才可以的,且一定是先关联信号再进行绑定(顺序不能颠倒),后续只要信号触发了,Qt就会自动执行槽函数
信号与槽这种机制是QT框架很重要的机制,它的优点有:松散耦合,但缺点:效率较低
2. 图形化操作
比如我现在要在窗口中放置一个按钮,当我点击这个按钮的时候,这个窗口就会关闭
注意:在ui界面中改了控件名字,是需要重新构建的,否则控件名将不会生效
2.1 编写槽函数
在ui界面中选中控件,然后右键转到槽,然后选择对应的信号
之后在头文件对应的类中就会自动生成一个信号,并把光标跳转到自动生成的槽函数中
void MainWindow::on_pushButton_clicked()
{// 关闭窗口this->close();
}
说明一下:
- 通过图形化界面的方式定义槽函数,的确很方便,很多步骤它都帮我们做完了
- 但在真实的开发环境中我们需要自定义信号,自定槽函数,自己去关联信号与槽函数,
3. 纯代码操作
3.1 connect函数
在 Qt 中,QObject 类提供了⼀个静态成员函数 connect() ,该函数专⻔⽤来关联指定的信号函数和槽函数
connect(const QObject * sender,const char* signal,const QObject* receiver
const char* method, Qt::ConnectionType type = Qt::AutoConnection)
参数说明:
- sender: 描述当前信号是那个控件发出的
- signal: 描述信号类型
- receiver: 描述了那个控件负责处理
- method: 描述了这个控件怎么处理(要处理信号的对象提供成员函数)
3.2 深度理解connect函数参数
- 我们上面传信号和槽都是传递的函数指针,而在C++中是不允许使用2个不同指针类型,相互传参相互赋值的(函数传参,本质就是赋值)
- 其实这个函数声明是以前旧版本的QT的connect函数声明,以前个信号参数传参,要搭配要给SIGNAL宏,给槽函数传参要搭配一个SLOT宏(这样做的目的是 将 传入参数 转成char*)
- connect(button,SIGNAL(&QPuhsButton::clicked),this,SLOT(&Widget::close))
但是后来因为书写起来太麻烦了,在Qt5时进行了改进
- 使用模板实现泛型编程,重载构造函数
- 此时connect函数就有一定的参数检查功能:参数1和参数2不匹配 or 参数3和参数4不匹配,
都会编译出错 - 则要求参数2必须是参数1的成员函数,参数4必须是参数3的成员函数
4. 查看信号与槽
自己平时的积累 + 查看文档
- QPushButton自己本身没有clicked信号,但是它继承了QAbstractButton
- 在QAbstractButton中就有一个clicked点击信号
- 注: 这个QAbstractButton又使继承QWidget
- 我们写的widget在定义的时候就是继承QWidget的,而QWidget就有close这个关闭槽
- 注: QWidget这个类又是继承自QObject类的
5. 自定义槽
- 在Qt中,除了通过connect来连接信号槽之外,还可以通过函数名字的方式自动连接
5. 自定义信号
- 自定义信号,本质就是一个函数,只需要声明就行了,而这个函数的定义,是Qt在编译过程中,自动生成的(我们无法干预)
- 而作为信号参数,这个函数的返回值,必须是void的,有没有参数都是可以的,甚至可以支持重载
- Qt内置的信号,不需要手动触发,而自定义信号,需要手动代码触发 emit mySignal();
- 这里使用图形化方式创建一个按钮,在通过这个按钮发送 自定义信号
-
发送信号的操作, 也可以在任意合适的代码中. 不一定非得在构造函数里
6. 带参数的信号与槽
-
带有参数的信号,要求信号的参数和槽的参数要一致
-
类型,个数要满足要求(信号的参数个数要多于槽的参数个数)
7. 额外说明
7.1 前置条件
- Qt 中如果要让某个类能够使用信号槽(可以在类中定义信号和槽函数),则必须要在类最开始的地方,写下Q_OBJECT宏
- 将这个宏展开,会得到一大段代码,然后而这一大段代码又可以展开
7.2 设计目的
- 解耦,: 把触发 用户操作的控件 和 处理对应用户的操作逻辑 解耦合
- 实现"多对多"效果
一个信号,可以connect到多个槽函数上
一个槽函数也可以被多个信号connect - 但是多对多的需求实际中并不常见,所以以后图形化开发框架都没有支持多对多
7.3 disconnect断开连接
主动断开往往是把信号重新绑定到另一个槽函数上
-
如果没有 disconnect, 就会构成 一个信号绑定了两个槽函数. 触发信号的时候, 两个槽函数都会执行
7.4 lambda表达式
定义槽函数的时候 也是可以使用lambda表达式的,
- 为了解决lambda访问作用域的问题,就需要引入变量捕捉的语法规则
- lambda语法是c++11中引入的,对于Qt5及其更高版本,默认就是按照c++ 11来编译的
- 如果使用Qt4或者更老的版本,就需要手动在.pro文件中加上C++的编译选项:CONFIG += c++11
8. 登录案例(动手)
我们要实现的是一个登录和注册的页面,具体参考下图:
要求说明:
- 实现一个登录系统的主页面,2个功能,一个登录,一个注册
- 登录成功,需要跳转到欢迎界面,并欢迎这个登录的用户
- 注册成功,需要跳转到主页面,并把注册到的信息填入对于的输入框中
- .....
注意: 信号与槽不能嵌套,且电脑分辨率不同,会对qt designer产生影响
4.1 关键点:如何新建一个窗口
说明一下:
- 然后选择对应的控件就行了
4.2 关键点:如何展示子类的页面
推荐:在父类中添加一个成员变量,类型是子类的类型,然后在构造函数中实例化这个对象,如下:
而想要展示一个页面直接调用show方法,关闭一个页面直接调用hide方法
4.3 关键点:如何把父类的数据传递给子类
可以使用信号与槽,但这里推荐直接传递参数就可以了,处理参数的函数在子类中实现就可以了, 毕竟父类中有个成员变量是指向子类的(具体情况如上)
4.4 关键点:如何把子类的数据传递给父类
推荐:使用自定义信号与槽,首先在子类中自定义一个信号,然后再父类的构造函数中connect自定义信号与槽,然后再子类中要传数据的地方emit自定义信号,对了槽函数需要在父类中实现
第四节 Qt Core
Qt Core也就是Qt的核心部分
1. QString常见函数
1.1 append - 字符串拼接
void function1()
{QString str = "123456";str.append("abc");// 字符串拼接qDebug() << str;
}
1.2 prepend - 头前拼接
void function2()
{QString str = "123456";str.prepend("abc");// 字符串拼接qDebug() << str;
}
1.3 arg - 格式化
void function3()
{QString str = "姓名:%1,年龄:%2,性别:%3";QString format = str.arg("张三").arg(30).arg("男");qDebug() << format;
}
1.4 contains - 判断是否包含
void function4()
{QString str = "123abc456";if(str.contains("abc")){qDebug() << "包含abc" << endl;}else{qDebug() << "不包含abc" << endl;}
}
1.5 isEmpty && isNull
void function5()
{QString str1 = "";QString str2;if(str1.isEmpty()){qDebug() << "str1是empty的" << endl;}if(str1.isNull()){qDebug() << "str1是null的" << endl;}if(str2.isEmpty()){qDebug() << "str2是empty的" << endl;}if(str2.isNull()){qDebug() << "str2是null的" << endl;}
}
说明一下:
- isEmpty()是用来判断字符串是否是空字符串的
- isNull()既能判断是否是NULL,而且还能判断是不是空字符串
1.6 startWith -- 判断是否以xxxxx打头
void function6()
{QString str = "123456";if(str.startsWith("123")){qDebug() << "str是以12打头的" << endl;}
}
1.7 indexOf - 找xxx出现的位置
void function7()
{QString str1 = "www.baidu.com";//找第一次.出现的位置qDebug() << str1.indexOf(".") << endl;
}
说明一下:
- 这个函数就类似于c++中的find函数
- 同理下标索引也是从0开始的
1.8 repalce - 替换
QString &QString::replace(int position, int n, const QString &after)
参数说明:
- 第一个参数是被替换字符串的位置
- 第二个参数是被替换字符串的长度
- 第三个参数是新的字符串
void function8()
{QString str1 = "www.baidu.com";//找第一次.出现的位置QString target = ".";int index = str1.indexOf(target);str1.replace(index,target.length(),"//");qDebug() << str1 << endl;
}
1.9 split - 分割字符串
void function9()
{QString str = "111:222:333:444";// 将str按照:分割成4个子字符串QStringList list = str.split(":");qDebug() << list << endl;
}
说明一下:
- split的返回值是QStringList类型,而这个类型就相当于一个只能存放QString的vector
1.10 string 与 QString相互转换
- QString::fromStdString(s);// 把std::string 转换成QString
- s.toStdString();//把QString转换成std::string
2. QByteArray
多用于处理字节数组
void function1()
{QString str = "123456";QByteArray data = str.toUtf8();QString str1 = QString(data);qDebug() << str1 << endl;
}
3. QStringList && QVector
QStringLis等价于QVector<QString>
void function2()
{QStringList v1 = {"ab"};v1.push_back("cd");for(auto e : v1){qDebug() << e << " ";}QVector<QString> v2 = {"ab"};v2.push_back("cd");for(auto e : v2){qDebug() << e << " ";}
}
说明一下:
- 除了QVector还有QList,QStack等等,前面已经说了QT它又搞了一套新的轮子,
- 其实就是又封装了一遍