本笔记参考黑马的Qt教程
Qt优点
- 跨平台
- 接口简单,容易上手
- 一定程度上简化了内存回收机制。。。
- 成功案例:
- Linux桌面环境KDE
- WPS
- 谷歌地图
Qt5包含的模块
窗口三大基类关系
Widget: 小装置、小部件
.pro文件解释
QT += core gui #Qt要包含core和gui模块
#大于4版本就加上widgets模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
#指定目标文件的名称,win下是.exe文件
TARGET = 01_QtFirst
#创建一个用于构建应用程序的Makefile(默认)
TEMPLATE = app
# 指定项目中的所有源文件
SOURCES += main.cpp\
mywidget.cpp
# 指定项目中的头文件
HEADERS += mywidget.h
命名规范
- 类名:首字母大写的驼峰命名法(MyWidget)
- 变量、函数名:首字母小写的驼峰法(如:getMesssage)
快捷键
- 快捷键
- 运行
ctrl + R
- 编译
ctrl + B
- 查询
ctrl + F
- 注释
ctrl + /
- 帮助
F1
- 字体缩放
ctrl + 鼠标滚轮
- 整行代码移动
ctrl + shift + ↑ ↓
- 自动对齐
ctrl + i
- 同名之间的.h .cpp切换
F4
- 帮助文档 F1 左侧列表中按钮
C:\Qt\Qt5.6.0\5.6\mingw49_32\bin
Qt对象树
在Qt中创建对象的时候会提供一个Parent对象指针,下面来解释这个parent到底是干什么的。
QObject是以对象树的形式组织起来的。
当你创建一个QObject对象时,会看到QObject的构造函数接收一个QObject指针作为参数,这个参数就是 parent,也就是父对象指针。
这相当于,在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的children()列表。
当父对象析构的时候,这个列表中的所有对象也会被析构。(注意,这里的父对象并不是继承意义上的父类!)
这种机制在 GUI 程序设计中相当有用。例如,一个按钮有一个QShortcut(快捷键)对象作为其子对象。当我们删除按钮的时候,这个快捷键理应被删除。这是合理的。
QWidget是能够在屏幕上显示的一切组件的父类。
QWidget继承自QObject,因此也继承了这种对象树关系。一个孩子自动地成为父组件的一个子组件。因此,它会显示在父组件的坐标系统中,被父组件的边界剪裁。例如,当用户关闭一个对话框的时候,应用程序将其删除,那么,我们希望属于这个对话框的按钮、图标等应该一起被删除。事实就是如此,因为这些都是对话框的子组件。
当然,我们也可以自己删除子对象,它们会自动从其父对象列表中删除。比如,当我们删除了一个工具栏时,其所在的主窗口会自动将该工具栏从其子对象列表中删除,并且自动调整屏幕显示。
Qt 引入对象树的概念,在一定程度上解决了内存问题。
当一个QObject对象在堆上创建的时候,Qt 会同时为其创建一个对象树。不过,对象树中对象的顺序是没有定义的。这意味着,销毁这些对象的顺序也是未定义的。
任何对象树中的 QObject对象 delete 的时候,如果这个对象有 parent,则自动将其从 parent 的children()列表中删除;如果有孩子,则自动 delete 每一个孩子。Qt 保证没有QObject会被 delete 两次,这是由析构顺序决定的。
如果QObject在栈上创建,Qt 保持同样的行为。正常情况下,这也不会发生什么问题。来看下下面的代码片段:
{
QWidget window;
QPushButton quit("Quit", &window);
}
作为父组件的 window 和作为子组件的 quit **都是QObject的子类(**事实上,它们都是QWidget的子类,而QWidget是QObject的子类)。这段代码是正确的,quit 的析构函数不会被调用两次,因为标准 C++要求,局部对象的析构顺序应该按照其创建顺序的相反过程。因此,这段代码在超出作用域时,会先调用 quit 的析构函数,将其从父对象 window 的子对象列表中删除,然后才会再调用 window 的析构函数。
但是,如果我们使用下面的代码:
{
QPushButton quit("Quit");
QWidget window;
quit.setParent(&window);
}
情况又有所不同,析构顺序就有了问题。我们看到,在上面的代码中**,作为父对象的 window 会首先被析构,因为它是最后一个创建的对象。**在析构过程中,它会调用子对象列表中每一个对象的析构函数,也就是说, quit 此时就被析构了。然后,代码继续执行,在 window 析构之后,quit 也会被析构,因为 quit 也是一个局部变量,在超出作用域的时候当然也需要析构。但是,这时候已经是第二次调用 quit 的析构函数了,C++ 不允许调用两次析构函数,因此,程序崩溃了。
由此我们看到,Qt 的对象树机制虽然帮助我们在一定程度上解决了内存问题,但是也引入了一些值得注意的事情。
坐标系
-
x以右侧为正,y以下侧为正
-
左上角是(0, 0)
信号与槽
例子代码:在main函数中:
MyPushButton *myBtn = new MyPushButton;
//会使得该按钮对象上对象树了
myBtn->setParent(this);
myBtn->setText("myButton");
myBtn->move(300, 300);
//实现功能:点击myBtn,关闭窗口(MyWidget对象)
//connect( 信号发送者,发送的信号, 信号接受者, 处理的槽函数)
//槽函数就类似于收到信号后触发的【回调函数】
//信号和槽的优点:松散耦合(将没有关联的两个对象通过事件关联起来)
connect(myBtn, &QPushButton::clicked, this, &QWidget::close);
自定义信号与槽
触发自定义信号使用emit
例子:
student.h
#ifndef STUDENT_H
#define STUDENT_H
#include <QObject>
class Student : public QObject
{
Q_OBJECT
public:
explicit Student(QObject *parent = 0);
signals:
//自定义槽函数:
//写到 public slots下,Qt 5.0以上
//可以写成全局函数或者public作用域下 或者 lambda表达式
public slots:
//返回值是void,需要声明,也需要实现
//可以有参数,可以重载
void treat();
};
#endif // STUDENT_H
teacher.h
#ifndef TEACHER_H
#define TEACHER_H
#include <QObject>
class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = 0);
//自定义信号写到signals下
signals:
//返回值void
//只需要声明,无需实现
//可以有参数,可以发生重载
void hungry();
public slots:
};
#endif // TEACHER_H
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "student.h"
#include "teacher.h"
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
Teacher *te;
Student *st;
//下课,老师发出饥饿信号
void classOver();
};
#endif // WIDGET_H
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
student.cpp
#include "student.h"
#include <QDebug>
Student::Student(QObject *parent) : QObject(parent)
{
}
void Student::treat()
{
qDebug() << "请老师吃饭";
}
teacher.cpp
#include "teacher.h"
Teacher::Teacher(QObject *parent) : QObject(parent)
{
}
widget.cpp(重要)
#include "widget.h"
//Teacher老师类
//Student学生类
//下课后,老师触发一个 饿了 信号
//学生收到这个信号之后,响应 并 请老师吃饭
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//设置te对象的父亲为本对象
//从而该对象就上了本Widget对象的对象树
//析构和释放就可以交给Qwidget来实现
this->te = new Teacher(this);
this->st = new Student(this);
//连接信号和槽函数
//指定 老师向学生发出饥饿信号时,学生应做的回应
connect(te, &Teacher::hungry, st, &Student::treat);
classOver();
}
void Widget::classOver()
{
//触发自定义信号
emit this->te->hungry();
}
Widget::~Widget()
{
}
注意事项
//1.信号可以连接信号
connect(te, teacherSignal2, st, studentSlot2);
//按钮按下信号发出给老师,老师会执行发出teacherSignal2信号操作
connect(btn, &QPushButton::clicked, te, teacherSignal2);
//2.可以断开信号和槽函数连接
//disconnect(te, teacherSignal2, st, studentSlot2);
//3.一个信号可以响应多个槽函数(一个信号发出到接收者处,接收者可执行多个函数)
//4.多个信号可以连接同一个槽函数
//5.信号和槽函数的【参数】类型需一一对应
//信号的参数个数可以多于槽函数的个数,反之不对
//参数类型要一一对应
Qt4信号和槽的写法
//Qt4信号和槽的写法
//利用Qt4版本连接有参信号和槽
//优势:参数直观
//劣势:参数不做匹配检测,参数不正确也能构建通过
//Qt4:实际上SIGNAL("hungry(QString") SLOT("treat(QString)")
connect(te, SIGNAL(hungry(QString)), st, SLOT(treat(QString)));
classOver();
使用lambda表达式的槽函数
//1.实现按下名为bbb的按钮,按钮名字变为aaa
QPushButton *btn4 = new QPushButton("bbb", this);
btn4->move(100, 0);
//btn4按下的信号发送到当前父控件对象,会执行将btn4名字改为aaa的动作
connect(btn4, &QPushButton::clicked, this, [=](){
btn4->setText("aaa"); //这里值捕获相当于拷贝了一个QPushButton指针
});
//注意:lambda不可以为引用捕获即[&]
//因为当进行信号和槽的连接时,控件内会进入一个锁的状态,不允许修改对应的属性
//2.mutable相关
QPushButton *myBtn = new QPushButton(this);
QPushButton *myBtn2 = new QPushButton(this);
myBtn2->move(100, 100);
int m = 10;
//这行修改的只是m的一份拷贝
//这里一定要加【mutable】,不加会提示修改只读变量
//即便加了mutable,修改的也只是拷贝,所以用的不多
connect(myBtn, &QPushButton::clicked, this, [m]() mutable{ m = 20; qDebug()<< m;});
connect(myBtn2, &QPushButton::clicked, this, [=]() { qDebug() << m; });
qDebug()<< m;//没有按下按钮前,这个会先打印
//3.->指定返回值类型
int num = [=]()->int{
return 1000;
}();
qDebug()<< "num = "<< num;
//4.将点击信号绑定到多个槽函数上,但先执行哪一个函数不能确定
//点击btn4,关闭窗口
connect(btn4, &QPushButton::clicked, this, [=](){
//this->close();
st->treat("糖醋里脊");
});
QString转换为C风格字符串
void Student::treat(QString foodName)
{
//QString转 char* 通过toUtf8转换为QByteArray
//再通过.data转换为 char *
qDebug() << "请老师吃饭,老师要吃" <<foodName.toUtf8().data();
}
总结
杂项
- 防止打印中文出现乱码,可以在文件开头添加:
#pragma execution_character_set("utf-8")
顺便在工具
->选项
->行为
中设置文件编码
为UTF-8
,并且UTF-8BOM
设置为 总是删除
,如果还出现乱码,把中文重新输入一遍即可。
问题
-
qt的模块和相关的头文件有什么差别?
-
qt父亲和孩子控件的析构顺序:后创建先释放
-
父类对象析构的时候,先把析构函数内代码执行,然后去找子类对象,最后从最下面儿子开始真正释放对象。
-
出现LNK2019: 无法解析的外部符号的错误时候,可以尝试删除项目文件下的编译文件夹,如build-01_QtFirst-Desktop_Qt_5_6_3_MSVC2013_32bit-Debug