qt学习笔记(一)

2 篇文章 0 订阅

本笔记参考黑马的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设置为 总是删除,如果还出现乱码,把中文重新输入一遍即可。

问题

  1. qt的模块和相关的头文件有什么差别?

  2. qt父亲和孩子控件的析构顺序:后创建先释放

  3. 父类对象析构的时候,先把析构函数内代码执行,然后去找子类对象,最后从最下面儿子开始真正释放对象。

  4. 出现LNK2019: 无法解析的外部符号的错误时候,可以尝试删除项目文件下的编译文件夹,如build-01_QtFirst-Desktop_Qt_5_6_3_MSVC2013_32bit-Debug

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值